Decide whether a request reaches the route handler. Used for authorization: roles, permissions, ownership, anything that should short-circuit before the handler runs.
Signature
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"import { Request } from "express"import { Observable } from "rxjs"@Injectable()export class AuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> { const request = context.switchToHttp().getRequest<Request>() return Boolean(request.headers.authorization) }}
canActivate returns:
true → continue to the next layer (interceptors then pipes then handler).
A thrown exception → caught by exception filters, same as anywhere else.
It can return synchronously, as a Promise, or as an RxJS Observable.
Return shapes: one example each
The same contract (“give me something that resolves to a boolean”) is expressed three ways so guards stay idiomatic regardless of how the decision is computed. Nest awaits whichever shape you return before deciding to proceed or throw ForbiddenException.
boolean: synchronous, in-memory check. No I/O, no reason to pay the microtask cost of a Promise.
Promise<boolean>: anything async. The common case: verify a JWT, hit the DB, call an auth service.
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"import { Request } from "express"import { UsersService } from "./users.service"@Injectable()export class TokenGuard implements CanActivate { constructor(private readonly users: UsersService) {} async canActivate(ctx: ExecutionContext): Promise<boolean> { const req = ctx.switchToHttp().getRequest<Request & { user?: unknown }>() const token = req.headers.authorization?.replace(/^Bearer\s+/i, "") if (!token) return false const user = await this.users.findByToken(token) if (!user) return false req.user = user return true }}
Observable<boolean>: the source is already a stream. HttpService returns Observable<AxiosResponse>; gRPC clients return Observables; an RxJS-based cache lookup. Return the stream directly instead of bridging with firstValueFrom. Nest subscribes, takes the first emitted value, and uses it.
Why a union and not just Promise<boolean>? Forcing every guard to return a Promise would add a tick to every request even for trivial checks. Accepting Observable<boolean> means RxJS-native sources (HTTP, gRPC, WebSocket, microservices) don’t need a paradigm bridge. Other Nest constructs in the request pipeline use their own, different unions — see each note for the exact signature: interceptors, pipes, middleware.
Generate with the CLI
nest generate guard roles # full formnest g gu roles # short alias → src/roles/roles.guard.tsnest g gu roles --flat # no wrapping folder → src/roles.guard.tsnest g gu auth/jwt # nested path → src/auth/jwt/jwt.guard.tsnest g gu auth/jwt --flat # nested + flat → src/auth/jwt.guard.tsnest g gu roles --no-spec # skip the *.spec.ts test filenest g gu roles --dry-run # preview the file plan, write nothing
Creates <name>.guard.ts (and <name>.guard.spec.ts unless --no-spec). The nest CLI wraps the file in a folder named after the element by default; pass --flat to drop it directly in the target path. Source: @nestjs/cli generate command, Nest CLI usages.
Both run before the handler, but middleware is “dumb”: it doesn’t know which handler will execute next. A guard receives an ExecutionContext and can read route metadata (@Roles(), @Public(), etc.) plus the controller class and handler reference. That is what makes role/permission decisions declarative. See Guards intro.
Built-in guards
Nest core ships none. Authorization is application-specific, so you write your own — or pull one from a peer package.
'http' | 'rpc' | 'ws' (or 'graphql' with @nestjs/graphql)
getHandler() and getClass() are the keys that Reflector uses to read decorator metadata. Source: Execution context.
Binding
Scope
How
DI access
Global
app.useGlobalGuards(new AuthGuard()) or the APP_GUARD provider (preferred)
Provider only
Controller
@UseGuards(AuthGuard) on the class
Yes
Route
@UseGuards(AuthGuard) on the method
Yes
Controller- and route-scoped bindings always resolve through Nest’s DI container when you pass the class (@UseGuards(RolesGuard)). Pass an instance (@UseGuards(new RolesGuard())) and DI is bypassed.
Pass the class, not an instance
@UseGuards(RolesGuard) is resolved by Nest’s DI container, so the guard’s constructor injections (Reflector, repositories, config services, anything else) are wired up. @UseGuards(new RolesGuard()) looks identical at the call site but skips DI entirely: the guard runs with undefined dependencies and fails the first time it touches this.reflector. Same trap applies to @UseInterceptors, @UsePipes, and @UseFilters. Pass the class unless you genuinely need a pre-configured instance with no DI needs.
import { Controller, Get, UseGuards } from "@nestjs/common"import { RolesGuard } from "./roles.guard"@UseGuards(RolesGuard)@Controller("cats")export class CatsController { @Get() list() {}}
// cats.controller.tsimport { Body, Controller, Post } from "@nestjs/common"import { Roles } from "./roles.decorator"import { CreateCatDto } from "./create-cat.dto"@Controller("cats")export class CatsController { @Post() @Roles(["admin"]) create(@Body() dto: CreateCatDto) {}}
// roles.guard.tsimport { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"import { Reflector } from "@nestjs/core"import { Request } from "express"import { Roles } from "./roles.decorator"@Injectable()export class RolesGuard implements CanActivate { constructor(private readonly reflector: Reflector) {} canActivate(ctx: ExecutionContext): boolean { const required = this.reflector.getAllAndOverride(Roles, [ctx.getHandler(), ctx.getClass()]) if (!required?.length) return true // no @Roles() → public const request = ctx.switchToHttp().getRequest<Request & { user?: { roles: string[] } }>() const user = request.user return Boolean(user?.roles?.some((r) => required.includes(r))) }}
Routes without @Roles() are treated as public: getAllAndOverride returns undefined when no metadata is found at handler or class level, so the guard short-circuits to true.
Reflector lookup methods
Method
When to use
get(decorator, target)
Single target — handler or class. Returns undefined if absent
getAllAndOverride(d, [..])
Multiple targets, first non-empty wins. Use when route metadata should override controller defaults
getAllAndMerge(d, [..])
Multiple targets, merge arrays/objects. Use when you want both controller and route metadata combined
The target list [ctx.getHandler(), ctx.getClass()] is the conventional order: handler first, controller second, so route-level metadata overrides controller-level. Source: Reflection and metadata.
“First non-empty wins” means getAllAndOverride walks the targets in order and returns the value from the first one that has the metadata defined. Targets without it are skipped, later targets are never consulted, and nothing is merged. Concretely:
@Roles('user') on handler, @Roles('admin') on class
['user'] (handler wins, class never read)
Neither
undefined
Use getAllAndMerge instead when you want the union (e.g. ['user', 'admin'] for the second row).
Low-level @SetMetadata
Reflector.createDecorator is the recommended path. @SetMetadata('roles', [...]) is the older string-keyed alternative — fine for one-off cases, but loses type safety:
Pattern: register a global JwtAuthGuard, then mark public routes with @Public() so the guard skips them.
// public.decorator.tsimport { SetMetadata } from "@nestjs/common"export const IS_PUBLIC_KEY = "isPublic"export const Public = () => SetMetadata(IS_PUBLIC_KEY, true)
// jwt-auth.guard.tsimport { ExecutionContext, Injectable } from "@nestjs/common"import { Reflector } from "@nestjs/core"import { AuthGuard } from "@nestjs/passport"import { IS_PUBLIC_KEY } from "./public.decorator"@Injectable()export class JwtAuthGuard extends AuthGuard("jwt") { constructor(private readonly reflector: Reflector) { super() } canActivate(ctx: ExecutionContext) { const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [ ctx.getHandler(), ctx.getClass(), ]) if (isPublic) return true return super.canActivate(ctx) }}
Register JwtAuthGuard via APP_GUARD and decorate exempt routes with @Public(). Pattern from the official Authentication recipe.
Throw a custom exception instead of the default ForbiddenException
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common"@Injectable()export class TokenGuard implements CanActivate { canActivate(ctx: ExecutionContext): boolean { const request = ctx.switchToHttp().getRequest() if (!request.headers.authorization) { throw new UnauthorizedException("Missing bearer token") } return true }}
Returning false always produces 403. Throw an explicit exception when the semantically correct status is something else (401, 429, etc.). The thrown error flows through the normal exception filter chain.
Ownership check using route params
import { CanActivate, ExecutionContext, ForbiddenException, Injectable } from "@nestjs/common"@Injectable()export class CatOwnerGuard implements CanActivate { async canActivate(ctx: ExecutionContext): Promise<boolean> { const req = ctx.switchToHttp().getRequest() const userId: string | undefined = req.user?.id const catId: string = req.params.id if (!userId) throw new ForbiddenException() // …query DB; return true/false based on ownership return true }}
Registered via useGlobalGuards(new X()) instead of APP_GUARD provider
Reflector returns undefined for known decorator
Looking up the wrong target — used getHandler() when the metadata is on the class (getClass())
Controller-level metadata ignored
Used reflector.get(d, ctx.getHandler()) instead of getAllAndOverride(d, [getHandler(), getClass()])
Guard runs but request.user is undefined
Authentication middleware/guard didn’t run first, or no upstream layer attached user
Guard doesn’t run for WebSocket/microservice handler
Wrong context — verify with ctx.getType() and use switchToWs() / switchToRpc() for the right transport
cannot read property of undefined inside guard
Calling switchToHttp() in a non-HTTP context. Branch on ctx.getType() for cross-transport guards
Gotchas
Returning false always yields 403, never 401
Nest converts false into ForbiddenException. If the route is unauthenticated (no token) the correct status is 401 Unauthorized — throw new UnauthorizedException() instead of returning false. See the official putting it all together note.
useGlobalGuards() skips microservice/WebSocket gateways in hybrid apps
Same trap, same fix as the other lifecycle components. Use APP_GUARD or pass { inheritAppConfig: true } to connectMicroservice. Full explanation in Global providers > Hybrid apps gotcha.
Guards run after middleware
If your authentication logic lives in middleware, it runs first and can attach request.user before the guard reads it. The opposite is impossible: a guard cannot mutate the request in time for middleware. If both layers need shared context, decide which one owns it.
Cross-transport guards: branch on ctx.getType()
The same guard class can run on HTTP, RPC, and WebSocket handlers, but the request shape differs. Use ctx.getType() to switch: