Chapter 09 — Guards (Protecting Routes)
Chapter 09 — Guards (Protecting Routes)
Hey everyone! Welcome back to Namaste NestJS! 🙏
Time to lock the doors. A Guard answers one crisp question: "Is this request allowed to proceed — yes or no?" Return true and the request sails through; return false and Nest slams the door with a 403 Forbidden. Guards are the backbone of authentication and authorization, so this chapter matters a lot.
What we will cover:
- What a guard is and its single job
- Where guards run in the pipeline
- The ExecutionContext — a guard's window into the request
- Writing your first guard (CanActivate)
- Applying guards (method, controller, global)
- Passing metadata with custom decorators (@Roles)
- Reading metadata with the Reflector
- Common mistakes
- Interview Questions
1. What is a Guard?
┌─────────────────────────────────────────────────────────────┐ │ WHAT IS A GUARD? │ ├─────────────────────────────────────────────────────────────┤ │ │ │ A class that decides: "Can this request continue?" │ │ │ │ canActivate() returns: │ │ true → ✅ allow → request proceeds to the handler │ │ false → ❌ block → 403 Forbidden, handler skipped │ │ │ │ Unlike middleware, a guard KNOWS which handler is │ │ about to run (via the ExecutionContext). │ │ │ └─────────────────────────────────────────────────────────────┘
A simple analogy: a guard is the bouncer at a club with a guest list. They know exactly which room you're trying to enter (the handler), check your name/age (your token/roles), and either wave you in or turn you away.
2. Where Guards Run
THE PIPELINE (where guards sit):
─────────────────────────────────
Request → Middleware → ★ GUARDS ★ → Interceptors → Pipes → HANDLER
│
runs AFTER middleware, BEFORE pipes.
Knows the target handler + its metadata.
First checkpoint that can say "you're not allowed."
Note: guards run after middleware but before pipes and interceptors' pre-logic. So a request is authorized before Nest bothers validating its body — sensible, since you shouldn't process data for someone who isn't allowed in.
3. The ExecutionContext
Every guard receives an ExecutionContext — a richer version of the request that knows the target class and handler. This is the superpower middleware lacked.
context.switchToHttp().getRequest() → the request object (headers, user...) context.getHandler() → the METHOD about to run (e.g. findAll) context.getClass() → the CONTROLLER class
In simple words: the ExecutionContext lets a guard read the request and know which endpoint it's guarding — so it can look up per-route rules (like "this route needs admin").
4. Writing Your First Guard
A guard implements CanActivate and its canActivate() method, which returns a boolean (or a Promise/Observable of one).
// src/auth/auth.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const token = request.headers['authorization'];
return isValid(token); // true → allow, false → 403 block
}
}
function isValid(token?: string): boolean {
return !!token && token.startsWith('Bearer '); // toy check; real JWT in Ch 15
}
Attach it with @UseGuards and trace it:
@Controller('cats')
@UseGuards(AuthGuard) // 👈 protect every route in this controller
export class CatsController { /* ... */ }
HAND TRACE: GET /cats with NO Authorization header
──────────────────────────────────────────────────────
1. Middleware runs (e.g. logging)
2. AuthGuard.canActivate() runs
3. token = undefined → isValid() → false
4. Guard returns false
▼
Response: 403 Forbidden (handler never runs) ✔
5. Applying Guards — Three Levels
| Level | How | Scope |
|---|---|---|
| Method | @UseGuards(AuthGuard) above a handler | One route |
| Controller | @UseGuards(AuthGuard) above the class | All routes in it |
| Global | app.useGlobalGuards(new AuthGuard()) | Whole app |
Pro tip: a common real-world setup is a global auth guard that protects everything by default, plus a @Public() decorator to opt specific routes out (like login). Secure-by-default beats remembering to guard each route.
6. Passing Metadata — Custom Decorators (@Roles)
Guards get really powerful when combined with route metadata. Say some routes need an admin role. First, create a decorator that attaches that requirement:
// src/auth/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
// ▲ custom decorator ▲ key ▲ value stored on the route
Now tag routes declaratively:
@Post()
@Roles('admin') // 👈 "only admins may create cats"
create(@Body() dto: CreateCatDto) {
return this.catsService.create(dto);
}
7. Reading Metadata — The Reflector
A guard reads that metadata using the injectable Reflector and decides accordingly.
// src/auth/roles.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {} // 👈 inject the Reflector
canActivate(context: ExecutionContext): boolean {
// read the 'roles' metadata off the target handler
const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler());
if (!requiredRoles) return true; // no @Roles → open route
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user?.roles?.includes(role));
}
}
HAND TRACE: POST /cats by a normal (non-admin) user
─────────────────────────────────────────────────────────
1. RolesGuard runs
2. reflector.get('roles', handler) → ['admin']
3. request.user.roles → ['user']
4. does ['user'] include 'admin'? → NO
5. guard returns false
▼
Response: 403 Forbidden ✔
This pattern — custom decorator + Reflector + guard — is how RBAC (role-based access control) is built in Nest. We use it for real with JWTs in Chapters 15 & 16.
8. Little Traps Beginners Fall Into
┌─────────────────────────────────────────────────────────────┐ │ COMMON MISTAKES ❌ │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Forgetting to return a value from canActivate(). │ │ → undefined is falsy → everything gets blocked (403). │ │ │ │ Doing data validation in a guard. │ │ → Guards decide ALLOW/BLOCK. Validation → pipes. │ │ │ │ Registering a global guard with `new RolesGuard()` when │ │ it needs the injected Reflector. │ │ → Register it as a provider (APP_GUARD) so DI works. │ │ │ │ Expecting a 401 when you return false → it's a 403. │ │ → Throw UnauthorizedException yourself for a 401. │ │ │ └─────────────────────────────────────────────────────────────┘
Interview Questions — Quick Fire!
Q: What is a guard in NestJS?
"A guard is a class implementing CanActivate that decides whether a request may proceed to its handler. Its canActivate method returns true to allow the request or false to block it with a 403. Guards are the standard mechanism for authentication and authorization."
Q: How is a guard different from middleware?
"Both run before the handler, but a guard runs after middleware and receives an ExecutionContext, so it knows which controller and handler are targeted and can read their metadata. Middleware runs earlier and doesn't know the target route. That handler-awareness is why authorization belongs in guards, not middleware."
Q: What is the ExecutionContext?
"It's an object passed to guards and interceptors that provides access to the current request as well as the target handler and controller class via getHandler and getClass. This lets a guard combine request data with route metadata to make a decision."
Q: How do you implement role-based access control?
"You create a custom decorator using SetMetadata — for example @Roles('admin') — to attach required roles to a route. Then a guard injects the Reflector, reads that metadata off the handler, compares it against the authenticated user's roles, and returns true or false accordingly."
Q: At what levels can guards be applied?
"At the method level with @UseGuards on a handler, at the controller level with @UseGuards on the class, or globally with app.useGlobalGuards or by providing an APP_GUARD. A global guard that needs injected dependencies like the Reflector should be registered via APP_GUARD so dependency injection works."
Key Points to Remember
| Concept | Key Takeaway |
|---|---|
| Guard | Returns true/false to allow or block a request (403). |
| CanActivate | The interface; implement canActivate(context). |
| ExecutionContext | Knows the request AND the target handler/class. |
| @UseGuards | Apply at method, controller, or global level. |
| Metadata + Reflector | @Roles(...) sets metadata; Reflector reads it → RBAC. |
| Global guard with DI | Register via APP_GUARD so injection works. |
What's Next?
Guards decide if a request runs. But what about wrapping logic around the handler — measuring how long it took, reshaping every response into a standard envelope, or caching results? That's the job of Interceptors, Nest's take on Aspect-Oriented Programming. Chapter 10 shows their before-and-after superpower.
Keep coding, keep shipping! See you in the next one!
Post a Comment