This chapter follows Middleware in NestJS.
Guards run after middleware and before interceptors and pipes. A guard returns true to let the request through or false to stop it, and it has one advantage over middleware: it knows which handler is about to run. That makes guards the home for authentication and authorization, and for any rule that depends on what a particular route declares about itself.
The CanActivate contract
A guard is a provider that implements CanActivate. Its canActivate() method may return a boolean, a Promise of one, or an Observable of one, so it can look things up before deciding.
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';import { ConfigService } from '@nestjs/config';import type { Request } from 'express';
@Injectable()export class ApiKeyGuard implements CanActivate { constructor(private readonly config: ConfigService) {}
canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest<Request>(); return request.header('x-api-key') === this.config.getOrThrow<string>('API_KEY'); }}When the guard returns false, Nest throws a ForbiddenException, and the client gets 403 Forbidden. To send a different status, throw it from the guard yourself. An authentication guard usually throws UnauthorizedException, so the client receives 401 and knows to sign in.
The ExecutionContext
The context argument is what separates guards from middleware. It wraps the arguments of the current call and adds two methods:
getHandler()returns the route method that will run, for exampleCatsController.prototype.create.getClass()returns the controller class.
switchToHttp() gives access to the request and response. The same guard can serve other transports: switchToRpc() for microservices, switchToWs() for WebSockets, and GqlExecutionContext.create(context) from @nestjs/graphql for resolvers. context.getType() tells you which one you are in.
Steering a guard with metadata
Because a guard knows the handler, a decorator on that handler can configure it. Reflector.createDecorator() creates a typed decorator and a key to read it back with:
import { Reflector } from '@nestjs/core';
export const RequiresFeature = Reflector.createDecorator<string>();import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';import { Reflector } from '@nestjs/core';import { FeatureFlags } from './feature-flags.service';import { RequiresFeature } from './feature-flag.decorator';
@Injectable()export class FeatureFlagGuard implements CanActivate { constructor( private readonly reflector: Reflector, private readonly flags: FeatureFlags, ) {}
canActivate(context: ExecutionContext): boolean { const feature = this.reflector.getAllAndOverride(RequiresFeature, [ context.getHandler(), context.getClass(), ]); return feature === undefined || this.flags.isEnabled(feature); }}getAllAndOverride() checks the handler first and falls back to the class, so a method-level decorator overrides one on the controller. getAllAndMerge() combines both instead, which suits lists such as roles. The same pattern drives the @Public() and @Roles() decorators in Authentication in NestJS and Authorization in NestJS.
Binding guards
@UseGuards() binds guards to one controller or one route. Pass the class rather than an instance, so Nest creates it through the injector and its dependencies are resolved:
@Controller('cats')@UseGuards(ApiKeyGuard)export class CatsController { @Post('import') @RequiresFeature('bulk-import') @UseGuards(FeatureFlagGuard) import() {}}For a guard that protects everything, register it as a provider with the APP_GUARD token. It then runs for every route in the application, whichever module the provider is declared in:
import { APP_GUARD } from '@nestjs/core';
@Module({ providers: [ { provide: APP_GUARD, useClass: ApiKeyGuard }, { provide: APP_GUARD, useClass: FeatureFlagGuard }, ],})export class AppModule {}Global guards run first, in the order they are registered, then controller guards, then route guards. All of them must pass. A guard cannot let a request through that an earlier guard rejected.
Guards and the rest of the pipeline
Guards run before pipes, so the body has not been validated yet when a guard reads it. Base a decision on headers, the authenticated user, or route parameters, and leave body checks to the pipes. Guards also run before interceptors, so a rejected request never reaches a logging or caching interceptor. If you need to log denied requests, log in the guard or in an exception filter.
Next: Interceptors in NestJS.
Check your understanding
Question 1 of 5
0 of 5 checked