Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62
Subscribe to my email list now at http://jauyeung.net/subscribe/
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.