Updates once a month. I hate spam too.
Subscribe now

Last weekend I had the pleasure to visit Tokyo and gave a talk at the amazing nodefest conference, titled Node.js Production Checklist. The talk was dedicated to topics like error handling, monitoring and logging best practices, as well as incident handling. If you interested in all the slides, you can find it on Speakerdeck.

In this blog post you will learn about how you can handle errors in Express applications.


Tokyo in pictures


You can find a lot of amazing gardens in Tokyo - they are the perfect locations to get away a bit and get some fresh air.


Tokyo garden

Tokyo is one of the world’s most exciting dining destinations.


Tokyo breakfast

Oh, and the city never sleeps!


Tokyo night

Error handling in Express

One of the most crucial things to get right when building web applications are error handling. As the system will depend on other services, databases, as well as consumers of those services, the unexpected becomes expected. Databases will fail, services become unavailable and your consumers won’t be calling your endpoints with the parameters you expect.

To prepare your application for such circumstances, you have to handle errors properly. Let’s take a look at the following endpoint:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
app.get('/users/:id', (req, res) => {
const userId = req.params.id
if (!userId) {
return res.sendStatus(400).json({
error: 'Missing id'
})
}
Users.get(userId, (err, user) => {
if (err) {
return res.sendStatus(500).json(err)
}
res.send(users)
})
})

The endpoint above has some serious flaws:

  • it handles errors in different parts of the codebase,
  • it does not use Express’ error handler to have a unified error handling logic.

Let’s see how you can fix these issues! 👌

Adding the Express error handler

All Express route handlers come with a third function parameter, next, which can be used to call the next middleware, or to signal Express that an error happened:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app.get('/users/:id', (req, res, next) => {
const userId = req.params.id
if (!userId) {
const error = new Error('missing id')
error.httpStatusCode = 400
return next(error)
}
Users.get(userId, (err, user) => {
if (err) {
err.httpStatusCode = 500
return next(err)
}
res.send(users)
})
})

This way you’ve already let Express know that an error arose, and the error handler will get called. To add an error handler to your application, you have to add a middleware with four parameters - that’s how Express will know it is an error handler.

1
2
3
4
app.use((err, req, res, next) => {
// log the error...
res.sendStatus(err.httpStatusCode).json(err)
})

Even if this solution works, it adds a lot of boilerplate code - let’s get rid of that!

Meet boom

boom is an HTTP-friendly error object, that provides you helpers for standard HTTP errors:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const boom = require('boom')
app.get('/users/:id', (req, res, next) => {
const userId = req.params.id
if (!userId) {
return next(boom.badRequest('missing id'))
}
Users.get(userId, (err, user) => {
if (err) {
return next(boom.badImplementation(err))
}
res.send(users)
})
})
Interested in working on Node.js with me at GoDaddy? Drop me a DM on Twitter! 🤗

Updating the error handler function

To make sure we return with the newly created status codes and errors, you have to update the error handler to use properties from boom errors:

1
2
3
4
5
6
7
8
app.use((err, req, res, next) => {
if (err.isServer) {
// log the error...
// probably you don't want to log unauthorized access
// or do you?
}
return res.status(err.output.statusCode).json(err.output.payload);
})

Error handling in Express with async-await

If you’d like to use async-await in your Express route handlers, you have to do the following:

  • create a wrapper for the async functions, so errors are propagated
  • instead of calling next in your route handlers, simply throw.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const boom = require('boom');
// wrapper for our async route handlers
// probably you want to move it to a new file
const asyncMiddleware = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch((err) => {
if (!err.isBoom) {
return next(boom.badImplementation(err));
}
next(err);
});
};
// the async route handler
app.get('/users/:id', asyncMiddleware(async (req, res) => {
const userId = req.params.id
if (!userId) {
throw boom.badRequest('missing id')
}
const user = await Users.get(userId)
res.json(user)
}))

Further improvements

  • For unified error messages, you can start using joi to validate request parameters and the request body,
  • log all server errors in the route handler, but filter client errors - check out these loggers:
boom, express, node.js
Visit GitHub to add a comment