typescript 50 lines · 8 steps

Role-based route guards in NestJS

A custom decorator tags routes with required roles and a guard reads that metadata to allow or forbid each request.

Explained by highlit
1import {
2 CanActivate,
3 ExecutionContext,
4 Injectable,
5 SetMetadata,
6 ForbiddenException,
7} from '@nestjs/common';
8import { Reflector } from '@nestjs/core';
9 
10export enum Role {
11 User = 'user',
12 Editor = 'editor',
13 Admin = 'admin',
14}
15 
16export const ROLES_KEY = 'roles';
17export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);
18 
19@Injectable()
20export class RolesGuard implements CanActivate {
21 constructor(private readonly reflector: Reflector) {}
22 
23 canActivate(context: ExecutionContext): boolean {
24 const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
25 context.getHandler(),
26 context.getClass(),
27 ]);
28 
29 if (!requiredRoles || requiredRoles.length === 0) {
30 return true;
31 }
32 
33 const { user } = context.switchToHttp().getRequest();
34 
35 if (!user) {
36 throw new ForbiddenException('Authentication required');
37 }
38 
39 const userRoles: Role[] = user.roles ?? [];
40 const hasRole = requiredRoles.some((role) => userRoles.includes(role));
41 
42 if (!hasRole) {
43 throw new ForbiddenException(
44 `Requires one of: ${requiredRoles.join(', ')}`,
45 );
46 }
47 
48 return true;
49 }
50}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Custom metadata plus a reflector lets you attach declarative rules to routes and read them back at request time.
  2. 2getAllAndOverride merges handler and class metadata so route-level roles override controller-level defaults.
  3. 3Returning true for unrestricted routes keeps the guard opt-in rather than locking everything down by default.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Role-based route guards in NestJS — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code