Skip to content
ExplainerBackend

Express middleware explained, step by step

Middleware is a line of functions every request walks before your handler. Watch three stations stamp one request in order, then a missing token stop it early.

3 min read
Express middleware explained, step by step, the opening step of the interactive scene

Every request walks the same line

Before your route handler runs, the request passes through a line of small functions, in the exact order you registered them.

Step 1 of 6

If you have written app.use(express.json()) without being sure what it does, or watched req.body come back undefined for no reason you could see, this is the idea you are missing. Middleware is not a plugin system. It is a line, and every request walks it. Step through the scene above first.

The line, and the three powers of a station

Before your route handler runs, the request passes through every function you registered with app.use, in the order you registered them. Each of those functions is middleware, and each has the same shape: (req, res, next). Inside it, a station can do exactly one of three things.

  • Inspect and forward. Read the request, maybe attach something to it, then call next() so the next station gets a turn. Parsers and loggers do this.
  • Answer. Send a response and stop. The request never reaches anything behind this station. An auth check that returns 401 does this.
  • Neither. Return without answering and without next(). The request hangs forever. This is the bug, and it is silent.
app.use(express.json());              // 1: parses the body, sets req.body
app.use((req, res, next) => {          // 2: logs, then forwards
  console.log(req.method, req.url);
  next();
});
app.use((req, res, next) => {          // 3: verifies the token
  const user = verify(req.headers.authorization);
  if (!user) return res.sendStatus(401);   // answers, line ends here
  req.user = user;
  next();
});
app.post("/books", (req, res) => {     // the handler uses what the line left
  const book = save({ ...req.body, ownerId: req.user.id });
  res.status(201).json(book);
});

Order is behaviour, not style

Each station leaves something on the request for the stations behind it. The parser leaves req.body. Auth leaves req.user. The handler at the end reads both and does nothing but the work that makes that route unique. That is the whole point: write the shared chore once, put it on the line, and every request passes through it.

It also means reordering the line changes what your server does. Move express.json below the route and req.body is undefined when the handler runs. Move the logger below auth and a rejected request never gets logged, because it left the line before reaching the logger. And a station that answers unconditionally near the top silences every route beneath it. The Express guide to writing middleware has the reference; the lesson lets you reorder the line, predict the result, and break it both famous ways.

Common questions

What happens if middleware never calls next()?
The request hangs. It has entered a function that neither answered nor passed it on, so the client waits until it times out. Every middleware must either send a response or call next().
Does the order I call app.use() in matter?
Yes, completely. Registration order is execution order. Put express.json after a route and that route sees req.body as undefined, because it ran before the body was parsed.
What is the difference between middleware and a route handler?
Shape wise, nothing. Both are functions of (req, res, next). The difference is intent. Middleware does shared work and usually calls next(); a route handler does the unique work and sends the response.
Can middleware run only for some routes?
Yes. Pass it to app.use with a path prefix, or drop it directly into one route's argument list before the handler. It then sits on the line only for the requests that match.