error handling in expressjs

January 07, 2018

This post is mostly just a braindump for my own purposes, but perhaps it will come in handy for you too. This code should work great in Nodejs 6+, as it uses arrow functions and template strings.

ExpressJS is a great and flexible framework for writing REST APIs, but that also means that we need to implement basic things like error handling and reporting by ourselves. The basic problem we run into when naïvely writing APIs using express is that in order to return the correct error code, you need to manually set the status code in each response and make sure that your error messages propagate back to the client. You might end up with each route handler doing something like:

api.get("/albums", function(req, res) {
    getAlbums(req, function(err, result) {
        if (err) {
            res.status(500).send({ message: err.message });
        } else {
            res.send(result);
        }
    });
});

...and that's just clumsy! We can definitely do better.

how do we do async?

There are a lot of articles about doing async in node.js so I won't go over all the benefits and drawbacks of callbacks vs promises vs async functions here. In my opinion, callbacks require a lot of boilerplate compared to promises or async functions, and that tanks their rating. The less boilerplate there is in my code, the easier it is to read. I prefer to use promises, but async functions are pretty great too (and they boil down to the same code, anyway). So now we've got this:

api.get("/albums", function(req, res) {
    return getAlbums(req)
        .then(result => res.send(result))
        .catch(err => res.status(500).send({ message: err.message }));
});

Great! Fewer lines of code. I like to return Promises as a matter of habit. If the caller doesn't care about the return, fine; if they do, they have it.

how do we unify error handling?

In our code above, we're still catching every error individually. It would be easy to mess up one handler and hose the client if they rely on us to send back errors in a consistent format. Fortunately, there's a great little library called express-promise-router. It replaces express's Router and makes sure that if one of our route handlers throws an exception, we can catch it at the end of our middleware chain in one error handler function. So, assuming we've instantiated api with express-promise-router, we can do something like this:

api.get("/albums", function(req, res) {
    return getAlbums(req).then(result => res.send(result));
});

api.use(function errorHandler(err, req, res, next) {
    res.status(500).send({ message: err.message });
});

how do we return the right error code?

Returning the correct error code is crucial for clients. If input validation failed, the client needs to know what to change. If the API endpoint requires authentication, the client needs to know to direct the end user to a login page. If it was an internal server error, there may not be much the client can do beyond telling the end user that something went wrong.

Now that we have one error handling function to rule them all, we can write all of our status code setting in one place. However, what's our convention going to be for setting the status code? Well, there's another great library for that. It's called http-errors and it's authored by the same guy who maintains express. Using this library, we can throw errors of the appropriate type inside our route handlers, catch them in the error handler, and set the status code by checking what constructor was used to create the error. In fact, errors created from the library already have a statusCode property we can use!

const { BadRequest } = require("http-errors");

function getAlbums(req) {
    if (req.query.year && isNaN(req.query.year)) {
        throw new BadRequest(`"year" query parameter must be a number!`);
    }
    // dummy database code! the important thing is that it's returning a promise.
    return database.execute(`Get_Albums`, { year: req.query.year });
}

api.get("/albums", function(req, res) {
    return getAlbums(req).then(result => res.send(result));
});

api.use(function errorHandler(err, req, res, next) {
    res.status(err.statusCode || 500).send({ message: err.message });
});

Now if we send a bogus year query parameter, we'll get an error 400 back with a message that tells us what to fix.

Depending on what other libraries your route handlers use, you may have other type of errors to check for. For instance, the mssql library will throw its own kind of RequestError if the database throws an error upon execution of the stored procedure or query. You'll want to capture that in your logs, because it contains valuable information like the exact stored procudure and line number where the error was thrown:

function errorHandler(err, req, res, next) {
    if (err instanceof sql.RequestError && err.code === "EREQUEST") {
        console.error("Error:", err.message, {
            procName: err.procName,
            line: err.lineNumber
        });
    }
    res.status(err.statusCode || 500).send({ message: err.message });
}

You might also want to send a generic response if it's an InternalServerError for security purposes. And thanks to express-promise-router, your error handling code is always in one place. You could even publish it as an independent package to use in all of your projects.

There's a lot more to explore here. RFC 7807 defines a verbose standard for describing the exact nature of an error, and we could implement that standard for input validation using a validation library like joi. Hopefully this serves as a useful starting point!