---
# === IDENTITY ===
id: software/patterns/middleware-pipeline/2026
canonical_question: "How does the middleware pipeline pattern work?"
aliases:
  - "middleware pattern"
  - "request pipeline pattern"
  - "middleware chain"
  - "chain of responsibility HTTP"
  - "next() callback pattern"
  - "middleware composition"
  - "HTTP request pipeline"
  - "middleware stack"
entity_type: software_reference
domain: software > patterns > middleware_pipeline
region: global
jurisdiction: global
temporal_scope: 2010-2026

# === VERIFICATION ===
last_verified: 2026-02-24
confidence: 0.90
version: 1.0
first_published: 2026-02-24

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: null
  next_review: 2026-08-23
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "Middleware execution order is registration order -- reordering middleware changes behavior silently"
  - "Every middleware MUST call next() (or equivalent) or the request will hang or time out"
  - "Error-handling middleware must be registered separately from regular middleware in frameworks that distinguish them (Express uses 4-arg signature)"
  - "Never mutate shared state across concurrent requests -- use request-scoped context objects instead"
  - "Short-circuiting middleware (auth, rate-limit) must appear before the handlers they protect"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Looking for event-driven pub/sub or message broker patterns"
    use_instead: "software/system-design/message-queue-event-driven/2026"
  - condition: "Need to implement an API gateway with routing, load balancing, and service discovery"
    use_instead: "software/system-design/api-gateway/2026"
  - condition: "Looking for Unix pipe-and-filter data processing pipelines"
    use_instead: "Pipe-and-filter architecture pattern (different: processes data streams, not HTTP request/response)"

# === AGENT HINTS ===
inputs_needed:
  - key: "framework"
    question: "Which web framework or language are you using?"
    type: choice
    options: ["Express/Node.js", "Koa", "Fastify", "Django", "FastAPI", "ASP.NET Core", "Go net/http", "Gin", "other"]
  - key: "concern"
    question: "What cross-cutting concern do you need to add -- logging, auth, error handling, rate limiting, or something else?"
    type: text

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/patterns/middleware-pipeline/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-02-24)"

# === RELATED UNITS ===
related_kos:
  related_to:
    - id: "software/patterns/dependency-injection/2026"
      label: "Dependency Injection Patterns"
    - id: "software/patterns/error-handling-strategies/2026"
      label: "Error Handling Strategies"
    - id: "software/patterns/api-rate-limiting/2026"
      label: "API Rate Limiting Patterns"
  depends_on: []
  solves:
    - id: "software/system-design/api-gateway/2026"
      label: "API Gateway Design"
  alternative_to:
    - id: "software/patterns/decorator-pattern/2026"
      label: "Decorator Pattern (OOP alternative for wrapping behavior)"
  often_confused_with:
    - id: "software/system-design/message-queue-event-driven/2026"
      label: "Message Queue / Event-Driven Architecture (async, not request/response)"

# === SOURCES (7 authoritative sources) ===
sources:
  - id: src1
    title: "Using Express middleware"
    author: Express.js
    url: https://expressjs.com/en/guide/using-middleware.html
    type: official_docs
    published: 2024-01-01
    reliability: authoritative
  - id: src2
    title: "ASP.NET Core Middleware"
    author: Microsoft
    url: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/
    type: official_docs
    published: 2024-11-12
    reliability: authoritative
  - id: src3
    title: "Making and Using HTTP Middleware in Go"
    author: Alex Edwards
    url: https://www.alexedwards.net/blog/making-and-using-middleware
    type: technical_blog
    published: 2023-09-15
    reliability: high
  - id: src4
    title: "Advanced Middleware - FastAPI"
    author: FastAPI
    url: https://fastapi.tiangolo.com/advanced/middleware/
    type: official_docs
    published: 2024-06-01
    reliability: authoritative
  - id: src5
    title: "Understanding the Middleware Pattern in Express.js"
    author: DZone
    url: https://dzone.com/articles/understanding-middleware-pattern-in-expressjs
    type: technical_blog
    published: 2023-04-10
    reliability: moderate_high
  - id: src6
    title: "Middleware Patterns in Go"
    author: Dave Stearns (University of Washington)
    url: https://drstearns.github.io/tutorials/gomiddleware/
    type: technical_blog
    published: 2023-01-15
    reliability: high
  - id: src7
    title: "Deep Dive: How is the ASP.NET Core Middleware Pipeline Built?"
    author: Steve Gordon
    url: https://www.stevejgordon.co.uk/how-is-the-asp-net-core-middleware-pipeline-built
    type: technical_blog
    published: 2023-03-20
    reliability: high
---

# Middleware Pipeline Pattern

## TL;DR

- **Bottom line**: The middleware pipeline is a chain of composable handlers that each process an HTTP request/response, optionally transforming them before calling the next handler in the sequence.
- **Key tool/command**: `next()` callback pattern -- each middleware calls `next()` to pass control to the next handler, or short-circuits by sending a response directly.
- **Watch out for**: Middleware order matters -- registering auth middleware after your route handler means the route is unprotected.
- **Works with**: Every major web framework (Express, Koa, Django, FastAPI, ASP.NET Core, Go net/http, Gin, Fiber, Fastify).

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- Middleware execution order is registration order -- reordering middleware changes behavior silently
- Every middleware MUST call `next()` (or equivalent) or the request will hang or time out
- Error-handling middleware must be registered separately from regular middleware in frameworks that distinguish them (Express uses 4-arg signature)
- Never mutate shared state across concurrent requests -- use request-scoped context objects instead
- Short-circuiting middleware (auth, rate-limit) must appear before the handlers they protect

## Quick Reference

| Framework | Middleware Signature | Execution Order | Error Handling | Context Passing |
|---|---|---|---|---|
| **Express** (Node.js) | `(req, res, next) => {}` | Registration order | 4-arg `(err, req, res, next)` | `req.customProp` mutation |
| **Koa** (Node.js) | `async (ctx, next) => {}` | Onion model (downstream then upstream) | `try/catch` around `await next()` | `ctx.state` object |
| **Fastify** (Node.js) | `(req, reply, done) => {}` or hooks | Hook lifecycle order | `onError` hook | `req.customProp` via decorators |
| **Django** (Python) | `class` with `__call__(self, request)` | `MIDDLEWARE` list order | `process_exception()` method | `request.META` / custom attrs |
| **FastAPI** (Python) | `@app.middleware("http")` or ASGI class | Registration order | `try/except` in middleware | `request.state` |
| **ASP.NET Core** (C#) | `RequestDelegate` via `Use()` / `Run()` | `Configure()` pipeline order | `UseExceptionHandler()` | `HttpContext.Items` dict |
| **Go net/http** | `func(http.Handler) http.Handler` | Wrapping order (outermost first) | `recover()` in defer | `context.WithValue()` |
| **Gin** (Go) | `func(c *gin.Context)` | `Use()` registration order | `c.AbortWithError()` | `c.Set()` / `c.Get()` |
| **Fiber** (Go) | `func(c *fiber.Ctx) error` | `Use()` registration order | Error return + `ErrorHandler` | `c.Locals()` |

## Decision Tree

```
START
|-- Need cross-cutting concerns (logging, auth, CORS, rate-limit)?
|   |-- YES --> Use middleware pipeline (this pattern)
|   +-- NO --> Standard route handlers are sufficient
|
|-- Using Node.js?
|   |-- Need async/await with backpressure? --> Koa (onion model)
|   |-- Need maximum performance? --> Fastify (hook lifecycle)
|   +-- General purpose? --> Express (classic next() chain)
|
|-- Using Python?
|   |-- Async with type hints? --> FastAPI ASGI middleware
|   +-- Traditional sync? --> Django middleware classes
|
|-- Using Go?
|   |-- Standard library only? --> net/http Handler wrapping
|   +-- Need routing + middleware? --> Gin or Fiber
|
|-- Using C#?
|   +-- ASP.NET Core --> RequestDelegate pipeline with Use/Map/Run
|
+-- DEFAULT --> Implement the handler-wrapper pattern in your language:
     func middleware(next Handler) Handler
```

## Step-by-Step Guide

### 1. Define the middleware signature

Choose the appropriate signature for your framework. The core idea is always the same: accept a handler (or `next` function), do work before/after calling it. [src1]

```javascript
// Express: (req, res, next) => void
const myMiddleware = (req, res, next) => {
  // pre-processing
  console.log(`${req.method} ${req.url}`);
  next(); // pass to next middleware
  // post-processing (runs after downstream completes in sync mode)
};
```

**Verify**: Add `console.log('middleware hit')` and confirm it prints on every request.

### 2. Register middleware in the correct order

Order determines behavior. Auth must come before protected routes. Logging typically comes first. [src2]

```javascript
// Express example
const express = require('express');
const app = express();

// 1. Logging (runs first)
app.use(requestLogger);
// 2. CORS headers
app.use(cors());
// 3. Body parsing
app.use(express.json());
// 4. Authentication
app.use(authMiddleware);
// 5. Routes (protected by auth above)
app.use('/api', apiRouter);
// 6. Error handler (must be last, must have 4 args)
app.use(errorHandler);
```

**Verify**: Send an unauthenticated request -- it should be rejected at step 4 before reaching step 5.

### 3. Implement short-circuiting for guard middleware

Guard middleware (auth, rate-limit) should NOT call `next()` when the check fails. [src1]

```javascript
const authMiddleware = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
    // next() is NOT called -- pipeline stops here
  }
  try {
    req.user = verifyToken(token);
    next(); // authenticated -- continue pipeline
  } catch (err) {
    return res.status(403).json({ error: 'Invalid token' });
  }
};
```

**Verify**: `curl -H "Authorization: Bearer invalid" /api/protected` returns 403.

### 4. Handle errors in the pipeline

Each framework has its own error propagation model. Express uses a special 4-argument middleware. Go uses `recover()`. Koa uses `try/catch`. [src5]

```javascript
// Express error-handling middleware (MUST have 4 parameters)
const errorHandler = (err, req, res, next) => {
  console.error(err.stack);
  res.status(err.status || 500).json({
    error: err.message || 'Internal Server Error'
  });
};
// Register LAST in the middleware chain
app.use(errorHandler);
```

**Verify**: Throw an error in a route handler and confirm the error middleware catches it.

### 5. Pass data between middleware via request context

Never use module-level variables for per-request data. Use the request-scoped context object provided by your framework. [src3]

```javascript
// Express: attach to req
app.use((req, res, next) => {
  req.requestId = crypto.randomUUID();
  req.startTime = Date.now();
  next();
});

// Later middleware or route can read req.requestId
app.get('/api/data', (req, res) => {
  res.json({ requestId: req.requestId });
});
```

**Verify**: Two concurrent requests should have different `requestId` values.

## Code Examples

### Node.js/Express: Complete Middleware Pipeline

> Full script: [node-js-express-complete-middleware-pipeline.js](scripts/node-js-express-complete-middleware-pipeline.js) (27 lines)

```javascript
// Input:  HTTP request
// Output: HTTP response processed through logging, auth, and timing middleware
const express = require('express');
const app = express();
// Timing middleware -- measures response time
# ... (see full script)
```

### Python/FastAPI: ASGI Middleware

```python
# Input:  ASGI HTTP request
# Output: Response with timing header and request ID

import time
import uuid
from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware

app = FastAPI()

class TimingMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        start = time.perf_counter()
        request.state.request_id = str(uuid.uuid4())
        response = await call_next(request)
        duration = time.perf_counter() - start
        response.headers["X-Process-Time"] = f"{duration:.4f}"
        response.headers["X-Request-ID"] = request.state.request_id
        return response

app.add_middleware(TimingMiddleware)

@app.get("/api/data")
async def get_data(request: Request):
    return {"request_id": request.state.request_id}
```

### Go: Custom Middleware Chain (net/http)

> Full script: [go-custom-middleware-chain-net-http.go](scripts/go-custom-middleware-chain-net-http.go) (48 lines)

```go
// Input:  http.Handler (the next handler in the chain)
// Output: http.Handler (wrapped handler with added behavior)
package main
import (
    "context"
# ... (see full script)
```

## Anti-Patterns

### Wrong: Order-dependent logic without documentation

```javascript
// BAD -- auth middleware registered AFTER the route
app.get('/admin/users', getUsers);     // unprotected!
app.use(authMiddleware);                // too late
```

### Correct: Guard middleware before protected routes

```javascript
// GOOD -- auth registered BEFORE the routes it protects
app.use('/admin', authMiddleware);      // runs first
app.get('/admin/users', getUsers);      // protected
```

### Wrong: Middleware doing too much (god middleware)

```javascript
// BAD -- one middleware handles auth, logging, CORS, rate-limit, and validation
app.use((req, res, next) => {
  // 80 lines of mixed concerns
  logRequest(req);
  if (!checkAuth(req)) return res.status(401).end();
  res.setHeader('Access-Control-Allow-Origin', '*');
  if (isRateLimited(req.ip)) return res.status(429).end();
  validateBody(req.body);
  next();
});
```

### Correct: Single-responsibility middleware

```javascript
// GOOD -- each middleware does one thing
app.use(requestLogger);
app.use(cors({ origin: ALLOWED_ORIGINS }));
app.use(rateLimiter({ windowMs: 60000, max: 100 }));
app.use(authenticate);
app.use(validateRequest);
```

### Wrong: Mutating shared module-level state

```javascript
// BAD -- shared mutable state across all requests
let currentUser = null;  // module-level -- race condition!

app.use((req, res, next) => {
  currentUser = decodeToken(req.headers.authorization);
  next();
});
```

### Correct: Request-scoped context

```javascript
// GOOD -- data attached to the request object (per-request scope)
app.use((req, res, next) => {
  req.user = decodeToken(req.headers.authorization);
  next();
});
```

## Common Pitfalls

- **Forgetting to call `next()`**: Request hangs until client timeout. Every non-terminal middleware must call `next()`. Fix: lint rule or middleware wrapper that warns on missing `next()` call. [src1]
- **Calling `next()` after sending a response**: Causes "headers already sent" errors in Express or double-write panics in Go. Fix: always `return` after `res.send()` / `res.json()` / `http.Error()`. [src5]
- **Error swallowed silently**: If middleware catches an error but does not re-throw or pass to error handler, failures are invisible. Fix: always call `next(err)` (Express) or re-raise (Python). [src2]
- **Async middleware without `await next()`**: In Koa and FastAPI, forgetting `await` on `next()` / `call_next()` means post-processing runs before downstream completes. Fix: always `await next()` in async middleware. [src4]
- **Applying middleware globally when only some routes need it**: Performance waste and potential security issues (e.g., body parsing on GET-only routes). Fix: use path-scoped middleware -- `app.use('/api', middleware)`. [src1]
- **Not handling the Koa onion model correctly**: In Koa, code after `await next()` runs in reverse order (upstream). Developers expecting Express-style linear flow get confused. Fix: understand that Koa middleware wraps like nested function calls. [src5]

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Adding cross-cutting concerns (logging, auth, CORS, rate-limiting) to HTTP handlers | Processing asynchronous event streams | Message queue / pub-sub pattern |
| Need to compose reusable request/response transformations | Single handler with no shared concerns | Direct route handler |
| Building plugin systems where third parties extend request processing | Heavy data transformation pipelines (ETL) | Pipe-and-filter or streaming architecture |
| Need clean separation of concerns in web application request processing | Simple scripts or CLIs with no request/response model | Function composition or decorator pattern |
| Framework provides built-in middleware support (Express, Django, ASP.NET Core, etc.) | Performance-critical hot path where function call overhead matters | Inlined handler logic |

## Important Caveats

- The middleware pipeline pattern is inherently synchronous in concept (one request flows through a chain), but many frameworks support async middleware. Be aware of the async model of your specific framework.
- Koa's "onion model" is fundamentally different from Express's linear chain. Code after `await next()` in Koa runs after downstream handlers complete, creating a symmetrical wrap. Express does not have this behavior by default.
- In Go, middleware wrapping reverses the apparent registration order: `Chain(handler, A, B)` means A executes first (outermost), then B, then handler. This is opposite to Express where the first `app.use()` runs first.
- ASP.NET Core builds the pipeline in reverse at startup (innermost delegate first), but middleware executes in registration order at runtime. The `Run()` method is terminal and does not call `next()`.
- Performance overhead per middleware is typically negligible (microseconds), but deeply nested pipelines (>20 middleware) in hot paths should be profiled.

## Related Units

- [Dependency Injection Patterns](/software/patterns/dependency-injection/2026)
- [Error Handling Strategies](/software/patterns/error-handling-strategies/2026)
- [API Rate Limiting Patterns](/software/patterns/api-rate-limiting/2026)
- [API Gateway Design](/software/system-design/api-gateway/2026)
