intermediate
Express error middleware
Map thrown, rejected, validation, domain, and unknown errors to stable HTTP responses without losing diagnostic context.
Error-handling middleware has four parameters `(err, req, res, next)` and must be registered after all routes and other middleware. Express routes thrown errors and `next(err)` calls to these handlers.
app.use((err, req, res, next) => {
const status = err.statusCode ?? 500;
const body = err.isOperational
? { error: err.message, code: err.code }
: { error: 'Internal server error' };
logger.error({ err, requestId: req.id });
res.status(status).json(body);
});
Classify errors: validation (4xx), auth (401/403), domain conflicts (409), and unknown (500). Operational errors carry safe client messages; programmer errors should log stack traces but not leak internals.
On interviews: explain why async route errors need wrappers, how to preserve `requestId` in logs, and stable error shapes for API clients.
Common pitfalls: only one generic handler with no classification, returning stack traces in production, and missing error middleware entirely so async failures hang or crash the process.
The trade-off is balancing simplicity, performance, safety, and operability — name which axis you optimized and what cost you accepted.
Checklist:
- Register error middleware last.
- Distinguish operational from programmer errors.
- Map domain errors to HTTP status consistently.
- Log context; sanitize client responses.