Node.js Backend Fundamentals with Express · Build an API with Express

Middleware: Logging, Timing and Request IDs

Write middleware that runs before and after handlers, understand next() and ordering, and add request IDs and timing.

Create a free account to save this page, take notes and track your progress. Sign in

What you'll build

Three middleware functions: a request ID that every log line and response carries, a timer that records how long each request took, and an API key check on one router only.

The idea

  • Middleware is a function (req, res, next): do something, then call next() to continue, or respond to stop the chain.
  • Middleware runs in registration order. Global middleware goes first; route-specific middleware is passed to the router or route.
  • To act after the handler (for timing or logging), listen for the response's finish event.
  • Attach per-request data to res.locals (or req) for later middleware and handlers.

The code

Request IDs, timing and an API key check
import express from "express";
import { randomUUID } from "node:crypto";

const log = [];
const requestId = (req, res, next) => {
  res.locals.id = req.get("x-request-id") ?? randomUUID().slice(0, 8);
  res.set("x-request-id", res.locals.id);
  next();
};
const timing = (req, res, next) => {
  const start = performance.now();
  res.on("finish", () => log.push(`${res.locals.id} ${req.method} ${req.originalUrl} ${res.statusCode} ${performance.now() - start < 50 ? "<50ms" : "slow"}`));
  next();
};
const requireApiKey = (req, res, next) =>
  req.get("x-api-key") === "secret-key" ? next() : res.status(401).json({ error: "missing or invalid API key" });

const admin = express.Router();
admin.use(requireApiKey);
admin.get("/stats", (req, res) => res.json({ orders: 42 }));

const app = express();
app.use(requestId, timing);
app.get("/health", (req, res) => res.json({ ok: true }));
app.use("/admin", admin);

const server = app.listen(0);
const base = `http://127.0.0.1:${server.address().port}`;
await fetch(`${base}/health`, { headers: { "x-request-id": "req-0001" } });
await fetch(`${base}/admin/stats`, { headers: { "x-request-id": "req-0002" } });
const ok = await fetch(`${base}/admin/stats`, { headers: { "x-request-id": "req-0003", "x-api-key": "secret-key" } });
console.log("response header x-request-id:", ok.headers.get("x-request-id"), JSON.stringify(await ok.json()));
server.close();
console.log(log.join("\n"));

Output

response header x-request-id: req-0003 {"orders":42}
req-0001 GET /health 200 <50ms
req-0002 GET /admin/stats 401 <50ms
req-0003 GET /admin/stats 200 <50ms

Run this on your computer — see “Get Started” for setup.

Common mistakes

  • Forgetting to call next() (the request hangs) or calling it after sending a response.
  • Registering logging middleware after the routes, so it never runs.
  • Doing slow work (database calls) in global middleware for every request, including health checks.

Key points

  • Middleware runs in order and either calls next() or ends the request.
  • Use the finish event to act after the handler.
  • Scope middleware to routers when only some routes need it.

Further reading: Writing middleware · Using middleware

Practice questions

What happens if middleware neither responds nor calls next()?

Answer

The request hangs until the client times out, because nothing else in the chain runs.

How can middleware measure how long the handler took?

Answer

Record the start time, then listen for res.on('finish') and compute the duration when the response has been sent.

How do you apply middleware to only some routes?

Answer

Pass it to a Router (router.use) or directly to a route (app.get(path, mw, handler)) instead of app.use for everything.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?