typescriptintermediate
Middleware Pattern and Composition
Implement the middleware pattern for composable request processing with next() and error handling.
typescriptPress ⌘/Ctrl + Shift + C to copy
// Generic middleware pattern (framework-agnostic)
type Context = {
method: string;
path: string;
headers: Record<string, string>;
body?: unknown;
state: Record<string, unknown>;
response?: { status: number; body: unknown };
};
type Middleware = (ctx: Context, next: () => Promise<void>) => Promise<void>;
// Middleware runner (like Koa)
async function compose(middlewares: Middleware[]): (ctx: Context) => Promise<void> {
return async (ctx: Context) => {
let index = -1;
async function dispatch(i: number): Promise<void> {
if (i <= index) throw new Error('next() called multiple times');
index = i;
if (i >= middlewares.length) return;
const fn = middlewares[i];
await fn(ctx, () => dispatch(i + 1));
}
await dispatch(0);
};
}
// Logger middleware
const logger: Middleware = async (ctx, next) => {
const start = Date.now();
console.log(`→ ${ctx.method} ${ctx.path}`);
await next();
const ms = Date.now() - start;
console.log(`← ${ctx.response?.status ?? 200} (${ms}ms)`);
};
// Auth middleware
const auth: Middleware = async (ctx, next) => {
const token = ctx.headers['authorization'];
if (!token) {
ctx.response = { status: 401, body: { error: 'Unauthorized' } };
return; // don't call next
}
ctx.state.userId = 'user-123'; // decoded from token
await next();
};
// Error handler middleware
const errorHandler: Middleware = async (ctx, next) => {
try {
await next();
} catch (err) {
const message = err instanceof Error ? err.message : 'Unknown error';
ctx.response = { status: 500, body: { error: message } };
console.error('Error:', message);
}
};
// Response time header
const timing: Middleware = async (ctx, next) => {
const start = process.hrtime.bigint();
await next();
const ns = process.hrtime.bigint() - start;
ctx.headers['x-response-time'] = `${Number(ns / 1_000_000n)}ms`;
};
// Route handler
const handler: Middleware = async (ctx, _next) => {
ctx.response = {
status: 200,
body: { message: 'Hello!', user: ctx.state.userId },
};
};
// Compose and run
const app = await compose([
errorHandler,
logger,
timing,
auth,
handler,
]);
const ctx: Context = {
method: 'GET',
path: '/api/profile',
headers: { authorization: 'Bearer token123' },
state: {},
};
await app(ctx);
console.log('Response:', ctx.response);Use Cases
- Request processing pipelines
- Express/Koa-style middleware
- Cross-cutting concerns (logging, auth)
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
typescriptintermediate
Middleware Chain Pattern
Implement the middleware/pipeline pattern for composable request processing without Express.
Best for: Custom HTTP framework
#nodejs#middleware
typescriptintermediate
Express Error Handling Middleware
Centralized error handling in Express with custom error classes, async wrapper, and structured responses.
Best for: API error standardization
#nodejs#express
typescriptbeginner
CORS Configuration Middleware
Configure Cross-Origin Resource Sharing with origin allowlists, credentials, and preflight handling.
Best for: API CORS configuration
#nodejs#cors
typescriptadvanced
Middleware Chain Pattern
Compose multiple middleware functions for auth, rate limiting, and geolocation in Next.js middleware.
Best for: Auth guard middleware
#nextjs#middleware