Skip to content
NestJS ns guards 4 min read

Guards Overview

A guard is a class with a single responsibility: decide whether a given request is allowed to reach its route handler. Guards are NestJS’s answer to authorization — they answer the question “is this caller permitted to do this?” — and they do it with full knowledge of the runtime context, including the handler being invoked and the controller it belongs to. Because they run after middleware but before pipes and interceptors, a guard can reject a request early, with the right HTTP status, before any expensive work happens.

What a guard is

Every guard implements the CanActivate interface, which requires exactly one method: canActivate. Nest calls this method for each incoming request that the guard is bound to, and inspects the return value. If it resolves to a truthy value, the request proceeds down the pipeline; if it resolves to a falsy value, Nest throws a ForbiddenException automatically and the handler never runs.

Guards are @Injectable() providers, so they participate fully in dependency injection. That is the key difference from middleware: a guard can inject a JwtService, a Reflector, a repository, or any other provider, and it receives a rich ExecutionContext rather than the raw platform req/res.

// auth.guard.ts
import {
  CanActivate,
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { Request } from 'express';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest<Request>();
    const token = request.headers.authorization?.replace('Bearer ', '');

    if (!token || !this.isValid(token)) {
      throw new UnauthorizedException('Missing or invalid token');
    }
    return true;
  }

  private isValid(token: string): boolean {
    // Replace with real verification (e.g. JwtService.verify).
    return token.length > 10;
  }
}

The CanActivate return type

canActivate is intentionally flexible about what it returns. The return type is boolean | Promise<boolean> | Observable<boolean>, which lets a guard be synchronous, asynchronous, or stream-based depending on what its decision needs.

Return valueWhen to use it
booleanThe decision is purely in-memory (e.g. checking a header or role array)
Promise<boolean>The decision needs await — verifying a JWT, querying a database
Observable<boolean>The decision comes from an RxJS source, such as an HTTP call via HttpService

Returning false (or resolving/emitting false) raises a ForbiddenException. If you need a different status — for example 401 Unauthorized instead of 403 Forbidden — throw the exception yourself, as the example above does.

// roles.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';

@Injectable()
export class RolesGuard implements CanActivate {
  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const user = request.user; // attached upstream by an auth guard

    // Asynchronous policy lookup — fully awaited before the decision.
    const allowed = await Promise.resolve(user?.roles?.includes('admin'));
    return Boolean(allowed);
  }
}

Throwing a specific exception (UnauthorizedException, ForbiddenException) gives clients an accurate status code and message. Returning a bare false always produces a generic 403, which is rarely what you want for authentication failures.

Where guards sit in the lifecycle

Guards execute after all middleware has run and before interceptors and pipes. By the time a guard runs, Nest knows precisely which controller and handler the request is routed to, which is exactly why guards — and not middleware — are the correct place for authorization.

Incoming request
  -> Middleware            (raw req/res, no route metadata)
  -> Guards                (CanActivate — authorization decision)
  -> Interceptors (pre)
  -> Pipes                 (validation / transformation)
  -> Route handler
  -> Interceptors (post)
  -> Exception filters     (catch thrown errors)

This ordering matters: middleware cannot read route metadata, so it cannot know which roles a handler requires. A guard receives the ExecutionContext, from which it can pull the handler and class references and read decorator metadata via the Reflector.

Binding a guard

Guards can be applied at three scopes. Method and controller scope use the @UseGuards() decorator; global scope is registered once for the whole app.

// cats.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';

@Controller('cats')
@UseGuards(AuthGuard) // applies to every handler in this controller
export class CatsController {
  @Get()
  findAll() {
    return ['Tom', 'Felix'];
  }
}

To register a guard globally so it protects every route, bind it as a provider with the APP_GUARD token — this keeps the guard inside the DI container so it can still inject dependencies:

// app.module.ts
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth.guard';

@Module({
  providers: [{ provide: APP_GUARD, useClass: AuthGuard }],
})
export class AppModule {}

Best Practices

  • Implement CanActivate for one decision per guard — keep authentication, role checks, and policy evaluation in separate, composable guards.
  • Use the ExecutionContext (context.switchToHttp().getRequest()) rather than reaching for platform globals, so the same guard works across transports.
  • Throw UnauthorizedException for missing/invalid credentials and ForbiddenException for insufficient permissions, instead of returning a bare false.
  • Register cross-cutting guards with APP_GUARD so they stay in the DI container and can inject Reflector, services, and config.
  • Prefer async/Promise<boolean> when a decision touches I/O — never block the event loop with synchronous network or database calls.
  • Pair guards with custom metadata (e.g. a @Roles() decorator read via Reflector) so handlers declare their requirements declaratively.
Last updated June 14, 2026
Was this helpful?