← Node & Express

Middleware

Why does the order in which you register middleware and routes in Express matter?

Show answer — try answering out loud first

It matters because Express runs middleware and routes from top to bottom, in the same order you registered them. Every request travels through that chain in sequence. If you place a middleware after the route that responds, that middleware won't run for that route, because the response already finished before reaching it. On top of that, if a middleware forgets to call next(), it breaks the chain and hangs the request.

Express isn't "magic": it doesn't look for the most appropriate middleware or run things in some smart order. It simply builds a list with everything you registered (app.use, app.get, app.post, etc.), in the exact order you wrote the code, and for each request it walks that list from top to bottom.

Two practical rules come out of this:

  • Cross-cutting concerns go first. Things like body parsing (express.json()), logging, or authentication must be registered before the routes that depend on them. If you put express.json() after your routes, req.body won't exist yet when the route runs.
  • The chain runs until someone responds. As soon as a middleware or route finishes the response (res.send, res.json...) and doesn't call next(), the request stops right there. Anything registered further down won't run for that request.

A very common source of confusion: you register a route that responds, and below it you add a middleware thinking it will "run for everything". It won't run for that route, because the route already closed the response before Express reached your middleware.

Another source of bugs: a middleware that doesn't call next() and doesn't respond either. The chain gets stuck on it, and the request never advances or replies. The client is left waiting.

A simple mental rule: top to bottom, until someone responds. Think of the order in your file as the real execution order.

const express = require("express");
const app = express();

// 1) Global middleware: runs FIRST on every request.
//    It goes up top on purpose, so it applies to all routes.
app.use((req, res, next) => {
  console.log(`[log] ${req.method} ${req.url}`);
  next(); // without this next(), the request would hang here
});

// 2) The route responds and does NOT call next().
//    As soon as it replies, the chain stops for this request.
app.get("/usuarios", (req, res) => {
  res.json({ usuarios: ["Ana", "Luis"] });
});

// 3) This middleware is AFTER the /usuarios route.
//    It does NOT run when you request GET /usuarios, because the route
//    above already finished the response before reaching here.
app.use((req, res, next) => {
  console.log("This message NEVER appears for GET /usuarios");
  next();
});

// 4) "catch-all" middleware at the end: only requests that were NOT
//    answered by any earlier route reach it (e.g. nonexistent routes).
app.use((req, res) => {
  res.status(404).json({ error: "Ruta no encontrada" });
});

app.listen(3000, () => {
  console.log("Servidor en http://localhost:3000");
});

Placing express.json() (or any middleware that prepares data) after the routes that need it, and then being surprised that req.body comes through as undefined. Since Express runs top to bottom, by the time the route runs the parsing middleware hasn't executed yet. The fix is to always register preparation middleware before the routes. The other classic mistake is putting a "catch-all" middleware at the end of the file and expecting it to apply to routes that already responded: it never runs for them.

"Order matters because Express runs middleware and routes from top to bottom, in the order I registered them, and it walks that chain until someone finishes the response. That's why cross-cutting middleware, like express.json(), logging, or authentication, goes above the routes that depend on it: if I put it after, that data isn't ready yet when the route runs. I'm also careful not to forget next(), because if a middleware neither responds nor calls next(), the request hangs. And I put a 404 handler at the end, so only requests that no earlier route answered reach it."

Quick challenge

Given this code, what does GET /perfil respond and why?

app.get("/perfil", (req, res) => {
  res.json({ nombre: "Mike" });
});

app.use((req, res, next) => {
  req.autenticado = true; // supposedly authenticates
  next();
});
See answer

It responds { "nombre": "Mike" }, without authenticating. The authentication middleware is registered after the /perfil route, so it never runs for that route: the route already responded and cut the chain before Express reached the middleware. For authentication to apply, you have to move it above the route. This is exactly the kind of ordering bug that can leave routes unprotected.