Error-handling middleware is a special kind of middleware that Express recognizes by its four-argument signature. This lesson shows how to centralize error handling instead of repeating try/catch blocks everywhere.
What Makes Middleware an Error Handler?
Express identifies error-handling middleware purely by its arity, a function with exactly four parameters, (err, req, res, next), is treated as an error handler. Everything else is treated as regular middleware, even if you intended it to catch errors.
Error handlers are skipped during normal request handling. They only run when a previous middleware or route handler calls next(err), or when a synchronous error is thrown inside a route handler.
Calling next(err) skips every remaining regular middleware/route and jumps directly to the four-argument error handler.
Error Middleware Syntax
app.use((err, req, res, next) => {
// must have exactly 4 parameters to be recognized as an error handler
res.status(err.status || 500).json({ error: err.message });
});
Error-handling middleware must be registered after all other app.use()/routes.
You can define multiple error handlers; Express calls the next one if you call next(err) again.
Synchronous throws inside a normal route handler are caught automatically by Express.
Errors inside async functions are NOT caught automatically in Express 4, you must catch and forward them yourself.
Error Handling Cheatsheet
How different kinds of errors reach your centralized handler.
Situation
How the Error Reaches the Handler
Synchronous throw
Caught automatically by Express, forwarded to error handler
Manual forward
next(err) inside a route or middleware
Async/await error
Must catch and call next(err) yourself (Express 4)
Validation failure
next(new ValidationError(...)) from your validation layer
Unmatched route
Custom 404 middleware, not an error handler, registered before error handlers
Handling Errors in Async Route Handlers
In Express 4, a rejected promise inside an async route handler does NOT automatically reach your error middleware, you must wrap it in a try/catch and call next(err) explicitly, or use a small wrapper helper.
Express 5 automatically forwards rejected promises from async handlers to next(err), but explicit handling is still clear and works on both versions.
Building a Centralized Error Handler
A single, well-designed error handler at the bottom of the middleware stack keeps error formatting consistent across your entire API, instead of scattering res.status(500).json(...) calls throughout your codebase.
app.use((err, req, res, next) => {
const status = err.status || 500;
const message = status === 500 ? 'Internal server error' : err.message;
if (status === 500) {
console.error(err.stack);
}
res.status(status).json({ success: false, error: message });
});
Common Mistakes
Writing an error handler with three arguments instead of four, Express treats it as regular middleware and skips it for errors.
Registering the error handler before your routes, it will never be reached.
Forgetting to catch rejected promises inside async route handlers in Express 4.
Leaking internal error details (stack traces, SQL errors) directly to API clients in production.
Key Takeaways
Express recognizes error-handling middleware purely by its four-parameter signature.
next(err) skips remaining regular middleware and jumps to the nearest error handler.
Async handlers need explicit try/catch (or a wrapper) to forward rejected promises in Express 4.
A single centralized error handler keeps error responses consistent across the whole API.
Pro Tip
Write one small asyncHandler wrapper once and reuse it on every async route, it is far less error-prone than remembering a manual try/catch in every single handler.
You now know how centralized error handling works. Next, learn how to build your own middleware from scratch in the Custom Middleware lesson.