design·lab

Behavioral pattern

Chain of Responsibility

A request travels down a line of handlers — each one either deals with it or passes it on — so the sender never needs to know which handler will actually respond.

✗ The problem

One giant function, tangled with every concern

Auth, rate-limiting, validation and the actual handling all live in nested if/else blocks. You can't reorder, reuse, or remove a single step in isolation.

function handleRequest(req) {
  if (checkAuth(req)) {
    if (checkRateLimit(req)) {
      if (validate(req)) {
        return route(req);
      } else return 400;
    } else return 429;
  } else return 401;
  // ← want a new step? squeeze in another nested branch…
}
Every concern is jammed into one place — hard to add, remove, or reorder a single step without touching all the others.
✓ The pattern

A chain of handlers — each passes to the next

Every handler either short-circuits the request or forwards it to this.next. Nobody needs to know the whole chain.

class Handler {
  setNext(h) { this.next = h; return h; }
  handle(req) {
    return this.next ? this.next.handle(req) : null;
  }
}
// Auth, RateLimit, Validate, Route each
// extend Handler and short-circuit or
// call super.handle(req) to move on.
Auth
RateLimit
Validate
Route
✓ See it live

Send a request — watch it walk the chain

A valid request flows through every handler to the end. An unauthorized one is rejected by Auth — the rest never run.

Follow the numbered messages top-to-bottom — each handler either passes the request on to the next, or short-circuits the chain.

✓ Takeaway

Decouple sender from receiver

  • The caller just fires the request at the first handler — it never knows which one (if any) ends up handling it.
  • Composable: add, remove, or reorder handlers without touching the others.
  • You already use it: Express/Koa middleware, servlet filters, DOM event bubbling, logging pipelines.
  • Careful: if no handler deals with the request, it can fall off the end unhandled — always have a default/terminal handler.
🎯 Principle applied: each handler has one job (SRP) and you add or reorder handlers without touching the others (Open/Closed).