Chain of Responsibility
Chain of Responsibility arranges handlers in sequence; each handles the request or passes to next.
Introduction
Chain of Responsibility arranges handlers in sequence; each handles the request or passes to next. Shines for preprocessing pipelines: auth → rate limit → validate → route. Express/Koa middleware is Chain of Responsibility made first-class.
The story
A B2B API piled auth, rate-limit, schema validation, and observability into every controller. New endpoints kept forgetting one step. Middleware chain at router boundary made controllers 8 lines and impossible to skip essentials.
The business problem
Cross-cutting concerns duplicated in every handler cause gaps and drift:
- Forgotten steps: new endpoint skips auth or rate-limit.
- Duplicated boilerplate: same 40 lines atop every controller.
- Reorder pain: changing pipeline order edits every handler.
- Environment variance: dev skips auth; prod needs it — per-route config hard.
The problem teams faced
Chain of Responsibility fits when:
- Many cross-cutting concerns run per request (auth, validate, log).
- Concerns must be added/removed/reordered per route or environment.
- Multiple handlers may process same request (pipeline) or first match wins.
- Sender should not know which handler ultimately processes request.
Understanding the topic
Intent: Avoid coupling sender to receiver; chain handlers until one handles or pipeline completes.
- Handler — handle(req, next) or setNext chain.
- ConcreteHandler — processes or forwards.
- Client — submits to head of chain.
- Middleware — CoR with next() callback (Express style).
Internal architecture
Express middleware chain:
app.use(authMiddleware) // 401 or next()app.use(rateLimitMiddleware) // 429 or next()app.use(validateSchema) // 400 or next()app.use(requestLogger)app.post("/orders", async (req, res) => {const order = await orderService.place(req.body) // 8 linesres.json(order)})
Visual explanation
Three diagrams cover middleware chain, short-circuit, and vs Decorator:
Informative example
Before / after — duplicated checks to middleware chain:
// ❌ Every controller repeats cross-cuttingapp.post("/orders", async (req, res) => {if (!req.user) return res.status(401).end()if (await rateLimiter.isOver(req.ip)) return res.status(429).end()if (!schema.validate(req.body)) return res.status(400).end()console.log(req.path)// ... actual logic})// ✅ Chain at router — controller is business onlyconst pipeline = [auth, rateLimit, validateOrder, logger]app.post("/orders", ...pipeline, placeOrderHandler)async function placeOrderHandler(req, res) {const order = await orderService.place(req.body)res.json(order)}
Execution workflow
Identify concerns
Auth, rate-limit, validate, log, audit.
Real-world use
Express/Koa/ASP.NET middleware, Java Servlet filters, nginx access phases, Kafka interceptor chains, gRPC interceptors, Spring HandlerInterceptor chain.
Production case study
B2B API middleware extraction:
- Scenario: Auth, rate-limit, validation, logging copy-pasted per controller.
- Decision: Router-level middleware chain; controllers to 8 lines.
- Outcome: New endpoints can't skip essential steps; order changed in one place.
Trade-offs
- Pro: single place per concern; reorder without touching handlers.
- Pro: compose per route or environment.
- Con: debugging through long chains — order matters.
- Con: forgotten next() hangs request silently.
Decision framework
- Use when 3+ cross-cutting concerns repeat per request.
- Use when order of concerns matters and varies.
- Avoid for single concern — direct middleware function enough.
- Avoid deep chains without logging — debugging nightmare.
Best practices
- One concern per middleware — auth separate from logging.
- Always call next() or send response — never fall through.
- Document chain order; test each middleware in isolation.
Anti-patterns to avoid
- Mega-middleware doing auth + validate + log + business logic.
- Business logic in middleware — belongs in handler.
- 20-layer chain with no tracing.
Common mistakes
- Forgotten next() — request hangs until timeout.
- Error swallowed without passing to error middleware.
- Async middleware without proper error forwarding.
Debugging tips
- Log middleware name at entry/exit with request ID.
- Temporarily bisect chain — disable half to find failure.
Optimization strategies
- Skip expensive middleware when feature flag off early in chain.
- Cache auth result on req context — don't re-validate in every middleware.
Common misconceptions
- CoR ≠ Decorator. CoR processes requests through pipeline; Decorator wraps object interface.
- Express middleware is CoR — canonical modern example.
- CoR in event systems — command validation pipeline before aggregate.
Advanced interview questions
Interview Prep
Practice concise answers, then expand each card for the explanation.
1IntermediateQuestionChain of Responsibility vs Decorator?+
Answer
Follow-up
2BeginnerQuestionForgotten next() symptom?+
Answer
Follow-up
3IntermediateQuestionOrder auth vs logger?+
Answer
Follow-up
4AdvancedQuestionCoR in event-driven systems?+
Answer
Follow-up
Summary
You can build middleware pipelines, avoid duplicated cross-cutting, and debug chain order. Tell the B2B API controller story — if you can do that, you own Chain of Responsibility.