Guards, Interceptors & Pipes
Apply authentication, validation, logging, and response transformation with NestJS 11 guards, interceptors, and pipes.
Search across all documentation pages
Apply authentication, validation, logging, and response transformation with NestJS 11 guards, interceptors, and pipes.
Quick-reference recipe card - copy-paste ready.
import { Controller, Get, Param, UseGuards, UseInterceptors, UsePipes, ValidationPipe } from "@nestjs/common";
import { AuthGuard } from "./auth.guard.js";
import { LoggingInterceptor } from "./logging.interceptor.js";
@Controller("users")
@UseGuards(AuthGuard)
@UseInterceptors(LoggingInterceptor)
export class UsersController {
@Get(":id")
@UsePipes(ValidationPipe)
findOne(@Param("id") id: string) {
return { id };
}
}Execution order: Middleware -> Guards -> Interceptors (before) -> Pipes -> Handler -> Interceptors (after) -> Exception Filters
// auth.guard.ts
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common";
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest();
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) throw new UnauthorizedException();
req.userId = "user-42";
return true;
}
}
// logging.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from "@nestjs/common";
import { Observable, tap } from "rxjs";
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
const start = Date.now();
const req = context.switchToHttp().getRequest();
return next.handle().pipe(
tap(() => console.log(`${req.method} ${req.url} ${Date.now() - start}ms`))
);
}
}
// roles.guard.ts
import { SetMetadata, Injectable, CanActivate, ExecutionContext } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
export const ROLES_KEY = "roles";
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>(ROLES_KEY, context.getHandler());
if (!roles) return true;
const req = context.switchToHttp().getRequest();
return roles.includes(req.userRole);
}
}What this demonstrates:
boolean or throws to allow/deny accesspipe for before/after logicSetMetadata + Reflector for declarative role checks| Component | Runs when | Purpose | Returns |
|---|---|---|---|
| Pipe | Before handler | Transform/validate input | Transformed value |
| Guard | Before handler | Auth/authorization | true or throw |
| Interceptor | Around handler | Logging, caching, mapping | Observable stream |
| Filter | On exception | Error response shaping | HTTP response |
// main.ts
app.useGlobalGuards(new AuthGuard());
app.useGlobalInterceptors(new LoggingInterceptor());
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));| Pipe | Purpose |
|---|---|
ValidationPipe | class-validator DTO validation |
ParseIntPipe | String param to integer |
ParseUUIDPipe | Validate UUID format |
DefaultValuePipe | Default for optional params |
class-validator decorators to DTO classes.catchError. Fix: handle in interceptor or let exception filter catch.@Public() decorator with guard that skips marked routes.| Alternative | Use When | Don't Use When |
|---|---|---|
| Express middleware | NestJS on Express, simple auth | Want declarative per-route guards |
| Fastify hooks | NestJS on Fastify adapter | Need Nest decorator DX |
| Middleware in NestJS | Raw request/response access (cors) | Auth (guards are better) |
| Manual checks in controller | Quick prototype | Production auth |
Guards have access to execution context (handler, class metadata) and integrate with Reflector. Middleware is lower-level. Prefer guards for auth.
Yes. Use map() operator to transform the return value. Or use tap() for side effects only.
Create a @Public() metadata decorator and check it in the AuthGuard with Reflector.
Pipes transform/validate data going into the handler. Guards decide if the handler should run at all.
Global LoggingInterceptor with tap() measuring elapsed time. Or use OpenTelemetry interceptor.
Yes. NestJS supports guards, pipes, and interceptors on WebSocket and RPC contexts too.
ValidationPipe uses class-validator decorators. Fastify uses JSON Schema. Different syntax, same goal.
Yes. @UseGuards(AuthGuard, RolesGuard) runs all guards in order. All must return true.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 18, 2026