Categories
Express

Error Handling with Express

Spread the love

Like with any other apps, we have to make Express apps ready to handle errors like unexpected inputs or file errors.

In this article, we’ll look at how to handle errors with Express.

Catching Errors

Error handling is the process of processing any errors that comes up both synchronously and asynchronously. Express comes with a default error handler so that we don’t have to write our own.

For example, if we throw errors in our route handlers as follows:

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

Express will catch it and proceed. We should see error instead of the app crashing.

For asynchronous errors, we have to call next to pass the error to Express as follows:

app.get('/', (req, res, next) => {
  setTimeout(() => {
    try {
      throw new Error('error');
    }
    catch (ex) {
      next(ex);
    }
  })
});

The code above will throw an error in the setTimeout callback and the catch block has the next call with the error passed in to call the built-in error handler.

We should see error instead of the app crashing.

Likewise, we have to catch rejected promises. We can do it as follows:

app.get('/', (req, res, next) => {
  Promise
    .reject('error')
    .catch(next)
});

Or with the async and await syntax, we can write the following:

app.get('/', async (req, res, next) => {
  try {
    await Promise.reject('error');
  }
  catch (ex) {
    next(ex);
  }
});

We should see error displayed instead of the app crashing with the stack trace.

The same logic also applies to routes with a chain of event handlers. We can call next as follows:

app.get('/', [
  (req, res, next) => {
    setTimeout(() => {
      try {
        throw new Error('error');
      }
      catch (ex) {
        next(ex);
      }
    })
  },
  (req, res) => {
    res.send('foo');
  }
]);

We should see the error displayed with the stack trace.

Default Error Handler

The default error handler catches the error when we call next and don’t handle it with a custom error handler.

The stack trace isn’t displayed in production environments.

If we want to send a different response than the default, we have to write our own error handler.

The only difference between route handlers, middleware and error handlers is that error handler has the err parameter before the request parameter that contains error data.

We can write a simple route with a custom event handler as follows:

app.get('/', (req, res, next) => {
  setTimeout(() => {
    try {
      throw new Error('error');
    }
    catch (ex) {
      next(ex);
    }
  })
});

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

Note that we have the error handler below the route. The order is important. It has to below all the routes that we want to handle with it so that the error handler will get called.

We can write more than one custom error handler as follows:

app.get('/', (req, res, next) => {
  setTimeout(() => {
    try {
      throw new Error('error');
    }
    catch (ex) {
      next(ex);
    }
  })
});

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

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

What we have above is that if req.xhr is truthy in the first error handler, then it’ll send the Fail! response and not proceed to the second one. Otherwise, the second one will be called by calling next .

So if we add req.foo = true before the setTimeout in our route handler to have:

app.get('/', (req, res, next) => {
  req.foo = true;
  setTimeout(() => {
    try {
      throw new Error('error');
    }
    catch (ex) {
      next(ex);
    }
  })
});

Then we get Fail! . Otherwise, we get Error! .

Calling next will skip to the error handler even if there’re other route handlers in the chain.

Conclusion

To handle errors, we should call next to delegate the error handling to the default event handler if no custom event handler is defined.

We can also define our own error handler function by creating a function that has the err parameter before, req , res , and next . The err parameter has the error object passed from next .

Error handlers have to be placed after all the regular route handling code so that they’ll get run.

Also, we can have multiple error handlers. If we call next on it, then it’ll proceed to the next error handler.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *