Pass a request along a line of handlers until one of them deals with it, without the sender knowing which one will.
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.
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.
Each handler decides independently whether to call `next()`. Reordering the chain is just reordering the `setNext` calls.
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" }));