Skip to content

NestJS Guards

Guards determine whether a given request should be handled by the route handler at all, most commonly used for authentication and authorization checks. This lesson covers the CanActivate interface and applying guards at different scopes.

What Is a Guard?

A guard is a class implementing the CanActivate interface with a single canActivate() method that returns a boolean (or a Promise/Observable resolving to one). If it returns true, the request proceeds; if false, Nest automatically throws a 403 Forbidden.

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';

@Injectable()
export class ApiKeyGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    return request.headers['x-api-key'] === process.env.API_KEY;
  }
}

context.switchToHttp().getRequest() gives the guard access to the underlying request regardless of whether the app uses Express or Fastify.

Applying Guards at Different Scopes

@UseGuards(ApiKeyGuard)
@Get()
findAll() {}

@UseGuards(ApiKeyGuard)
@Controller('cats')
export class CatsController {}

app.useGlobalGuards(new ApiKeyGuard());
  • @UseGuards() can be applied to a single route, an entire controller, or globally.
  • Guards run after middleware but before interceptors and pipes.
  • Multiple guards can be combined, all must return true for the request to proceed.
  • Globally registered guards via useGlobalGuards() cannot use dependency injection directly, register them as providers instead for DI support.

Guards Cheatsheet

How and where to apply NestJS guards.

Scope Syntax
Route-level @UseGuards(SomeGuard) above a method
Controller-level @UseGuards(SomeGuard) above a class
Global (in main.ts) app.useGlobalGuards(new SomeGuard())
Global (DI-enabled) { provide: APP_GUARD, useClass: SomeGuard }

Registering a Global Guard With Dependency Injection

Calling app.useGlobalGuards() directly in main.ts bypasses Nest's DI container for that guard's own dependencies. Registering the guard as a provider using the APP_GUARD token instead keeps it fully DI-enabled.

import { APP_GUARD } from '@nestjs/core';

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

Guards vs. Middleware

Unlike middleware, guards have access to the full ExecutionContext, including route handler metadata set by custom decorators (like @Roles()), making them the right tool for authorization decisions that depend on route-specific configuration.

Common Mistakes

  • Putting authorization logic in middleware instead of a guard, losing access to route metadata.
  • Registering a global guard with useGlobalGuards() when it needs injected dependencies.
  • Forgetting that a guard returning false produces a generic 403, not a custom error message, without extra handling.
  • Stacking too many unrelated checks into a single guard instead of composing smaller, focused guards.

Key Takeaways

  • Guards implement CanActivate and decide whether a request is allowed to proceed.
  • Guards can be applied at the route, controller, or global level.
  • Global guards needing dependency injection should be registered via the APP_GUARD token.
  • Guards have access to route metadata, making them ideal for role- and permission-based checks.

Pro Tip

Keep each guard focused on one concern (is the user authenticated? does the user have this role?) and compose multiple small guards with @UseGuards(AuthGuard, RolesGuard) rather than writing one large guard that does everything.