Centralized Error Handling
Funnel all errors to one error-handling middleware for consistent responses.
Syntax
app.use((err, req, res, next) => { ... })Rather than writing try/catch responses in every route, funnel errors to a single error-handling middleware at the bottom of your app. Routes just call next(err), and the handler decides how to respond.
A common pattern is a custom error class that carries a status code, so the handler knows what to send.
Example
class AppError extends Error {
constructor(message, status) {
super(message);
this.status = status;
}
}
app.get('/posts/:id', (req, res, next) => {
const post = null; // pretend not found
if (!post) return next(new AppError('Post not found', 404));
res.json(post);
});
app.use((err, req, res, next) => {
res.status(err.status || 500).json({ error: err.message });
});When to use it
- A REST API uses a centralised error middleware to ensure every unhandled error returns a JSON body instead of an HTML Express error page.
- A team creates an AppError class with a status field so controllers can throw domain errors and the single error handler sets the HTTP status automatically.
- A developer wraps all async route handlers in a try/catch that calls next(err) to funnel every promise rejection to the global error middleware.
More examples
Centralised error middleware
Registers a 4-argument error middleware that catches any error passed via next(err) and returns a consistent JSON error body.
// Must be the LAST app.use() call
app.use((err, req, res, next) => {
const status = err.status || 500;
res.status(status).json({
error: {
message: err.message || 'Internal Server Error',
status,
},
});
});Custom AppError class
Defines AppError with a status property so route handlers can throw semantically typed errors that the handler serialises.
class AppError extends Error {
constructor(message, status = 500) {
super(message);
this.status = status;
this.name = 'AppError';
}
}
app.get('/items/:id', (req, res, next) => {
const item = items.find(i => i.id == req.params.id);
if (!item) return next(new AppError('Item not found', 404));
res.json(item);
});
app.use((err, req, res, next) => {
res.status(err.status || 500).json({ error: err.message });
});404 catch-all handler
A final middleware creates a 404 AppError for any unmatched route, which the error handler then serialises.
// Place after all routes, before the error handler
app.use((req, res, next) => {
next(new AppError(`Cannot ${req.method} ${req.path}`, 404));
});
// Error handler last
app.use((err, req, res, next) => {
res.status(err.status || 500).json({ error: err.message });
});
Discussion