Reputation: 317
I'm new to Promises and async/await programming and I am not sure I am getting it straight. I am creating an API in Nodejs, with Express, Mongoose and MongoDB. I have seen a lot of tutorials on how to deal with asynchronicity but all of them are about NodeJs projects where the routing and the DB query are in the same file. example:
const asyncMiddleware = fn =>
(req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next);
};
router.get('/users/:id', asyncMiddleware(async (req, res, next) => {
const something = await getSomethingFromDb({ id: req.params.id })
res.json(something);
}));
However, for clarity purposes, I have separated the routing from the controller but I have serious doubts I have done it correctly. Here is my code:
router.js
const asyncMiddleware = fn =>
(req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next);
};
router.get('/something/:id', asyncMiddleware(async (req, res, next) => {
const answer = await somethingController.findById(req, res, next)
}));
controller.js
exports.findById = async (req, res, next) => {
const something = await Something.findById(req.params.id).exec();
res.send(something);
};
I have tried to console.log() stuff to check what gets printed what, but I have realized, due to the awaiting part, this whole piece of code will wait for the query to finish. Is this well implemented? How can I test it?
Versions: NodeJs v10.16.3 Mongoose v5.7.1
Upvotes: 3
Views: 5992
Reputation: 1283
Firstly you don't need an "asyncMiddleware". Let me give a full example of how you can separate routes and controllers, while keeping the controller async:
Controller
exports.findById = async (req, res, next) => {
try{
const something = await Something.findById(req.params.id).exec();
return res.send(something);
}catch(err){
return res.status(500).send({
message: err.message
})
}
};
You should wrap you async
calls in a try/catch block.
Route
You would then simply call your controller in your route like so:
router.get('/:id', Controller.findByID)
and that's it. You don't need any additional async
call on your route.
If you have middlewares your want to add you route you can do it like this:
//for single middleware
router.get('/:id',somethingMiddle,Controller.findByID)
//for multiple middleware
router.get('/:id',[somethingMiddle, anotherMiddle],Controller.findByID)
Let me know if this helps
Upvotes: 14
Reputation: 1028
Personally, I would stick to using try-catch for my async functions, rather than using a middleware. This could look something like the following:
module.exports.view = async function(req, res, next) {
try {
var something = await Something.findById(req.params.id).orFail();
res.send(something);
} catch (err) {
next(err);
}
};
Then in your router it would simply be:
router.get('/something/:id', somethingController.view);
Doing this way will allow you to manipulate and handle special error cases for that controller action, before next(err)
sends it to the global error handler.
Now I'm not sure if this is something you're expecting or not, but you may want to call .orFail()
on your query. This way an error is thrown if the "something" with the specified ID is not found. Otherwise the something
variable will be null. But that totally depends on how you want your API to function.
Additionally, when you define an async
function, it is basically saying that the function returns a Promise
. An await
statement basically tells the program to pause the function until the promise is resolved. In this example, await
will wait for the database to return the result before continuing the controller action. Express will magically handle awaiting your async
route methods and middleware.
Upvotes: 2