WittCode💻

Express Error Handling

By

Learn how to handle errors with Express including how to write custom error handling middleware and the difference between synchronous and asynchronous errors.

Table of Contents 📖

Express Default Error Handling

Express error handling refers to how Express catches errors in route handlers and middleware. Express has built in error handling for both synchronous and asynchronous errors. For example, if we throw an error synchronously inside a middleware function it won't crash the program.

app.get('/', (req, res) => {
  throw new Error('woops!');
});

Here, Express will use its default error handling middleware to send the error back to the client and also log it to the server console. However, asynchronous errors are handled slightly differently. For example, if we throw an error inside an asynchronous function it will crash the program.

app.get('/async', async (req, res) => {
  throw new Error('woops!');
});

To have Express handle asynchronous errors we need to use the Express next function. It should be noted that Express version 5 will handle asynchronous errors just like synchronous errors.

Express next Function and Errors

The Express next function is a function that passes control to the succeeding, or next, middleware. If we pass an argument to the next function, then it will pass control to the next error handling middleware.

app.get('/async', async (req, res, next) => {
  try {
    throw new Error('woops!');
  } catch (err) {
    next(err);
  }
});

If we haven't made any custom error middleware then the default error middleware will be hit. Specifically, if anything is passed to the next function Express will treat it as an error, calling any error handling middleware and skipping any non-error handling middleware.

app.get('/async', async (req, res, next) => {
  next('yes!');
});

app.use((req, res, next) => {
  console.log('hello!');
  next();
});

app.use((req, res, next) => {
  console.log('hello 2!');
  next();
});

This code above will skip the two middlewares in front of it as something is passed to the next function. However, if we don't pass anything to the next function then the following middleware will be called.

app.get('/async', async (req, res, next) => {
  next();
});

app.use((req, res, next) => {
  console.log('hello!');
  next();
});

app.use((req, res, next) => {
  console.log('hello 2!');
  next();
});

Here, nothing is passed to the next function so the error handling middleware is not invoked. There is a catch to this rule though as if the string route is passed to the next function then it will treat it as though nothing has been passed and consecutive middleware functions will be called.

Custom Express Error Handling Middleware

So the built in Express error middleware runs at the end of the middleware function stack, sends the error to the client, and writes the stack trace to the console. If we want to do something else, we can create our own custom error handler. To make custom error handler middleware, we need to provide the middleware function with 4 arguments: the error, then request, then response, and then next function.

app.use((err, req, res, next) => {
});

From this custom error handler, we can still trigger the default error handler if we call next and pass an error.

app.use((err, req, res, next) => {
  next(err);
});

However, if we don't call next in the error handling function then we need to send a response back to the client.

app.use((err, req, res, next) => {
  res.status(500).send('Woops!');
});

If we don't respond to the client, then the request will hang indefinitely. Finally, like the Express default error handling middleware, this middleware should be placed last in the middleware chain.

Sending Two Responses

Something useful that the default error handling middleware does is close the connection and fail the request if a response has already been sent back to the client. This is because it is against the HTTP protocol to send back two responses to a request. We can check if a response has already been sent back to a client by using the headersSent property.

app.use((err, req, res, next) => {
  if (res.headersSent) {
    return next(err);
  }
  res.status(500).send('Woops!');
});

Here, if a response has already been sent we forward the error to the default Express error handler. If not, we send back our own 500 status code response.

Express Error Handling