Error handling in Express
In Express, an error middleware is distinguished from the rest by having
four parameters: (err, req, res, next). Express inspects the
arity (number of arguments) of the function to recognize it, so
all four must be declared, even if you don't use next.
function handleErrors(err, req, res, next) {
const status = err.statusCode || 500;
res.status(status).json({ error: err.message });
}
Passing the error with next(err)
A handler does not catch or respond to the error itself: it delegates by calling
next(err). When an argument is passed to next, Express skips all
the normal middleware and handlers and goes straight to the error middleware.
function getUser(req, res, next) {
const user = find(req.params.id);
if (!user) {
return next(new Error("User not found")); // delegates
}
res.json(user);
}
Why centralize
Centralizing error handling in a single middleware (registered at the end,
with app.use(handleErrors)) avoids repeating try/catch and error responses
in every handler. Advantages:
- Uniform format for error responses across the whole API.
- A single place to log failures.
- Handlers stay clean: they only express the happy path and delegate the rest.
Examples
The error middleware responds with the appropriate status
function handleErrors(err, req, res, next) {
const status = err.statusCode || 500;
return { status, body: { error: err.message } };
}
console.log(handleErrors({ message: "Broken" }, {}, {}, () => {}));
console.log(handleErrors({ message: "Doesn't exist", statusCode: 404 }, {}, {}, () => {}));