Skip to main content
Every HTTP request in nestrs passes through a layered pipeline before it reaches your handler. The pipeline runs cross-cutting concerns — authorization, logging, validation, error mapping — without cluttering handler code. Understanding the order of these layers is essential for reasoning about what happens when a guard fails, an interceptor logs a request, or a filter rewrites an error response.

Pipeline order

For a single matched route, nestrs composes Axum middleware in this sequence (outermost to innermost on the incoming path):
On the response path, the layers unwind in reverse: the handler produces a response, interceptors wrap it, filters catch any HttpException values, and the global stack applies final transformations (CORS headers, compression, production error sanitization).
NestJS teaches guards → interceptors → pipes → handler. nestrs respects a similar model but the exact nesting differs — exception filters are outermost. Refer to this page as the authoritative contract for nestrs, not a line-for-line NestJS clone.

Guards — CanActivate

Guards run before the handler and decide whether the request is allowed to proceed. Implement the CanActivate trait:
GuardError produces a JSON error response:
  • GuardError::unauthorized(message) → 401 Unauthorized
  • GuardError::forbidden(message) → 403 Forbidden

Applying guards

Declare guards per route inside impl_routes! with the with (G1, G2) syntax. Guards are evaluated left-to-right; the first failure short-circuits.

Pipes — PipeTransform

Pipes transform or validate a single value before it reaches the handler. nestrs includes two built-in pipes:
  • ParseIntPipe — parses a decimal string into i64
  • ValidationPipe — runs validator::Validate on a struct
Implement PipeTransform for custom pipes:

Applying pipes with #[use_pipes]

#[use_pipes(ValidationPipe)] switches #[param::body], #[param::query], and #[param::param] wiring to ValidatedBody, ValidatedQuery, and ValidatedPath extractors, which run validation at extraction time:
You can also call PipeTransform::transform directly in handler code without the macro:

Interceptors — Interceptor

Interceptors provide around-advice: they run logic before calling next.run(req) (pre-handler) and can inspect or modify the response after (post-handler). The built-in LoggingInterceptor shows the pattern:

Applying interceptors

The first interceptor listed in #[use_interceptors(I1, I2, …)] is the outermost Tower layer — it sees the request first and wraps next. On the response path, I1 runs last (outermost on the way back out).

Exception filters — ExceptionFilter

Exception filters intercept HttpException responses before they reach the client. Implement ExceptionFilter to rewrite error responses — for example, to translate error codes, add correlation IDs, or format errors for a specific API contract:

Applying exception filters

The first filter in #[use_filters(F1, F2, …)] is the outermost Tower layer. When an HttpException response bubbles up, the innermost filter (closest to the handler) catches it first, then the next filter outward.

Global middleware stack

Beyond per-route cross-cutting concerns, nestrs assembles a global middleware stack in NestApplication::build_router. The layers (from inner to outer on the incoming request) include:
  • Optional global exception filter
  • CORS
  • Security headers
  • Rate limiting
  • Timeouts
  • Request ID injection
  • Compression and request decompression
  • CSRF (when enabled)
  • Cookie and session layers
  • Your custom use_global_layer callbacks (outermost)
Each .layer(...) call in Axum wraps outside the existing stack, so later use_global_layer calls produce layers that are more outer on the incoming request. Do not rely on undocumented ordering between unrelated third-party layers; write integration tests if you need a guaranteed sequence.