I have built , using express() , a variety of methods. for simplicity let's I assume I built 2 POST() functions and I want to be able to use them by themselves and also to concatenate them via middleware for combine usage.
app.post('/create_obj_1' , function (req,res) {
//create Object_type_1
// send Object_type_1 via EXTERNAL API to somewhere
res.json({{ "statusCode": 200, "message": "OK" }
}
app.post('/create_obj_2' , function (req,res) {
//create Object_type_2
// send Object_type_2 via EXTERNAL API to somewhere
res.json({{ "statusCode": 200, "message": "OK" }
}
I want to have a new POST() that can invoke both of the other 2 (but still support stand alone invoking of the original 2
I think it's possible via middleware but I am not sure how - this is how I thought the new POST() should look like -
app.post('/create_obj_all' , function (req,res) {
//I want to invoke the create_obj_1 & create_obj_2 , check all OK, and finish
res.json({{ "statusCode": 200, "message": "OK" }
}
I am not sure how to approach the middleware usage in such case.
On top - how can I connect them to use one each other res? let's say the EXTERNAL API returns some value from obj_1 creation which I want to use in obj_2 post() function..
a Pseudo code of my attempt to use request() inside the middlware_1 -
var middle_1 = function (req, res, next) {
req.middle_1_output = {
statusCode : 404,
message : "fail_1"
}
var options = {
method: 'PUT', url: `EXTERNAL_API`, headers:
{
'cache-control': 'no-cache',
'content-type': 'application/x-www-form-urlencoded',
apikey: `KEY`
}
};
request(options, function (error, response, body) {
if (error) throw new Error(error);
// CODE THAT DO SOMETHING AND GET INFORMATION
// OLD WAY OF res.send here , to allow using in post.POST() was - res.status(200).send(body);
//res.status(200).send(body);
req.middle_1_output.statusCode = 200;
req.middle_1_output.message = "hello world";
});
next(); // trigger next middleware
}
Given the current example, I don't think you can do it unless you tweak the middlewares for the first two routes a bit:
var middleware1 = function(req, res, next) {
//create Object_type_1
// send Object_type_1 via EXTERNAL API to somewhere
next(); // calling next() triggers the next middleware
};
var middleware2 = function(req, res, next) {
//create Object_type_2
// send Object_type_2 via EXTERNAL API to somewhere
next(); // calling next() triggers the next middleware
};
/**
* This middleware is only used to send success response
*/
var response_success = function(req, res) {
res.json({ "statusCode": 200, "message": "OK" });
}
app.post('/create_obj_1', middleware1, response_success);
app.post('/create_obj_2', middleware2, response_success);
app.post('/create_obj_all', middleware1, middleware2, response_success);
Note that this is a very simplistic solution that I made from your example. The actual implementation will depend on what input each middleware is expecting and what output they generate. Also unlike here, there may also be different middlewares for sending the response.
2nd Part Addressing the second part of your question, if I have got you correctly you want to pass the output from middleware1 to middleware2. You can simply attach the output to the req object before calling next();. Like so:
var middleware1 = function(req, res, next) {
// do something
some_external_api_call(function(error, data) {
if (error) {
// handle the error yourself or call next(error);
} else {
req.middleware1_output = data; // set the output of the external api call into a property of req
next();
}
});
};
var middleware2 = function(req, res, next) {
// check to see if the middleware1_output has been set
// (meaning that the middleware has been called from /create_obj_all )
if (req.middleware1_output) {
// do something with the data
} else {
// handle scenario when /create_obj_2 is called by itself
}
next(); // calling next() triggers the next middleware
};
Notice how you have to account for both scenarios where middleware2 is called from POST /create_obj_all or directly from POST /create_obj_2.
3rd Part You should call next from within the callback. See my above example. This is due to the asynchronous/non-blocking nature of javascript.
function middleware(req, res, next) {
// do something
call_1st_external_api(some_options, function(error, data) {
// executed after call_1st_external_api completes
req.output_of_1st_external_api = data; // store the data of this api call for access from next middleware
next(); // calls the next middleware
// nothing here will be executed as next has already been called
});
// anything here will be executed before call_1st_external_api is completed
next(); // this will call the next middleware before call_1st_external_api completes
}
To handle two external APIs in the same middlewares you have to nest them (or use async or promises):
function middleware(req, res, next) {
// do something
call_1st_external_api(some_options, function(error1, data1) {
// executed after call_1st_external_api completes
req.output_of_1st_external_api = data1; // store the data of this api call for access from next middleware
// executed after call_2nd_external_api completes
call_2nd_external_api(some_options, function(error2, data2) {
req.output_of_2nd_external_api = data2; // store the data of this api call for access from next middleware
next();
});
// anything here will be executed before call_2nd_external_api is completed
});
// anything here will be executed before call_1st_external_api is completed
}
You have to handle all the errors above like I've shown in the 2nd Part which I have not shown in the above example for the sake of simplicity.
Related
I have configured a middleware (router level) which checks for admin access, and if successful I am logging the information and calling next().
middleware:
const validateAdmin = function (scope) {
return function (req, res, next) {
if (req.admin) {
//log the information and using scope to log
next();
} else {
// send the response with 403
}
};
};
router level usage
router.use('/rule/:name', validateAdmin({ serviceScope: this.#serviceScope }), async (req, res) => {
await this._handleRequest(req, res);
});
working case
when i trigger the req from postman, i can see that middleware is called and after next getting executed, the control is coming to 'await this._handleRequest(req, res);' which is expected.
issue
Test case
it.only('shall get single rule', async function () {
const getSingleReq = {
method: 'GET',
admin: true,
originalUrl: '<some req>/rule/rule_1',
baseUrl: '<some_req>',
query: {},
params: { name: 'rule_1' },
headers: {
//token info
},
body: {}
};
const next = sinon.spy();
await router.use.args[1][1](getSingleReq, res, next);
}));
when await router.use.args[1][1](getSingleReq, res, next); is called, I can see the middleware is triggered but next() is not calling the subsequent middleware. I expect that await this._handleRequest(req, res); is called just like the scenario when triggered from postman.
I hope the issue is clear.
Referred many use cases but everywhere the scenario of just checking if next() is called is only done .
app.get('/api/v3/app/events', async function (req, res){
try {
let unique_id=req.query.id
console.log(unique_id)
database.collection('event').findOne(ObjectId(unique_id),function(err,data){
if(err){
res.json({error:"no data found with specified id"})
}
console.log(data)
res.json(data)}
)
} catch (error) {
console.log("internal error")
res.json({error:error})
}
})
app.get('/api/v3/app/events', function(req,res) {
try {
let limit=parseInt(req.query.limit)
let page =parseInt(req.query.page)
console.log(database.collection('event').find().sort({$natural: -1}).limit(limit).skip(page-1).toArray((err, result) => {
console.log(result);
})
)
} catch (error) {
console.log(error)
return res.json({error:"internal error "})
}
})
I have to perform these functionalities with same base url i.e '/api/v3/app/events'.
Please help . I am successful as I change the names of endpoints, but keeping them same , I gets null and undefined on the console
I'm not sure why do you need both to use same URL, but you have two choices either use a single endpoint with both of the logics. The other option would be to use the next middleware based on the id query param like this:
app.get('/api/v3/app/events', async function (req, res, next){
if (!req.query.id) {
next();
return;
}
// Rest of your endpoint logic
}
Each endpoint in Express is considered as middleware. This means that response won't be sent back, but calling the next() middleware instead and allow other middlewares to be executed. You can use same if or modify it based on your login.
I have an API POST route where I receive data from a client and upload the data to another service. This upload is done inside of the post request (async) and takes awhile. The client wants to know their post req was received prior to the async (create project function) is finished. How can I send without ending the POST? (res.send stops, res.write doesn't send it out)
I thought about making an http request back to their server as soon as this POST route is hit. . .
app.post('/v0/projects', function postProjects(req, res, next) {
console.log('POST notice to me');
// *** HERE, I want to send client message
// This is the async function
createProject(req.body, function (projectResponse) {
projectResponse.on('data', function (data) {
parseString(data.toString('ascii'), function (err, result) {
res.message = result;
});
});
projectResponse.on('end', function () {
if (res.message.error) {
console.log('MY ERROR: ' + JSON.stringify(res.message.error));
next(new Error(res));
} else {
// *** HERE is where they finally receive a message
res.status(200).send(res.message);
}
});
projectResponse.on('error', function (err) {
res.status(500).send(err.message);
});
});
});
The internal system requires that this createProject function is called in the POST request (needs to exist and have something uploaded or else it doesn't exist) -- otherwise I'd call it later.
Thank you!
I think you can't send first response that post request received and send another when internal job i.e. createProject has finished no matter success or fail.
But possibly, you can try:
createProject(payload, callback); // i am async will let you know when done! & it will push payload.jobId in doneJobs
Possibility 1, If actual job response is not required:
app.post('/v0/projects', function (req, res, next) {
// call any async job(s) here
createProject(req.body);
res.send('Hey Client! I have received post request, stay tuned!');
next();
});
});
Possibility 2, If actual job response is required, try maintaining queue:
var q = []; // try option 3 if this is not making sense
var jobsDone = []; // this will be updated by `createProject` callback
app.post('/v0/projects', function (req, res, next) {
// call async job and push it to queue
let randomId = randomId(); // generates random but unique id depending on requests received
q.push({jobId: randomId });
req.body.jobId = randomId;
createProject(req.body);
res.send('Hey Client! I have received post request, stay tuned!');
next();
});
});
// hit this api after sometime to know whether job is done or not
app.get('/v0/status/:jobId', function (req, res, next) {
// check if job is done
// based on checks if done then remove from **q** or retry or whatever is needed
let result = jobsDone.indexOf(req.params.jobId) > -1 ? 'Done' : 'Still Processing';
res.send(result);
next();
});
});
Possibility 3, redis can be used instead of in-memory queue in possibility 2.
P.S. There are other options available as well to achieve the desired results but above mentioned are possible ones.
I would like to know how I can move the following code into a separate function (in the same file) and call upon it when either I call the POST or PUT routes to add or update documents.
I'm using https://www.npmjs.org/package/express-validator
The following is currently in my POST route but when I'm updating a record the title will still need to be validated.
app.post('/docs', auth, function (req, res) {
req.checkBody('title', 'Title is required').notEmpty();
var errors = req.validationErrors();
if(errors){
res.json(400, { errors: errors });
return;
}
//go ahead and save the document
});
I've tried making my own function but I'm not sure where to put the var errors = req.validationErrors(); or whether it's bad practice to return 400 errors from a separate function.
Any help/code much appreciated.
The body of the middleware function is almost identical to the code you are using right now, only with two notable differences:
The function ensures that the req.method is either POST or PUT.
The next() function is called when validation passes. This will trigger the next middleware function in the chain, or the route handler.
app.use('/docs', function(req, res, next) {
if (req.method == 'POST' || req.method == 'PUT') {
req.checkBody('title', 'Title is required').notEmpty();
var errors = req.validationErrors();
if (errors) {
res.json(400, { errors: errors });
return;
}
}
next();
});
app.post('/docs', auth, function (req, res) {
// go ahead and save the document
});
Background
Yes, there are a lot of different Node.js logging library winston, bunyan and console.log. It's easy to log down the information of the specific request when it has called and when and what information would be in response.
The problem
The problem begins with the sub function calls. When under one request your calling multiple functions which also uses the same logging, how would you pass the request meta - data to these log calls (function parameters seems to be one possible way but these are really messy) ?
Example
Small visual for coders:
// Middleware to set some request based information
app.use(function (req, res, next) {
req.rid = 'Random generated request id for tracking sub queries';
});
app.get('/', function (req, rest) {
async.series({
'users': async.apply(db.users.find),
'posts': async.apply(db.posts.find),
}, function (err, dbRes) {
console.log('API call made ', req.rid)
res.end(dbRes);
});
});
// Now the database functions are in other file but we also need to track down the request id in there
(db.js)
module.exports = {
users: {
find: function () {
console.log('Calling users listing ', req.rid); // ERROR this is not possible to access, not in this scope
// Make query and return result
}
},
posts: {
find: function () {
console.log('Calling post listing ', req.rid); // ERROR this is not possible to access, not in this scope
// Make query and return result
}
}
};
You can log your requests with simple conf in your app.js with;
app.use(function(req, res, next){
console.log('%s %s', req.method, req.url);
next();
});
However, you need to provide logs for specific functions in your controller.