Nest processes an incoming request through ordered extension points — middleware, guards, interceptors, and pipes — where each stage has full access to different information and a distinct, narrow responsibility.
In simpler words
Middleware runs first and knows nothing about routes; guards decide who may proceed; pipes clean up and validate the data the handler will receive.
Correlation ids, JWT/role checks, and DTO validation look similar — “code that runs before my controller” — but belong in different stages for a reason.
Order middleware, guards, and pipes in the request pipeline
Explain why middleware cannot read @Roles or @Public
Choose the right stage for a new cross-cutting check
Where each stage sits in the pipeline
Definition
Middleware executes before Nest resolves a route handler and its metadata, guards execute afterward with full execution-context access, and pipes execute per parameter to transform or validate arguments just before the handler runs.
In simpler words
Middleware cannot see @Roles or @Public; guards can. Pipes see one argument at a time, not the whole request.
CorrelationIdMiddleware runs for every matched path via consumer.apply(...).forRoutes("*") — it cannot make an authorization decision because guards, and the route metadata they read, do not exist yet at that point.
Cross-cutting and stateless — a good middleware job. It never reads @Roles or user identity.
Choosing the right tool for a new check
Definition
A new cross-cutting request concern belongs in middleware only when it needs no route metadata and no per-argument transformation, in a guard when it depends on route metadata or the authenticated subject, and in a pipe when it validates or reshapes one specific argument.
In simpler words
Ask: does this need the route’s decorators (guard), just one incoming value (pipe), or neither (middleware)?
ValidationPipe is registered globally in main.ts and runs for every DTO-typed parameter — it is what turns CreateTicketDto’s decorators into an actual 400 response.
ParseUUIDPipe on :id params is a pipe too: it rejects a malformed id before TicketsService ever runs a query.
Three stages, one request
GET /tickets/not-a-uuid
// Middleware: attaches correlation id, calls next()
// Guards: JwtAuthGuard confirms the session, RolesGuard allows (no @Roles here)
// Pipe: ParseUUIDPipe rejects 'not-a-uuid' -> 400 before the handler runs
Each stage rejects or passes the request for a different reason — none of them duplicate the others’ job.
Keep in mind
Middleware: cross-cutting, stateless, no route metadata.
Guards: authentication and authorization, route-metadata aware.
Pipes: per-argument validation and transformation, closest to the handler.
Test
Check your understanding
At least 10 questions — mix of concept, syntax, practical, and logic. Score ≥ 80% (enforced by the API) to save progress.