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.