Using Async/Await To Write Cleaner Route Handlers

Using async/await has given me the ability to cleanup my code a lot, by saving indentation levels (the infamous JavaScript callback hell), but also giving me the ability to control the flow of my application without having to resort to yet another indentation level.

During my work as an independent web developer, I find myself writing RESTful APIs most of the time. It is not uncommon to do a lot of asynchronous actions to handle a single API call, so the use of Promises is definitely a big part of getting things to work.

Here’s an example:

// routes/videos.js
router.get("/", (req, res, next) => {
    media.getAll({
        lang: req.getLocale(),
        type: 1
    })
    .then((videos) => {
        res.render("videos", {
        videos
    });
})
.catch(next);

It is a very simple route handler that first fetches media objects from the database, then renders them using a templating engine.

One of the most recurring pieces of code in my project is the .catch() part of my handlers. Because all my errors are handled by one (sometimes more) error handlers further down in the routing chain, I need to be able to pass an error to the next() method, otherwise they wouldn’t be handled properly.

So I have two issues I want to solve:

  1. I want to get rid of the callback hell (although, in this example, is not exactly an issue)
  2. I want to stop repeating myself and write the same code over and over again

The first issue can be resolved quite easily by using async/await, but I still end up with the following code:

// routes/video.js
router.get("/", async (req, res, next) => {
    try {
        let videos = await media.getAll({
            lang: req.getLocale(),
            type: 1
        });

        res.render("videos", {
            videos
        });
    } catch (err) {
        next(err);
    }
});

As you can see, the important code is no longer split up by a callback (requesting the data in the request handler scope, and rendering the data in the .then() resolver callback), but I still have the issue of having to repeat code, over and over again.

If your route handler is more elaborate, multiple try/catch blocks might occur in your code, again causing multiple indentation levels and resulting in generally less clean code.

Then I came up with a very simple solution to my problem: a middleware function, that allows me to wrap my actual route handler with just 6 extra characters, and never deal with the issue again!

// middleware/wrap.js
module.exports = (fn) => {
    return (req, res, next) => {
        Promise.resolve(fn(req, res, next)).catch(next);
    };
};

Now, all I have to do is include the middleware in my routes/videos.js file, and use it:

// routes/video.js
const wrap = require("../middleware/wrap");

router.get("/", wrap(async (req, res, next) => {
    let videos = await media.getAll({
        lang: req.getLocale(),
        type: 1
    });

    res.render("videos", {
        videos
    });
}));

Now, all the errors that my code might throw (syntax errors, wrong variable names, exceptions from external libraries) will be handled by my error handler, which, depending on my environment settings, might show an InternalServerError in production, but a detailed error in development.

Hopefully this small bit of code can help you to maintain a clean repository.

tl;dr abstractions, no matter how small, can improve your code