Skip to main content

Chain of Responsibility Pattern

Pass a request along a chain of handlers. Each handler decides to process the request or pass it to the next handler.


The Problem​

You're building an API. Every request needs authentication, validation, rate limiting, and logging. But it's all tangled together:

async function handleRequest(req: Request): Promise<Response> {
// Auth mixed in
const user = verifyToken(req.headers.authorization);
if (!user) return new Response("Unauthorized", { status: 401 });

// Rate limiting mixed in
if (isRateLimited(user.id)) return new Response("Too many requests", { status: 429 });

// Validation mixed in
const body = await req.json();
if (!isValid(body)) return new Response("Bad request", { status: 400 });

// Logging mixed in
console.log(`${req.method} ${req.url} by ${user.id}`);

// Actual business logic buried at the bottom
return processBusinessLogic(body);
}

Every new concern = modify the handler. Cross-cutting logic is tangled with business logic.


The Solution​

Each concern is a middleware. Chain them together, each one decides to handle or pass to next:

import { createChain } from "@pithos/core/eidos/chain/chain";

const handleRequest = createChain<Request, Response>(
// Auth: reject or enrich and pass
(req, next) => {
const user = verifyToken(req.headers.authorization);
if (!user) return new Response("Unauthorized", { status: 401 });
return next({ ...req, user });
},
// Rate limit: reject or pass
(req, next) => {
if (isRateLimited(req.user.id)) return new Response("Too Many Requests", { status: 429 });
return next(req);
},
// Validation: reject or pass
(req, next) => {
if (!isValid(req.body)) return new Response("Bad Request", { status: 400 });
return next(req);
},
// Logger: observe and always pass
(req, next) => {
console.log(`${req.method} ${req.url}`);
return next(req);
},
);

Add, remove, or reorder middleware without touching others. Each handler has a single responsibility. Sound familiar? It's exactly how Express, Hono, and Koa work under the hood.


Live Demo​

Code
/**
* HTTP Middleware using Chain of Responsibility pattern.
*
* createChain() works like Express/Hono/Koa middleware:
* each handler can either short-circuit (return a response) or pass to next.
*/
import { createChain, type Handler } from "@pithos/core/eidos/chain/chain";
import type { Request, Response, MiddlewareStep, MiddlewareConfig } from "./types";

interface MiddlewareDef {
key: keyof MiddlewareConfig;
name: string;
icon: string;
check: (req: Request) => { pass: true; req: Request; message: string } | { pass: false; response: Response; message: string };
}

const MIDDLEWARE_DEFS: MiddlewareDef[] = [
{
key: "rateLimit", name: "Rate Limit", icon: "⏱️",
check: (req) => {
const token = req.headers.authorization || "";
if (token.includes("rate-limited")) return { pass: false, response: { status: 429, statusText: "Too Many Requests", body: "Rate limit exceeded" }, message: "User exceeded rate limit" };
return { pass: true, req, message: "Within rate limit (42/100 requests)" };
},
},
{
key: "auth", name: "Auth", icon: "πŸ”",
check: (req) => {
const token = req.headers.authorization;
if (!token) return { pass: false, response: { status: 401, statusText: "Unauthorized", body: "Missing authorization token" }, message: "No token provided" };
return { pass: true, req: { ...req, user: { id: token.replace("Bearer ", "") } }, message: `Token verified: ${token.slice(0, 20)}...` };
},
},
{
key: "validation", name: "Validation", icon: "βœ…",
check: (req) => {
const body = req.body as Record<string, unknown> | undefined;
const isValid = body && typeof body.name === "string" && typeof body.email === "string";
if (!isValid) return { pass: false, response: { status: 400, statusText: "Bad Request", body: "Missing required fields: email" }, message: "Body validation failed" };
return { pass: true, req, message: "Body schema valid" };
},
},
{
key: "logging", name: "Logger", icon: "πŸ“",
check: (req) => ({ pass: true, req, message: `${req.method} ${req.path}` }),
},
];

function toHandler(def: MiddlewareDef): Handler<Request, Response> {
return (req, next) => {
const result = def.check(req);
return result.pass ? next(result.req) : result.response;
};
}

export function buildPipeline(config: MiddlewareConfig): (req: Request) => Response {
const handlers = MIDDLEWARE_DEFS.filter((def) => config[def.key]).map(toHandler);
handlers.push(() => ({ status: 200, statusText: "OK", body: "User created successfully" }));
return createChain(...handlers);
}

export async function executeMiddlewareChainAnimated(
request: Request,
config: MiddlewareConfig,
onStep: (step: MiddlewareStep) => void,
): Promise<Response> {
let req = { ...request };
for (const def of MIDDLEWARE_DEFS) {
if (!config[def.key]) continue;
await new Promise<void>((r) => setTimeout(r, 400));
const result = def.check(req);
if (!result.pass) {
onStep({ name: def.name, icon: def.icon, passed: false, message: result.message, response: result.response });
return result.response;
}
req = result.req;
onStep({ name: def.name, icon: def.icon, passed: true, message: result.message });
}
await new Promise<void>((r) => setTimeout(r, 400));
const response = { status: 200, statusText: "OK", body: "User created successfully" };
onStep({ name: "Handler", icon: "🎯", passed: true, message: "Business logic executed", response });
return response;
}
Result

Real-World Analogy​

Airport security. Your boarding pass is checked (auth), your bag goes through X-ray (validation), you walk through the metal detector (screening), then you're cleared to board. Each checkpoint can stop you or let you through to the next. Adding a new check doesn't change the others.


When to Use It​

  • Multiple handlers might process a request
  • The handler isn't known in advance
  • You want to add/remove processing steps dynamically
  • Building middleware pipelines (like Express, Hono, Koa)

When NOT to Use It​

If your processing is always the same fixed sequence with no early exits, a simple function composition or pipe is clearer. Chain shines when handlers can short-circuit.


API​

  • createChain β€” Build a handler chain with typed input/output
  • safeChain β€” Chain that catches errors and returns Result