chopeds
chopeds

Reputation: 317

Async/await in Nodejs + Mongoose

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

Answers (2)

O'Dane Brissett
O'Dane Brissett

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

Matthew Weeks
Matthew Weeks

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

Related Questions