Writing Your Own Middleware

A middleware function is a plain function of (req, res, next). It must do exactly one of three things: call next() to continue, call next(err) to divert to error handling, or end the response itself. Doing none hangs the request until the client times out; doing two throws ERR_HTTP_HEADERS_SENT on the second write.

The timer below measures how long a request took. It cannot do that by timing next(), which returns as soon as the next layer yields rather than when the response is finished. Instead it records a start time and listens for the response stream's finish event.

Timing every request with a finish listenerShell
export function requestTimer(req, res, next) {
  const start = process.hrtime.bigint();
  res.on('finish', () => {
    const ms = Number(process.hrtime.bigint() - start) / 1e6;
    console.log(`${req.method} ${req.originalUrl} ${res.statusCode} ${ms.toFixed(1)}ms`);
  });
  next();
}
Output
GET /health 200 4.6ms
GET /books 401 0.8ms
GET /books 200 0.4ms

Three habits separate middleware that survives production from middleware that does not. Use process.hrtime.bigint rather than Date.now for durations: the monotonic clock is immune to system clock changes. Name the function instead of passing an arrow expression, so it appears in stack traces and in the router's layer list. And attach data to a namespaced property or a symbol, so two packages cannot collide over req.user.