I am developing a REST API using Node.JS and AWS Lambda, which will be accessed by 3 apps.
Android app developed with Flutter
iOS app developed with Flutter
Web app developed with Java
I have always been a Java guy and never a Javascript guy. This is my first time on serious job with Javascript stuff.
Normally when we create REST APIs in Java, it will get the data from the database, convert it to a Java class and send it back as the response.
For an example, lets assume below is the structure of my database table. Have a close look at the names, AND the associated foreign keys.
In my Java based REST API, this will be the class.
public class SellerSettings implements java.io.Serializable {
private Integer idsellerSettings;
private User user;
private double responseRate;
private double responseTime;
private boolean isavailable;
public SellerSettings() {
}
public Integer getIdsellerSettings() {
return this.idsellerSettings;
}
public void setIdsellerSettings(Integer idsellerSettings) {
this.idsellerSettings = idsellerSettings;
}
public User getUser() {
return this.user;
}
public void setUser(User user) {
this.user = user;
}
public double getResponseRate() {
return this.responseRate;
}
public void setResponseRate(double responseRate) {
this.responseRate = responseRate;
}
public double getResponseTime() {
return this.responseTime;
}
public void setResponseTime(double responseTime) {
this.responseTime = responseTime;
}
public boolean getIsavailable() {
return this.isavailable;
}
public void setIsavailable(boolean isavailable) {
this.isavailable = isavailable;
}
}
When the data is requested from an API built with Java, it will send the response back with the same set of names you see in the java class I presented. Since the user is another class, it will actually add that's fields as well..
Anyway, this is my node.js code now.
const mysql = require('mysql');
const con = mysql.createConnection({
host : "*******.rds.amazonaws.com",
user : "****",
password : "*****",
port : 3306,
database : "*****"
});
exports.lambdahandler = (event, context, callback) => {
// allows for using callbacks as finish/error-handlers
context.callbackWaitsForEmptyEventLoop = false;
const sql = "select * from seller_settings";
con.query(sql, function (err, result) {
if (err) throw err;
var response = {
"statusCode": 200,
"headers": {
"Content-Type": "application/json"
},
"body": JSON.stringify(result),
"isBase64Encoded": false
};
callback(null, response)
});
};
And it return the below
[
{
"idseller_settings": 1,
"iduser": 1,
"response_rate": 90,
"response_time": 200,
"isavailable": 0
},
{
"idseller_settings": 2,
"iduser": 1,
"response_rate": null,
"response_time": 210,
"isavailable": 0
}
]
It is nothing but the pure table names.
As a Java guy, I looked at how we can convert these to the Java like names. Then I found Javascript classes. However I do understand that Javascript is not OOP as Java or anything similar. It is not perfect on OOP. Also adding these class, converting the values from JSON to classes, and the sending it back seems to be an over kill for my simple, nice Node.JS code.
As a Java guy, I would ask,
When requested via the REST API, Is it normal to grab the data from the database tables as per column names as it is and send it back in Javascript world? Or should I create classes?
My mobile and web apps are using classes in their languages, when sending POST requests they will most probably send a JSON that contains Java classe's variables I shared. From the node.js side, this can be converted to table names?
Related
My guess is I am dealing with an IIS routing issue.
I have a React/dotnet core 3.1 application that I am developing in Visual Studio. When loaded, the React app attempts to call a REST service in the application to pull in data for a select component (the select component is a drop-down of companies).
All works well in the development environment. When I deploy to a staging server, all API calls are redirected to index.html and I never get the data I was expecting.
My best guess is I have misconfigured routing in my IIS 7.5 server and it does not know how to handle calls to "/api/"
appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
DATA REMOVED HERE
}
}
Program.cs:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args)
.Build()
.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
// DI
services.AddSingleton<IConfiguration>(this.Configuration);
/*services.Configure<IISServerOptions>(options =>
{
options.AutomaticAuthentication = false;
});*/
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
}
The URL of the main app is: http://vmsaudit.esdev2.elwood.local
The API Call URL of the server is:
http://vmsaudit.esdev2.elwood.local/api/Company/er
Here is the code to my Company Controller:
[ApiController]
[Route("api/[controller]")]
public class CompanyController
{
VMSImportEngineContext db;
ErecruitContext erdb;
IConfiguration config;
public CompanyController (VMSImportEngineContext db, ErecruitContext erdb, IConfiguration config)
{
this.db = db;
this.erdb = erdb;
this.config = config;
}
[HttpGet("{datastore}")]
public List<Company> Get(string datastore)
{
List<Company> ret = new List<Company>();
if ("ss".Equals(datastore))
{
List<int?> vmsClients = db.VmsClient
.Where(c => c.legacyID != null && c.audit == true)
.Select(c => c.legacyID)
.ToList();
ret = this.GetSSCompanies(vmsClients);
}
else // Has to be ERecruit client
{
List<int> vmsClients = db.VmsClient
.Where(c => c.audit == true)
.Select(c => c.clientid)
.ToList();
ret = this.erdb
.Company
.Where(c => vmsClients.Contains(c.company_id))
.ToList();
}
return ret;
}
private List<Company> GetSSCompanies(List<int?> vmsClients)
{
List<Company> ret = new List<Company>();
// Generate a linked query
string connectionString = this.config.GetConnectionString("vmsimportengine");
SqlConnection connection = new SqlConnection(connectionString);
if (connection.State != System.Data.ConnectionState.Open)
{
connection.Open();
}
DbCommand command = connection.CreateCommand();
command.CommandText = GenSSCompanyQuery(vmsClients);
DbDataReader reader = command.ExecuteReader();
while (reader.Read())
{
Company company = GetCompanyFromDataRow(reader);
ret.Add(company);
}
return ret;
}
private Company GetCompanyFromDataRow(DbDataReader reader)
{
Company c = new Company();
if (!reader.IsDBNull(reader.GetOrdinal("name")))
{
c.name = reader.GetString(reader.GetOrdinal("name"));
}
if (!reader.IsDBNull(reader.GetOrdinal("company_component_id")))
{
int cid = (int)reader.GetDouble(reader.GetOrdinal("company_component_id"));
// now get the real ID from the current system...
List<int> potential = this.db.VmsClient
.Where(c => c.legacyID == cid)
.Select(c => c.clientid)
.ToList();
if (potential.Count > 0)
{
c.company_id = potential[0];
}
}
return c;
}
private string GenSSCompanyQuery(List<int?> vmsClients)
{
// Code Suppressed
}
}
UPDATE
I decided to add a new method to my controller and that method appears to be returning data... My issue apparently is with passing a parameter on the end of the API URL call??? Here is the method I just added to my controller which successfully returns "pong"
[HttpGet]
public string ping()
{
return "pong";
}
In summary:
This API URL Works - http://vmsaudit.esdev2.elwood.local/api/Company
This API URL does not - http://vmsaudit.esdev2.elwood.local/api/Company/ss
The only difference is the parameter on the end of the URL
There are a couple of potential issues in your code. Nobody will tell you for sure what is wrong, but you can try to play with some settings.
Try to comment this line in your startup.cs. Better, use HTTPS redirect on IIS side. Your app is running on Kestrel server which is running behind IIS, so if SSL is not set up on IIS, your app will die silently with some cryptic error, like site cannot be reached.
app.UseHttpsRedirection();
In production, you're supposed to build React project yourself, maybe you forgot to do this. For testing purposes, try to comment all lines like below or set environment in your appsettings.json to DEVELOPMENT. In this case you can try to check if your app is running the same way in production as it does on your local computer.
env.IsDevelopment()
To avoid CORS issues, try to allow not only all hosts, but all headers and methods
builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()
https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-3.1
By default, Kestrel server accepts requests only from localhost. When you deploy it to production server, you may need to allow requests coming from specific IP, or from all IPs on specific ports.
"Kestrel": {
"EndPoints": {
"Http": {
"Url": "http://0.0.0.0:5000"
}
}
}
For detailed guide of how to fix other potential issues with IIS, check this answer.
https://superuser.com/questions/1514511/iis-is-not-accessible-remotely-on-vps-hosting
The issue was a database connection. The authentication credentials for the application pool I was using was incorrect. The result was all REST service calls that used the connection returned the index.html page. I assume this is because the Entity Framework code threw an exception in startup and the routing code was never reached?
For my PHP app, I need to use the Syncfusion Javascript Word Processor. To instantiate it with a default text, Syncfusion asks that this text be formatted in SFDT, a kind of JSON.
//SFDT Example
"sections": [
{
"blocks": [
{
"inlines": [
{
"characterFormat": {
"bold": true,
"italic": true
},
"text": "Hello World"
}
]
}
],
"headersFooters": {
}
}
]
This code show this : Link
With a .NET Core Package Syncfusion.EJ2.WordEditor.AspNet.Core, I can convert a doc(x) file to sfdt format. So I create a new .NET Core Web Api App with Visual Studio 2017 For Mac with this package.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Syncfusion.EJ2.DocumentEditor;
namespace SyncfusionConverter.Controllers
{
[Route("api/[controller]")]
public class SyncfusionController : Controller
{
[AcceptVerbs("Post")]
public string Import(IFormCollection data)
{
if (data.Files.Count == 0)
return null;
Stream stream = new MemoryStream();
IFormFile file = data.Files[0];
int index = file.FileName.LastIndexOf('.');
string type = index > -1 && index < file.FileName.Length - 1 ?
file.FileName.Substring(index) : ".docx";
file.CopyTo(stream);
stream.Position = 0;
WordDocument document = WordDocument.Load(stream, GetFormatType(type.ToLower()));
string sfdt = Newtonsoft.Json.JsonConvert.SerializeObject(document);
document.Dispose();
return sfdt;
}
internal static FormatType GetFormatType(string format)
{
if (string.IsNullOrEmpty(format))
throw new NotSupportedException("EJ2 DocumentEditor does not support this file format.");
switch (format.ToLower())
{
case ".dotx":
case ".docx":
case ".docm":
case ".dotm":
return FormatType.Docx;
case ".dot":
case ".doc":
return FormatType.Doc;
case ".rtf":
return FormatType.Rtf;
case ".txt":
return FormatType.Txt;
case ".xml":
return FormatType.WordML;
default:
throw new NotSupportedException("EJ2 DocumentEditor does not support this file format.");
}
}
}
}
I make a Ajax request to call this .Net method with my doc(x) file as parameter.
function loadFile(file) {
const ajax = new XMLHttpRequest();
const url = 'https://localhost:5001/api/Syncfusion/Import';
ajax.open('POST', url, true);
ajax.onreadystatechange = () => {
if (ajax.readyState === 4) {
if (ajax.status === 200 || ajax.status === 304) {
// open SFDT text in document editor
alert(ajax.status);
}else{
alert(ajax.status);
}
}else{
alert(ajax.readyState);
}
};
let formData = new FormData();
formData.append('files', file);
ajax.send(formData);
}
When loadFile function is executed, i got this error in browser's console : "Cross-Origin Request (Blocking of a Multi-Origin Request): the "Same Origin" policy does not allow to consult the remote resource located on https://localhost:5001/Syncfusion/Import. Reason: CORS request failed."
I follow this tutorial and those SO posts Link1 Link2 but it doesn't work. Any idea to solve this problem ?
Edit 1 : It seems that my code work on Safari & Chrome, but doesn't work on Firefox.
Edit 2 : Startup.cs
namespace SyncfusionConverter
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(setup => setup.AddPolicy("CorsPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
}));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseCors("CorsPolicy");
app.UseHttpsRedirection();
app.UseMvc();
}
}
}
Your code looks fine. My guess is that firefox has an issue with your self signed development certificate of your asp.net core application. We had this several times in the past and the firefox error message were always a bit misleading.
What we did to "fix" it was:
Open https://localhost:5001 in firefox
You should see a certificate error in firefox now
"Trust" the self signed certificate / add an exception for it
Try your api call again. It should work now
I am new to Windows Azure mobile services , and now i need to post sign up data to Mysql table in Windows Azure Mobile Services.
I have made a pojo class (USER) , and saved user entered data to them , and now calling custom api line using this line.
ListenableFuture<User> result = mClient.invokeApi( "signup", User.class );
Log.d("try","THIS LINE IS OKAY");
Futures.addCallback(result, new FutureCallback<User>() {
#Override
public void onFailure(Throwable exc) {
createAndShowDialog((Exception) exc, "Error");
}
#Override
public void onSuccess(User result) {
createAndShowDialog( " item(s)inserted", "Success");
}
});
}
I have created a Custom API , in Mobile Services ,
exports.post= function(request, response) {
var queryString = "INSERT INTO User (user_name, user_email) VALUES (?,?)" ;
request.service.mssql.query(queryString, [request.query.username, request.query.user_email], {
success: function(results) {
request.respond(statusCodes.OK, results);
}
});
};
While running app , i am getting a Internal Server Error 500.
Any one please help me to resolve issue,
As mentioned by #carlosfiguera, you need to pass parameters to invokeApi. Here's he documentation on how to do this: How to: call a custom API in Android.
i'm using this method directly from Facebook Developer to get users information after login, i need to get username, userid and email
public void onSuccess(LoginResult loginResult) {
GraphRequest request = GraphRequest.newMeRequest(
loginResult.getAccessToken(),
new GraphRequest.GraphJSONObjectCallback() {
#Override
public void onCompleted(
JSONObject object,
GraphResponse response) {
// Application code
}
});
Bundle parameters = new Bundle();
parameters.putString("fields", "id,name,email");
request.setParameters(parameters);
request.executeAsync();
how should i do to get the JSON information and store it in Public variable in my document, to use it in other function ?
I am creating a REST API and I have been playing with the idea of allowing bundling of requests from clients. By bundling I mean they can send one request, containing multiple "real" requests, and they get delivered to the client together. Typically javascript ajax requests. Something like this:
POST /bundlerequest
["/person/3243", "/person/3243/friends", "/comments/3243?pagesize=10&page=1", "/products", "/product/categories" ]
(The bundled request can only be GET requests, as of now at least)
This is intended to return something like this
{
"success" : ["/person/3243", "/person/3243/friends", "/comments/3243?pagesize=10&page=1", "/products", "/product/categories" ],
"error" : [],
"completiontime" : 94,
other relevant metadata...
"responses" : [
{"key" : "/person/3243" , "data" : {"name" : "John", ...} },
{"key" : "/person/3243/friends" , "data" : [{"name": "Peter", "commonfriends" : 5, ...}] },
etc...
]
}
The benefits of this bundling is that it reduces the number of requests and that is especially important on mobile devices for instance.
So my first question is, is my approach to this a good one? Does anyone have experience with doing something like this?
AFAIK the common way of solving this is to write server side code to return combined data, that I believe is relevant for the client(s). (The twitter user stream for instance does this, combining person info, latest tweets, latest personal messages etc.) But this makes the API very opinionated and when the client needs changes the server might need to change to accomodate to optimize.
And the second question is how to implement this?
My backend is ASP.NET MVC 3 and IIS 7. Should I implement it in the application, having an bundlerequest action that internally calls the other actions specified in the request?
Could it be implemented in IIS 7 directly? Writing a module that transparently intercepts requests to /bundlerequest and then calls all the corresponding sub requests, making the application totally unaware of the bundling that happens? This would also allow me to implement this in an application-agnostic way.
You could use an asynchronous controller to aggregate those requests on the server. Let's first start by defining a view model that will be returned by the controller:
public class BundleRequest
{
public string[] Urls { get; set; }
}
public class BundleResponse
{
public IList<string> Success { get; set; }
public IList<string> Error { get; set; }
public IList<Response> Responses { get; set; }
}
public class Response
{
public string Key { get; set; }
public object Data { get; set; }
}
then the controller:
public class BundleController : AsyncController
{
public void IndexAsync(BundleRequest request)
{
AsyncManager.OutstandingOperations.Increment();
var tasks = request.Urls.Select(url =>
{
var r = WebRequest.Create(url);
return Task.Factory.FromAsync<WebResponse>(r.BeginGetResponse, r.EndGetResponse, url);
}).ToArray();
Task.Factory.ContinueWhenAll(tasks, completedTasks =>
{
var bundleResponse = new BundleResponse
{
Success = new List<string>(),
Error = new List<string>(),
Responses = new List<Response>()
};
foreach (var task in completedTasks)
{
var url = task.AsyncState as string;
if (task.Exception == null)
{
using (var response = task.Result)
using (var stream = response.GetResponseStream())
using (var reader = new StreamReader(stream))
{
bundleResponse.Success.Add(url);
bundleResponse.Responses.Add(new Response
{
Key = url,
Data = new JavaScriptSerializer().DeserializeObject(reader.ReadToEnd())
});
}
}
else
{
bundleResponse.Error.Add(url);
}
}
AsyncManager.Parameters["response"] = bundleResponse;
AsyncManager.OutstandingOperations.Decrement();
});
}
public ActionResult IndexCompleted(BundleResponse response)
{
return Json(response, JsonRequestBehavior.AllowGet);
}
}
and now we can invoke it:
var urls = [
'#Url.Action("index", "person", new { id = 3243 }, Request.Url.Scheme, Request.Url.Host)',
'#Url.Action("friends", "person", new { id = 3243 }, Request.Url.Scheme, Request.Url.Host)',
'#Url.Action("index", "comments", new { id = 3243, pagesize = 10, page = 1 }, Request.Url.Scheme, Request.Url.Host)',
'#Url.Action("index", "products", null, Request.Url.Scheme, Request.Url.Host)',
'#Url.Action("categories", "product", null, Request.Url.Scheme, Request.Url.Host)'
];
$.ajax({
url: '#Url.Action("Index", "Bundle")',
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(urls),
success: function(bundleResponse) {
// TODO: do something with the response
}
});
Of course some tweaking might be necessary to adapt this to your specific needs. For example you mentioned sending AJAX requests with session expired which might redirect to the Logon page and thus not capturing the error. That's indeed a PITA in ASP.NET. Phil Haack blogged a possible way to circumvent this undesired behavior in a RESTful manner. You just need to add a custom header to the requests.
I suggest looking wcf web api