Behavioral · DWG NO. 016

Chain of Responsibility

Pass a request along a line of handlers until one of them deals with it, without the sender knowing which one will.

TypeBehavioral
ScopeSequential handlers
ComplexityLow–Medium
Common inMiddleware stacks, support ticket routing, validation pipelines

One function that knows about every possible handler

An HTTP request needs auth checking, then rate limiting, then logging, then the actual route handler. Writing that as one function with all four concerns inline makes each concern impossible to reuse or test on its own, and reordering steps means editing the whole function.

A line of handlers, each free to stop or pass along

Each handler holds a reference to the next one in the chain. It either handles the request and stops, or passes it forward. The sender only ever talks to the first handler and doesn't know how many others exist.

AuthRateLimitLoggerRoute

Middleware-style request handlers

Each handler decides independently whether to call `next()`. Reordering the chain is just reordering the `setNext` calls.

request-chain.js
class Handler {
  setNext(handler) { this.next = handler; return handler; }
  handle(request) {
    if (this.next) return this.next.handle(request);
    return "unhandled";
  }
}

class AuthHandler extends Handler {
  handle(request) {
    if (!request.token) return "401 unauthorized";
    console.log("auth: ok");
    return super.handle(request);
  }
}

class RateLimitHandler extends Handler {
  handle(request) {
    if (request.requestsThisMinute > 100) return "429 too many requests";
    console.log("rate limit: ok");
    return super.handle(request);
  }
}

class RouteHandler extends Handler {
  handle(request) {
    return `200 handled ${request.path}`;
  }
}

const auth = new AuthHandler();
auth.setNext(new RateLimitHandler()).setNext(new RouteHandler());

console.log(auth.handle({ token: "abc", requestsThisMinute: 3, path: "/orders" }));
console.log(auth.handle({ requestsThisMinute: 3, path: "/orders" }));

Trade-offs