Guards determine whether a request is allowed to proceed to the route handler — they're NestJS's dedicated mechanism for authorization (and often authentication checks). A guard returns true to permit the request or false/throws to block it.
Guards determine whether a request is allowed to proceed to the route handler — they're NestJS's dedicated mechanism for authorization (and often authentication checks). A guard returns true to permit the request or false/throws to block it.
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
return !!request.headers.authorization; // true = allow, false = block (403)
}
}
A guard implements canActivate(), which returns a boolean (or Promise/Observable of one). Returning false blocks the request with a 403; you can also throw a specific exception (e.g. UnauthorizedException).
@UseGuards(AuthGuard) // on a single route
@Get("profile")
getProfile() {}
@UseGuards(AuthGuard) // on a whole controller (all routes)
@Controller("admin")
export class AdminController {}
app.useGlobalGuards(new AuthGuard()); // globally (all routes)
Guards can be applied at the method, controller, or global level.
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = request.headers.authorization?.split(" ")[1];
if (!token) throw new UnauthorizedException();
try {
request.user = await this.jwtService.verifyAsync(token); // attach the user
return true;
} catch {
throw new UnauthorizedException("Invalid token");
}
}
}
// a @Roles decorator stores required roles as metadata
@Roles("admin")
@Get("users")
listUsers() {}
// the RolesGuard reads the metadata and checks the user's roles
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(ctx: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>("roles", ctx.getHandler());
const { user } = ctx.switchToHttp().getRequest();
return roles.some(role => user.roles?.includes(role)); // allow if the user has a required role
}
}
Combining a @Roles() decorator (metadata) with a guard that reads it (via Reflector) is the standard pattern for role-based access control.
The ExecutionContext gives guards rich info (the handler, class, request) — more
than plain middleware — which is why guards (not middleware) are the right place
for authorization decisions.
Guards are the dedicated, idiomatic NestJS mechanism for authorization — controlling which requests are allowed to reach handlers based on authentication and permissions.
Understanding them is essential because virtually every real application needs to protect routes (require login, check roles/permissions), and guards are the correct tool for this (far cleaner than cramming auth logic into middleware or each handler).
The standard patterns — a JWT/auth guard that verifies credentials and attaches the user to the request, and role-based guards combining a @Roles() decorator with a Reflector-reading guard for RBAC — are fundamental to securing NestJS APIs.
Guards' access to the rich ExecutionContext (knowing the target handler, class, and request) is precisely what makes them suited to authorization decisions that middleware can't cleanly make.
Knowing how to write, apply (method/controller/global), and combine guards with metadata decorators is core knowledge for building secure NestJS applications and a frequent, architecturally-important topic in the framework.