Guards
A guard is a class that decides whether a controller method may be called for this request. It returns true — the request goes on, false — the client gets a 403 response. Guards are used for access checks: is the user logged in, do they have the right role.
A simple guard
Let creating posts require a secret key in the x-api-key header. Real password login comes in chapter five; a key is enough for now.
nest g guard common/guards/api-key --flat// src/common/guards/api-key.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Request } from 'express';
@Injectable()
export class ApiKeyGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest<Request>();
return request.headers['x-api-key'] === 'secret-key';
}
}canActivate() receives an ExecutionContext — information about the current request. Through it the guard gets the Express request object and reads the header.
Applying it to a method
// src/posts/posts.controller.ts
import { Body, Controller, Post, UseGuards } from '@nestjs/common';
import { ApiKeyGuard } from '../common/guards/api-key.guard';
import { CreatePostDto } from './dto/create-post.dto';
import { PostsService } from './posts.service';
@Controller('posts')
export class PostsController {
constructor(private readonly postsService: PostsService) {}
@Post()
@UseGuards(ApiKeyGuard)
create(@Body() dto: CreatePostDto) {
return this.postsService.create(dto);
}
}Send a POST request without the header — you get 403 Forbidden resource. With the header the post is created:
curl -X POST http://localhost:3000/posts \
-H "Content-Type: application/json" \
-H "x-api-key: secret-key" \
-d '{"title":"Guards","content":"Access check"}'Where to put a guard
| Where | How | Applies to |
|---|---|---|
| Method | @UseGuards(ApiKeyGuard) above the method | One route |
| Controller | @UseGuards(ApiKeyGuard) above the class | All routes of the controller |
| Application | An APP_GUARD provider in a module | All routes |
A global guard is registered in any module:
// src/app.module.ts
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { ApiKeyGuard } from './common/guards/api-key.guard';
@Module({
providers: [{ provide: APP_GUARD, useClass: ApiKeyGuard }],
})
export class AppModule {}Registered this way, the guard can receive services through its constructor — you will need that for token login.
Your own error code
Instead of returning false, a guard can throw an exception. The client then gets its code and text:
// src/common/guards/api-key.guard.ts
import { UnauthorizedException } from '@nestjs/common';
if (request.headers['x-api-key'] !== 'secret-key') {
throw new UnauthorizedException('Invalid API key');
}
return true;Code 401 means "you did not identify yourself", 403 means "you did, but you have no access".
Common mistake
A guard runs after all middleware but before pipes. So the guard has no validated DTO yet: the request body there is raw data. Do not rely on body fields in a guard; check headers and the user there, and validate data in the DTO.