Card 05/ 08

ExampleDifficulty: Intermediate3 min

Chain of Responsibility in the Wild: Express Middleware

Each middleware either answers the request or calls next() to pass it on.

Express, a widely used web framework for Node.js, processes every incoming request through a chain of functions called middleware. Each one is registered with app.use, receives the request and a function named next, and either sends a response or calls next() to pass the request to the following middleware. This card shows the chain with a small imitation of that interface, and where middleware differs from the Gang of Four's version.

The listing below is not Express. It is a twenty-line MiniApp that imitates Express's use and next, so that it runs without installing anything. Real Express middleware also receives a response object and writes to it, rather than returning a value.

ts
type Request = { path: string; token?: string };
type Response = { status: number; body: string };
type Next = () => Response;
type Middleware = (req: Request, next: Next) => Response;

class MiniApp {
  private readonly stack: Middleware[] = [];

  use(middleware: Middleware): void {
    this.stack.push(middleware);
  }

  handle(req: Request): Response {
    const run = (index: number): Response => {
      const middleware = this.stack[index];
      if (!middleware) {
        return { status: 404, body: "not found" };
      }
      return middleware(req, () => run(index + 1));
    };
    return run(0);
  }
}

const app = new MiniApp();

app.use((req, next) => {
  console.log(`  log: ${req.path}`);
  return next();
});

app.use((req, next) => {
  if (!req.token) {
    console.log("  auth: no token, answering 401");
    return { status: 401, body: "sign in first" };
  }
  console.log("  auth: token present, passing on");
  return next();
});

app.use((req, next) => {
  if (req.path === "/orders") {
    console.log("  orders: answering 200");
    return { status: 200, body: "3 orders" };
  }
  return next();
});

for (const req of [{ path: "/orders" }, { path: "/orders", token: "t1" }, { path: "/about", token: "t1" }]) {
  console.log(`GET ${req.path}${req.token ? " with token" : ""}`);
  const res = app.handle(req);
  console.log(`  -> ${res.status} ${res.body}`);
}
OutputRan with node:22-slim (Node 22.23.1), TypeScript 5.9.3
GET /orders
  log: /orders
  auth: no token, answering 401
  -> 401 sign in first
GET /orders with token
  log: /orders
  auth: token present, passing on
  orders: answering 200
  -> 200 3 orders
GET /about with token
  log: /about
  auth: token present, passing on
  -> 404 not found
`next()` runs the rest of the chain; returning without calling it stops the request there.

The logger runs for every request and always passes on. The authentication middleware stops the first request with a 401, so the orders route never sees it. The second request passes authentication and is answered by the orders route. The third reaches the end of the chain unanswered, and MiniApp answers 404 itself, as Express does with its own final handler.

This is the pattern's shape with one difference. In the Gang of Four's chain, one handler deals with a request and the rest never see it. In middleware, most links act and then pass on, as the logger does, and only some stop the request. Each link still decides alone whether the request goes further, which is the part both share. A chain where every link acts and passes on is close to Decorator, which wraps an object to add behaviour; the difference is that a decorator always calls the object it wraps.