In what order do middleware, guards, interceptors, pipes and filters run in NestJS?
Middleware, then guards, then interceptors (before the handler), then pipes, then the route handler, then interceptors again on the way out. Exception filters run only when something throws, at any of those steps.
NestJSimport { CallHandler, CanActivate, Controller, ExecutionContext, Get, MiddlewareConsumer, Module, NestInterceptor, NestMiddleware, Param, PipeTransform } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { tap } from 'rxjs'; const steps: string[] = []; class LogMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { steps.length = 0; steps.push('middleware'); next(); } } class AuthGuard implements CanActivate { canActivate(ctx: ExecutionContext) { steps.push('guard'); return true; } } class TimingInterceptor implements NestInterceptor { intercept(ctx: ExecutionContext, next: CallHandler) { steps.push('interceptor (before)'); return next.handle().pipe(tap(() => steps.push('interceptor (after)'))); } } class IdPipe implements PipeTransform { transform(value: string) { steps.push('pipe'); return Number(value); } } // GET /orders/7 @Controller('orders') class OrdersController { @Get(':id') find(@Param('id', IdPipe) id: number) { steps.push('handler'); return { id, steps }; } } @Module({ controllers: [OrdersController] }) class AppModule { configure(consumer: MiddlewareConsumer) { consumer.apply(LogMiddleware).forRoutes(OrdersController); } } async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalGuards(new AuthGuard()); app.useGlobalInterceptors(new TimingInterceptor()); await app.listen(3000); } bootstrap();The response lists the steps for
GET /orders/7.interceptor (after)is in it because Nest serializes the response only after the interceptor's stream completes. Make the guard returnfalseand send the request again: you get a403and the list never reachespipe.How do you write and apply middleware in NestJS?
A middleware is a class implementing
NestMiddleware(or a plain function) with the Express signature(req, res, next). Apply it in a module'sconfigure()method:@Injectable() export class RequestIdMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { res.setHeader('x-request-id', crypto.randomUUID()); next(); } } @Module({ controllers: [OrdersController] }) export class OrdersModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(RequestIdMiddleware) .exclude({ path: 'orders/health', method: RequestMethod.GET }) .forRoutes(OrdersController); } }Forget
next()and the request hangs. For app-wide plain middleware,app.use()inmain.tsalso works. See Express middleware.What is the difference between middleware and a guard?
Middleware runs before Nest has picked a handler, so it only knows the raw request. A guard runs after routing and receives an
ExecutionContext, so it knows which controller and method will run and can read their metadata (@Roles('admin')).Middleware Guard Knows the handler No Yes Returns Calls next()trueorfalse(or throws)Typical use Logging, request ids, body parsing Authentication, authorization Works for microservices, WebSockets No (HTTP only) Yes If the decision depends on the route, write a guard.
What is a guard in NestJS? Show a role check.
A guard is a class implementing
CanActivate. It decides whether the request reaches the handler: returningfalsemakes Nest answer403 Forbidden, and throwing an exception sends that exception instead.NestJSimport { Controller, Get, Module, Injectable, CanActivate, ExecutionContext, UseGuards } from '@nestjs/common'; import { NestFactory, Reflector } from '@nestjs/core'; export const Roles = Reflector.createDecorator<string[]>(); @Injectable() class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(ctx: ExecutionContext): boolean { const needed = this.reflector.get(Roles, ctx.getHandler()); if (!needed) return true; const role = ctx.switchToHttp().getRequest().headers['x-role']; return needed.includes(role); } } // GET /reports @Controller('reports') @UseGuards(RolesGuard) class ReportsController { @Get() @Roles(['admin']) all() { return ['q1', 'q2']; } @Get('public') open() { return 'anyone can read this'; } } @Module({ controllers: [ReportsController], providers: [RolesGuard] }) class AppModule {} async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap();The first request has no role, so it answers
403withForbidden resource. Add the headerx-role: adminin the request bar and send it again: you get["q1","q2"].GET /reports/publichas no@Roles, so the guard lets it through.Reflector.createDecorator()builds a typed metadata decorator; older code does the same withSetMetadata('roles', roles).How do you protect routes with JWT authentication in NestJS?
Write a guard that reads the
Authorization: Bearer <token>header, verifies the token withJwtServicefrom@nestjs/jwt, and attaches the payload to the request. ThrowUnauthorizedException(401) when it is missing or invalid.@Injectable() export class AuthGuard implements CanActivate { constructor(private jwt: JwtService) {} async canActivate(ctx: ExecutionContext) { const req = ctx.switchToHttp().getRequest(); const [type, token] = req.headers.authorization?.split(' ') ?? []; if (type !== 'Bearer' || !token) throw new UnauthorizedException(); try { req.user = await this.jwt.verifyAsync(token); } catch { throw new UnauthorizedException(); } return true; } }Register it globally with
APP_GUARDand mark open routes with a@Public()metadata decorator the guard checks. Passport (@nestjs/passport) is the other common route. JWT in Node.js shows the token itself.What is an interceptor in NestJS, and what is it used for?
An interceptor wraps the handler call. Its
intercept()gets the context and aCallHandler;next.handle()returns an RxJS Observable of the handler's result, so it can run code before the handler and transform, time, cache or replace the result after it.NestJSimport { CallHandler, Controller, createParamDecorator, ExecutionContext, Get, Injectable, Module, NestInterceptor, UseInterceptors, } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { map } from 'rxjs'; @Injectable() class EnvelopeInterceptor implements NestInterceptor { intercept(ctx: ExecutionContext, next: CallHandler) { const req = ctx.switchToHttp().getRequest(); return next.handle().pipe(map((data) => ({ data, path: req.url, version: 1 }))); } } const Lang = createParamDecorator((fallback: string, ctx: ExecutionContext) => { const req = ctx.switchToHttp().getRequest(); return req.headers['accept-language']?.slice(0, 2) || fallback; }); // GET /greet @Controller('greet') @UseInterceptors(EnvelopeInterceptor) class GreetController { @Get() hello(@Lang('en') lang: string) { return lang === 'fr' ? 'Bonjour' : 'Hello'; } } @Module({ controllers: [GreetController] }) class AppModule {} async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap();The handler returns the string
Hello, but the response is wrapped in{ data, path, version }by the interceptor. Send it again with the headerAccept-Language: fr-FRto getBonjour(read by the custom@Lang()decorator). Common uses: response envelopes, logging with timing,timeout(), and caching.How do you create a custom decorator like
@CurrentUser()?For a parameter, use
createParamDecorator, which receives the decorator's argument and theExecutionContext:export const CurrentUser = createParamDecorator( (field: keyof User | undefined, ctx: ExecutionContext) => { const user = ctx.switchToHttp().getRequest().user; return field ? user?.[field] : user; }, ); @Get('me') me(@CurrentUser() user: User, @CurrentUser('email') email: string) {}To bundle several decorators into one, use
applyDecorators:export const Auth = (...roles: string[]) => applyDecorators(Roles(roles), UseGuards(AuthGuard, RolesGuard));ValidationPipeskips arguments from custom parameter decorators unless it is created withvalidateCustomDecorators: true.What is an exception filter, and how do you change the error response format?
An exception filter catches exceptions thrown anywhere in the request lifecycle and writes the response.
@Catch(HttpException)limits it to HTTP exceptions;@Catch()with no argument catches everything.NestJSimport { ArgumentsHost, Catch, Controller, ExceptionFilter, Get, HttpException, HttpStatus, Module, NotFoundException, Param, } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; @Catch(HttpException) class ApiErrorFilter implements ExceptionFilter { catch(err: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const req = ctx.getRequest(); const status = err.getStatus(); ctx.getResponse().status(status).json({ ok: false, status, path: req.url, error: err.message, }); } } // GET /books/99 @Controller('books') class BooksController { private books = { 1: 'Dune' }; @Get(':id') find(@Param('id') id: string) { const title = this.books[id]; if (!title) throw new NotFoundException(`Book ${id} not found`); return { id, title }; } @Get('teapot/brew') brew() { throw new HttpException('No coffee here', HttpStatus.I_AM_A_TEAPOT); } } @Module({ controllers: [BooksController] }) class AppModule {} async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalFilters(new ApiErrorFilter()); await app.listen(3000); } bootstrap();The first request answers
404in the filter's shape:ok,status,pathanderror, instead of Nest's defaultstatusCode/message/error.GET /books/1is unaffected, andGET /books/teapot/brewshows the same shape for a418. Express error handling works the same way one level down: Express error handling.How does NestJS handle errors you throw in a controller?
Throw an
HttpExceptionor a subclass (BadRequestException,NotFoundException,ForbiddenException...) and Nest's built-in exception filter turns it into a JSON response with that status. Anything else becomes a500with a generic message, so internal details never reach the client.NestJS// /crash fails on purpose: a plain Error becomes a 500 import { BadRequestException, Controller, Get, HttpException, Module } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; // GET /pay @Controller() class PayController { @Get('pay') pay() { throw new BadRequestException('Card declined', { description: 'Payment failed' }); } @Get('code') coded() { throw new HttpException('Too many tries', 429, { errorCode: 'RATE_LIMITED' }); } @Get('crash') crash() { throw new Error('database password is hunter2'); } } @Module({ controllers: [PayController] }) class AppModule {} async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap();GET /payanswers400with your message and the description aserror. SendGET /code: NestJS 12 adds anerrorCodeoption that clients can switch on instead of parsing messages.GET /crashanswers500withInternal server error, while the real message only goes to the server log.What is the difference between
app.useGlobalGuards()and theAPP_GUARDprovider?Both apply a guard to every route, but
app.useGlobalGuards(new AuthGuard())creates the instance yourself, outside the container, so the guard cannot inject services such asJwtServiceorReflector.Registering it as a provider lets Nest build it with dependency injection:
@Module({ providers: [ { provide: APP_GUARD, useClass: AuthGuard }, { provide: APP_PIPE, useValue: new ValidationPipe({ whitelist: true }) }, { provide: APP_INTERCEPTOR, useClass: LoggingInterceptor }, { provide: APP_FILTER, useClass: AllExceptionsFilter }, ], }) export class AppModule {}The same pair exists for pipes, interceptors and filters. Prefer the provider form unless the enhancer has no dependencies. One trap:
useGlobal*inmain.tsis not applied in e2e tests that build the app from the module, whileAPP_*providers are.When guards, interceptors and filters are bound at several levels, in what order do they run?
Guards, interceptors and pipes run from the outside in: global, then controller, then method, and in array order within one decorator. Interceptors unwind in reverse on the way out, like nested functions.
Exception filters go the other way: Nest tries method filters first, then controller, then global, and only the first filter whose
@Catch()matches handles the exception. Filters do not chain; to reuse logic, extendBaseExceptionFilterand callsuper.catch().@UseInterceptors(A) // controller level class C { @UseInterceptors(B) // method level @Get() h() {} } // before: global, A, B -> handler -> after: B, A, global
NestJS Middleware, Guards and Interceptors Questions
What runs before and after a route handler, in which order, and when to use each piece.