How a request flows through a NestJS app, from socket to response. Knowing the order tells you where to put each piece of logic.
The pipeline
flowchart TD A[Incoming request] --> M[Middlewares] M --> EZ subgraph EZ [Exception Zone] direction TB G[Guards] --> BI[Before Interceptor] BI --> P[Pipes] P --> C[Controllers] C --> AI[After Interceptor] end BI -. same interceptor wraps both .- AI AI --> R[Response] G -.throws.-> EF[Exception Filters] BI -.throws.-> EF P -.throws.-> EF C -.throws.-> EF EF --> R
The two interceptor boxes are the same interceptor
A single
intercept(context, next)call wraps the handler. The “before” box is the code that runs prior to invoking the handler; the “after” box is the logic chained on the returned stream. Two boxes in the diagram, one method. Details in post pattern.You can stack multiple interceptors (global + controller + route). Each one wraps the next, so the boxes nest like onion layers — pre runs in registration order, post runs in FILO (first in, last out).
Why middleware sits outside the exception zone
Middleware runs on the raw platform layer (Express/Fastify), before Nest installs its filter chain. A synchronous
throwinside middleware bubbles to the platform’s default error handler, not to your@Catch()filters. To route a middleware error through the filter chain, callnext(err)explicitly. See Middleware > Gotchas.
The order
- Incoming request hits the HTTP adapter.
- Middleware: global, then module bound.
- Guards: global, controller, route.
- Interceptors (before): global, controller, route.
- Pipes: global, controller, route, then route parameters in reverse order.
- Controller handler runs.
- Interceptors (after): route, controller, global. FILO order — first interceptor in is the last one out.
- If anything threw, Exception filters catch it, resolving from route up to global.
- Response is sent.
Filters resolve in the opposite direction
Every other layer resolves outermost-first: global → controller → route. Exception filters invert that: route → controller → global. The first filter whose
@Catch()matches wins; nothing further sees the exception. This is why a route-bound filter can override a global one, but a global filter can never “wrap” a route filter.
Why the order matters
Pick the right tool by asking when it should run:
| Need | Tool |
|---|---|
| Mutate the raw request, attach correlation IDs | Middleware |
| Authorization decision before any work | Guards |
| Wrap the handler with logging, caching, retries | Interceptors |
| Validate or transform input | Pipes |
| Convert a thrown error into an HTTP response | Exception filters |
Source
Adapted from the official NestJS Request Lifecycle FAQ.