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).
  • false → Nest throws ForbiddenException (403 Forbidden).
  • A thrown exception → caught by exception filters, same as anywhere else.

It can return synchronously, as a Promise, or as an RxJS Observable.

Generate with the CLI

nest generate guard roles      # full form
nest g gu roles                # short alias → src/roles/roles.guard.ts
nest g gu roles --flat         # no wrapping folder → src/roles.guard.ts
nest g gu auth/jwt             # nested path → src/auth/jwt/jwt.guard.ts
nest g gu auth/jwt --flat      # nested + flat → src/auth/jwt.guard.ts
nest g gu roles --no-spec      # skip the *.spec.ts test file
nest 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.

Why a guard, not middleware

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.

GuardPackagePurpose
AuthGuard(strategy)@nestjs/passportBridge to a Passport strategy ('jwt', 'local', 'oauth2', …). See the IS_PUBLIC recipe below and JWT strategy recipe
ThrottlerGuard@nestjs/throttlerRate limiting per route or controller

Anything else you write yourself. The canonical example is a RolesGuard, covered below.

ExecutionContext essentials

ExecutionContext extends ArgumentsHost and adds two methods that make guards reusable across handlers.

MethodReturns
getHandler()The handler Function about to run (e.g. CatsController.prototype.create)
getClass()The controller Type (CatsController, not an instance)
switchToHttp()HttpArgumentsHostgetRequest(), getResponse(), getNext()
switchToRpc()RPC context (microservices)
switchToWs()WebSocket context
getType()'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

ScopeHowDI access
Globalapp.useGlobalGuards(new AuthGuard()) or the APP_GUARD provider (preferred)Provider only
Controller@UseGuards(AuthGuard) on the classYes
Route@UseGuards(AuthGuard) on the methodYes

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() {}
}

The global-scope variant of the same DI question — useGlobalGuards(new X()) vs APP_GUARD — has its own dedicated note: Global pipes, guards, interceptors, and filters via DI. See in particular the worked example of a guard that injects a request-scoped service and the side-by-side comparison of useGlobalGuards vs APP_GUARD.

Order

Multiple guards run in this order:

  1. Global guards (registration order).
  2. Controller guards (left-to-right inside @UseGuards()).
  3. Route guards (left-to-right inside @UseGuards()).

The chain stops at the first guard that returns false, throws, or rejects a returned Promise — later guards do not run.

import { Controller, Get, UseGuards } from "@nestjs/common"
 
@UseGuards(Guard1, Guard2)
@Controller("cats")
export class CatsController {
  @UseGuards(Guard3)
  @Get()
  list() {}
}
// Execution: Guard1 → Guard2 → Guard3 (then interceptors/pipes/handler)

In the request lifecycle, all guards run after middleware and before any interceptor or pipe.

Reflector and custom decorators

The point of a guard is to make per-route decisions, which means reading per-route metadata. Reflector (from @nestjs/core) is the bridge.

Strongly-typed decorators with Reflector.createDecorator

// roles.decorator.ts
import { Reflector } from "@nestjs/core"
 
export const Roles = Reflector.createDecorator<string[]>()
// cats.controller.ts
import { 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.ts
import { 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

MethodWhen 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() placementgetAllAndOverride(Roles, [handler, class]) returns
@Roles('admin') on class only['admin'] (handler empty, falls through to class)
@Roles('user') on handler, @Roles('admin') on class['user'] (handler wins, class never read)
Neitherundefined

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:

import { SetMetadata } from "@nestjs/common"
 
export const Roles = (...roles: string[]) => SetMetadata("roles", roles)
// Read with: this.reflector.get<string[]>("roles", ctx.getHandler())

Common recipes

Common errors

SymptomLikely cause
403 Forbidden resource on every requestGuard returns false (or undefined — falsy). Check canActivate actually returns true
Global guard’s injected provider is undefinedRegistered via useGlobalGuards(new X()) instead of APP_GUARD provider
Reflector returns undefined for known decoratorLooking up the wrong target — used getHandler() when the metadata is on the class (getClass())
Controller-level metadata ignoredUsed reflector.get(d, ctx.getHandler()) instead of getAllAndOverride(d, [getHandler(), getClass()])
Guard runs but request.user is undefinedAuthentication middleware/guard didn’t run first, or no upstream layer attached user
Guard doesn’t run for WebSocket/microservice handlerWrong context — verify with ctx.getType() and use switchToWs() / switchToRpc() for the right transport
cannot read property of undefined inside guardCalling switchToHttp() in a non-HTTP context. Branch on ctx.getType() for cross-transport guards

Gotchas

When to reach for it

  • Role, permission, or ACL checks.
  • Ownership checks (“can this user touch this resource?”).
  • Feature flags that should hard-block a route.
  • Anything that must short-circuit the request before validation, transformation, or DB work.

When not to

  • Mutating the raw request, attaching correlation IDs: use middleware.
  • Validating or coercing input shape: use a pipe.
  • Logging, caching, or wrapping the handler with timing: use an interceptor.
  • Turning a thrown error into an HTTP response: that’s an exception filter — the guard’s job ends at “throw”.

See also