Introduction
Robust APIs must handle errors gracefully instead of crashing or leaking stack traces to clients. Express provides a built-in mechanism for centralized error handling through special middleware functions that accept four arguments. Synchronous errors are caught automatically, but asynchronous errors — such as rejected promises inside async route handlers — need to be explicitly forwarded to Express.
Cricket analogy: A well-run team doesn't collapse into chaos after a bad umpiring decision — there's a designated process (centralized error middleware) for lodging a formal complaint; a clear no-ball is caught by the on-field umpire automatically, but a disputed boundary needs someone to actively call for a review (forwarding async errors) or the match just continues on a wrong call.
Syntax
// Error-handling middleware has exactly 4 parameters
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({ error: err.message || 'Server error' });
});Explanation
Express identifies error-handling middleware by its signature: it must accept exactly four arguments (err, req, res, next), and must be registered after all other app.use() and route calls. For synchronous code, throwing an error inside a route handler is automatically caught by Express and routed to this middleware. For asynchronous code (promises, async/await), errors are NOT caught automatically — you must call next(err) inside a .catch() block, or wrap async handlers in a helper that catches rejected promises and forwards them via next().
Cricket analogy: The complaints committee is identifiable by its exact structure — it only convenes after all match officials have filed their reports, never before; a blatant no-ball is flagged automatically by the on-field umpire, but a slow-motion review of a close catch needs someone to explicitly submit it to the committee (.catch(next)) or it never gets reviewed.
Example
// Helper to catch async errors automatically
function asyncHandler(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await db.findUserById(req.params.id);
if (!user) {
const err = new Error('User not found');
err.status = 404;
throw err;
}
res.json(user);
}));
// Centralized error-handling middleware, registered last
app.use((err, req, res, next) => {
res.status(err.status || 500).json({ error: err.message });
});Output
If db.findUserById rejects or the user is not found, asyncHandler catches the thrown error and passes it to next(), which invokes the error-handling middleware, producing a JSON response like { "error": "User not found" } with status 404 instead of an unhandled promise rejection or a hung request.
Cricket analogy: If a player's registration lookup fails or the player simply isn't registered, the system catches the rejected lookup and forwards it to the complaints desk, which returns a clear 'Player not found' ruling instead of leaving the match official waiting indefinitely for an answer that never comes.
Key Takeaways
Errors thrown inside async functions are NOT automatically caught by Express (in Express 4). You must call next(err) explicitly or use a wrapper like asyncHandler; otherwise the request will hang or crash the process with an unhandled rejection.
- Error-handling middleware is identified by having exactly 4 parameters: (err, req, res, next).
- Error-handling middleware must be registered after all routes and other middleware.
- Synchronous throws inside route handlers are caught automatically by Express 4.
- Async errors must be forwarded manually via next(err) or a wrapper function like asyncHandler.
- Centralizing error handling keeps response formatting and logging consistent across the app.
Practice what you learned
1. How does Express identify a function as error-handling middleware?
2. In Express 4, are errors thrown inside an async route handler automatically caught?
3. Where should error-handling middleware be placed in an Express app?
4. What does the asyncHandler wrapper pattern accomplish?
Was this page helpful?
You May Also Like
Middleware in Express
Master the Express middleware chain, the next() function, and the special error-handling middleware signature.
RESTful API Design
Learn the core principles of REST: resource-based URLs, proper HTTP methods, and meaningful status codes.
Async/Await in Node.js
Writing asynchronous code that reads like synchronous code using the async/await syntax built on Promises.
Data Validation in Express
Validate incoming request data in Express using schema-based validation before it reaches your database layer.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics