How does error-handling middleware work in Express?
3 minintermediatenodejsexpresserror-middlewarenextasync
Quick Answer
Error-handling middleware is defined with four parameters (err, req, res, next) and registered last. Express routes to it when you call next(err) or (in Express 5) when an async handler rejects. It centralizes logging and mapping errors to HTTP status codes and response bodies.
Detailed Answer
Answer: Express distinguishes error middleware from normal middleware by its arity: it takes four arguments.
// Registered AFTER all routes
app.use((err, req, res, next) => {
console.error(err.stack);
const status = err.statusCode || 500;
res.status(status).json({ error: err.message || 'Internal Server Error' });
});
How Express reaches it:
- You call
next(err)from any middleware/route. - A synchronous throw in a handler is caught by Express and routed here automatically.
- Async rejections: in Express 5, a rejected promise from an async handler is forwarded automatically. In Express 4, it is not — you must catch and
next(err)yourself, often via a wrapper:
const asyncHandler = fn => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await db.getUser(req.params.id); // rejection → next(err)
res.json(user);
}));
Best practices:
- Keep a single central error handler; don't scatter response formatting.
- Log full details server-side, but return a safe, generic message to clients (don't leak stack traces or internals in production).
- Combine with custom error classes carrying
statusCodeso the handler maps errors to the right HTTP code. - Add a catch-all 404 handler before the error handler for unmatched routes.