Decide Whether a Request Continues
Guards — Allow or Deny
A guard returns a boolean (or throws). True means continue; false means 403. They're how you enforce auth and authorization in Nest.
What you'll learn
- Implement the CanActivate interface
- Apply guards with @UseGuards
- Inject services and access the request inside a guard
Where pipes ask “is this argument valid?”, guards ask “should this request
even be running?”. They run before pipes and the handler, get the full
ExecutionContext, and either say yes or short-circuit with a 403.
A Minimal Guard
Implement CanActivate. Return true to continue, false to forbid, or
throw a specific exception.
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
@Injectable()
export class ApiKeyGuard implements CanActivate {
canActivate(ctx: ExecutionContext): boolean {
const req = ctx.switchToHttp().getRequest();
return req.headers['x-api-key'] === process.env.API_KEY;
}
} Applying a Guard
Attach with @UseGuards. It works at controller scope (every route in the
controller) or method scope (a single route).
@Controller('admin')
@UseGuards(ApiKeyGuard) // applies to every route below
export class AdminController {
@Get('stats')
stats() { return this.svc.stats(); }
@Post('purge')
@UseGuards(SuperAdminGuard) // stacks: both must pass
purge() { return this.svc.purge(); }
} For app-wide rules, use app.useGlobalGuards(...) in main.ts.
Injecting Services
A guard is just an @Injectable(), so it can inject anything — repositories,
config, a JWT verifier.
@Injectable()
export class TenantGuard implements CanActivate {
constructor(private readonly tenants: TenantsService) {}
async canActivate(ctx: ExecutionContext): Promise<boolean> {
const req = ctx.switchToHttp().getRequest();
const tenant = await this.tenants.findByDomain(req.hostname);
if (!tenant) return false;
req.tenant = tenant; // attach for downstream use
return true;
}
} Notice that canActivate can be async — return a Promise<boolean>
or an Observable<boolean> and Nest waits.
Throwing for Better Errors
Returning false produces a generic ForbiddenException. Throw your own
exception when you want a specific status code or message.
import { UnauthorizedException } from '@nestjs/common';
canActivate(ctx: ExecutionContext): boolean {
const req = ctx.switchToHttp().getRequest();
if (!req.headers.authorization) {
throw new UnauthorizedException('missing bearer token');
}
return verify(req.headers.authorization);
} Where Guards Run
Pipeline order, top to bottom: middleware → guards → interceptors (pre) → pipes → handler. Guards see the request after middleware has had a chance to mutate it (so cookies are parsed, the user is attached) and before validation runs. That’s the right point to gate access.
The next lesson focuses on the most common guard of all: auth.
Auth Guards →