Mastering Express.js Error‑Handling Middleware: A Practical Guide
Learn how to implement, order, and test Express.js error‑handling middleware for reliable, production‑safe error reporting.
03 Jul 2026, 04:36 UTC

Desired Outcome
By the end of this article you will know how to set up, order, and test Express error‑handling middleware so that all errors—synchronous or asynchronous—are caught, logged, and returned to the client in a controlled, production‑safe manner.
Prerequisites
- Node.js 18+ and npm installed.
- Basic Express application skeleton (app.js or index.js).
- Familiarity with JavaScript async/await and Promises.
- Understanding of HTTP status codes.
Procedure
1. Define the Error‑Handling Function
An Express error handler must have four parameters: (err, req, res, next). The presence of the first err argument signals to Express that this middleware is for errors.
function errorHandler(err, req, res, next) {
// Avoid leaking stack traces in production
const response = {
message: err.message || 'Internal Server Error',
};
if (process.env.NODE_ENV !== 'production') {
response.stack = err.stack;
}
res.status(err.statusCode || 500).json(response);
}
2. Register After All Other Middleware and Routes
Order matters. Place app.use(errorHandler) after every app.use() and app.get()/post() declaration. If it appears earlier, normal requests will be intercepted.
const express = require('express');
const app = express();
// Normal middleware and route registrations
app.use(express.json());
app.get('/api/data', (req, res) => {
res.json({ hello: 'world' });
});
// Error‑handling middleware comes last
app.use(errorHandler);
3. Propagate Errors from Synchronous Handlers
Throwing an exception inside a route or middleware automatically forwards it to the error handler.
app.get('/bad', (req, res) => {
throw new Error('Something went wrong');
});
4. Propagate Errors from Asynchronous Code
Express does not automatically catch rejected Promises. Use .catch(next) or try/catch with next(err).
// Using .catch
app.get('/async', (req, res, next) => {
someAsyncOperation()
.then(data => res.json(data))
.catch(next);
});
// Using async/await
app.get('/async-await', async (req, res, next) => {
try {
const data = await someAsyncOperation();
res.json(data);
} catch (err) {
next(err);
}
});
5. Add Multiple Handlers for Specific vs Generic Errors
Place more specific handlers before generic ones. The first handler that sends a response ends the chain.
// Specific 404 handler
app.use((req, res, next) => {
const err = new Error('Not Found');
err.statusCode = 404;
next(err);
});
// Generic error handler
app.use(errorHandler);
Verifying the Handler
- Sync Test: Create a route that throws. Hit the endpoint; you should receive a 500 JSON response with a message.
- Async Test: Create a route that rejects a Promise; confirm the same 500 response.
- Order Test: Add two error handlers—one that logs and one that sends a response. Only the responder should send a reply.
- Check stack trace presence only when
NODE_ENV !== 'production'.
Common Pitfalls & Recovery
- Missing the First Argument: If the function signature is
(req, res, next), Express treats it as normal middleware and will never catch errors. - Infinite Loop: Calling
next()without an error inside an error handler re‑enters the same handler. Always either send a response or callnext(err). - Leaking Stack Traces: In production, omit
err.stackfrom the JSON payload. - Unhandled Promise Rejections: Forgetting
.catch(next)will cause unhandled rejection warnings. Wrap async logic in try/catch or use a helper likeexpress-async-errors.
Recovery Options
If an error handler inadvertently crashes the app, restart the process via a process manager (PM2, systemd). Ensure your error handler never throws itself—wrap its logic in a try/catch if it performs I/O.
Conclusion
Express error‑handling middleware is a simple yet powerful tool. By registering it after all routes, propagating async errors correctly, and guarding against stack trace leaks, you create a robust error surface that aids debugging during development and protects users in production.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.