Lesson 17 of 5022 min read

NestJS Guards Tutorial – Secure Your API Routes

Learn how NestJS guards control access to routes using the CanActivate interface, and how to build authentication and role-based guards.

Author: CodersNexus

NestJS Guards Tutorial – Secure Your API Routes

Not every route in your API should be accessible to everyone. Some routes require the request to come from an authenticated user, and some require that authenticated user to have a specific role or permission. Guards are NestJS's dedicated mechanism for answering exactly one question before a route handler ever runs: is this request allowed to proceed at all?

This lesson explains what guards are, how the CanActivate interface works, and walks through building both a simple authentication guard and a role-based authorization guard, two of the most common and interview-relevant guard patterns you will encounter.

NestJS Guards: Learning Objectives

  • Define what a guard is and the specific question it answers in the request lifecycle.
  • Understand the CanActivate interface and its canActivate() method.
  • Build a simple authentication guard that checks for a valid token.
  • Build a role-based guard using custom metadata and the Reflector class.
  • Apply guards at the method, controller, or global level.

NestJS Guards: Key Terms and Definitions

  • Guard: A class implementing the CanActivate interface, responsible for determining whether a given request is allowed to proceed to its route handler.
  • CanActivate interface: The contract a guard must implement, requiring a canActivate() method that returns a boolean (or a Promise/Observable of one).
  • ExecutionContext: An object passed to a guard's canActivate() method, providing access to details about the current request, such as the underlying HTTP request object.
  • Reflector: A NestJS utility class used to read custom metadata attached to route handlers, commonly used by guards to implement role-based access control.
  • Custom decorator (metadata): A decorator like @Roles('admin') used to attach arbitrary metadata to a route handler, which a guard can later read using Reflector.

How NestJS Guards Works: Detailed Explanation

A guard in NestJS is a class implementing the CanActivate interface, which requires a single method: canActivate(context: ExecutionContext). This method must return true if the request should be allowed to proceed, or false (or throw an exception) if it should be blocked. Guards run after middleware but before interceptors and pipes, making them the appropriate place to answer authorization-related questions before any further request processing, including data transformation and validation, takes place.

The most fundamental guard pattern is an authentication guard, which checks whether a request carries valid credentials, typically a bearer token in the Authorization header. Inside canActivate(), the guard extracts the underlying HTTP request from the provided ExecutionContext using context.switchToHttp().getRequest(), inspects the relevant header, and either confirms the token is valid (often by verifying a JWT) and returns true, or throws an UnauthorizedException if the token is missing or invalid.

A more advanced and extremely common pattern in real applications is role-based access control, where different routes require the authenticated user to have a specific role, such as 'admin' or 'editor'. This is typically implemented using a combination of a custom decorator and NestJS's Reflector class. First, you create a simple custom decorator, commonly named @Roles(...roles: string[]), that attaches an array of allowed roles as metadata onto a specific route handler. Then, inside a RolesGuard's canActivate() method, you use the injected Reflector to read that metadata back off the current route handler, compare it against the roles present on the authenticated user (usually attached to the request by an earlier authentication step), and return true or false accordingly.

Guards can be applied at several levels of scope, exactly like pipes and exception filters. Applied to a single route handler using @UseGuards(AuthGuard), only that specific route is protected. Applied to an entire controller class, every route within it is protected. Applied globally in main.ts using app.useGlobalGuards(), every route across the entire application requires the guard's approval to proceed, which is a common pattern for a baseline authentication guard, often combined with an explicit @Public() decorator to selectively exempt specific routes like a login endpoint that must remain accessible without prior authentication.

Interview-Friendly Explanation

A strong interview or viva answer for nestjs guards must go beyond a one-line definition. Interviewers evaluating BCA, MCA, B.Tech, and self-taught backend developers reward answers that combine a precise definition, a clear working explanation, a real-world example, and a conclusion that highlights why it matters in production Node.js applications. Use the four-point framework below when answering under time pressure.

  • Definition point: Guard: A class implementing the CanActivate interface, responsible for determining whether a given request is allowed to proceed to its route handler.
  • Working point: Understand the CanActivate interface and its canActivate() method.
  • Example point: Virtually every production API with user accounts implements some version of the AuthGuard pattern shown here, verifying a JWT or session token before allowing access to protected routes.
  • Conclusion point: Guards exist to answer exactly one question: should this request be allowed to happen at all?

How to Answer This in a Technical Interview

  1. Give a two-to-three sentence definition using correct NestJS and Node.js terminology.
  2. Add one specific example drawn from a real backend scenario such as an e-commerce, fintech, or SaaS API.
  3. Mention the request lifecycle stage this concept operates at (pipe, guard, middleware, filter, interceptor) since interviewers often test whether you know the execution order.
  4. Close with one benefit, trade-off, or production use case.
  5. Avoid vague answers like "it just validates stuff" — interviewers filter these out immediately.

Practical Scenario

Imagine you are hardening a production API for a SaaS product, similar to systems used at companies like Razorpay, Freshworks, or Postman. Every lesson in this module maps directly to a decision you will make while making that backend secure, predictable, and resilient to bad input: validating what comes in, transforming it safely, catching failures gracefully, and protecting routes from unauthorized access.

NestJS Guards: Architecture and Flow Diagram

Visualize the guard decision flow:

[Incoming Request] --> [Guard: canActivate()] --> Returns true? --> [Yes: Continue to Interceptors/Pipes/Handler] --> [No: Request Blocked, typically 403 Forbidden or 401 Unauthorized]

For role-based guards, add: '[RolesGuard reads @Roles() metadata via Reflector] --> [Compares against authenticated user's roles] --> [Allow or Deny]'

NestJS Guards: Guard Type Reference Table

Guard TypeWhat It ChecksCommon Response If Denied
Authentication guardWhether a valid token/credential is present401 Unauthorized
Role-based guardWhether the authenticated user has a required role403 Forbidden
Ownership guardWhether the user owns or has access to a specific resource403 Forbidden
Rate-limit guardWhether the client has exceeded allowed request frequency429 Too Many Requests

NestJS Guards: NestJS Code Example

// A simple authentication guard
import {
  Injectable,
  CanActivate,
  ExecutionContext,
  UnauthorizedException,
} from '@nestjs/common';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const authHeader = request.headers['authorization'];

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      throw new UnauthorizedException('Missing or invalid authorization token');
    }

    // In a real app, verify the JWT here and attach the decoded user to the request
    const token = authHeader.replace('Bearer ', '');
    request.user = { id: 1, name: 'Asha', roles: ['admin'] }; // simplified for illustration

    return true;
  }
}

// A custom decorator to attach role metadata
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

// A role-based guard using Reflector
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler());
    if (!requiredRoles) return true; // no roles required, allow access

    const request = context.switchToHttp().getRequest();
    const user = request.user;

    const hasRole = requiredRoles.some((role) => user?.roles?.includes(role));
    if (!hasRole) {
      throw new ForbiddenException('You do not have permission to access this resource');
    }
    return true;
  }
}

// Applying both guards to a controller
import { Controller, Get, UseGuards } from '@nestjs/common';

@Controller('admin')
@UseGuards(AuthGuard, RolesGuard)
export class AdminController {
  @Get('dashboard')
  @Roles('admin')
  getDashboard() {
    return { message: 'Welcome to the admin dashboard' };
  }
}

AuthGuard checks for a valid Authorization header, throwing an UnauthorizedException if it's missing, and in a real implementation would verify a JWT before attaching the decoded user object to the request for later use. The Roles decorator attaches role metadata directly onto a route handler using SetMetadata. RolesGuard then reads that same metadata back using the injected Reflector, comparing it against the roles attached to request.user by AuthGuard, and throws a ForbiddenException if the user lacks the required role. Applying both guards together on AdminController, with @Roles('admin') on the specific dashboard route, means a request must both be authenticated and specifically have the admin role to succeed, demonstrating how multiple guards commonly compose together to enforce layered security.

Real-World NestJS Guards Industry Examples

  • Virtually every production API with user accounts implements some version of the AuthGuard pattern shown here, verifying a JWT or session token before allowing access to protected routes.
  • Admin panels and internal tools almost universally combine authentication guards with role-based guards, exactly like the AdminController example, to restrict sensitive operations to specific staff roles.
  • Multi-tenant SaaS platforms often extend guards further to check not just roles but resource ownership, ensuring a user can only access data belonging to their own organization or account.
  • Public APIs with tiered access levels (free, pro, enterprise) frequently use custom guards to check a user's subscription tier before allowing access to premium-only endpoints.

NestJS Guards Interview Questions and Answers

Q1. What is a guard in NestJS and what specific question does it answer?

Short answer: A guard is a class implementing the CanActivate interface that determines whether a given request is allowed to proceed to its route handler. It specifically answers an authorization question: is this request permitted to happen at all, based on factors like authentication status or user role, before any further request processing occurs.

Detailed explanation: A guard in NestJS is a class implementing the CanActivate interface, which requires a single method: canActivate(context: ExecutionContext). This method must return true if the request should be allowed to proceed, or false (or throw an exception) if it should be blocked. Guards run after middleware but before interceptors and pipes, making them the appropriate place to answer authorization-related questions before any further request processing, including data transformation and validation, takes place. The most fundamental guard pattern is an authentication guard, which checks whether a request carries valid credentials, typically a bearer token in the Authorization header. Inside canActivate(), the guard extracts the underlying HTTP request from the provided ExecutionContext using context.switchToHttp().getRequest(), inspects the relevant header, and either confirms the token is valid (often by verifying a JWT) and returns true, or throws an UnauthorizedException if the token is missing or invalid. A more advanced and extremely common pattern in real applications is role-based access control, where different routes require the authenticated user to have a specific role, such as 'admin' or 'editor'. This is typically implemented using a combination of a custom decorator and NestJS's Reflector class. First, you create a simple custom decorator, commonly named @Roles(...roles: string[]), that attaches an array of allowed roles as metadata onto a specific route handler. Then, inside a RolesGuard's canActivate() method, you use the injected Reflector to read that metadata back off the current route handler, compare it against the roles present on the authenticated user (usually attached to the request by an earlier authentication step), and return true or false accordingly. Guards can be applied at several levels of scope, exactly like pipes and exception filters. Applied to a single route handler using @UseGuards(AuthGuard), only that specific route is protected. Applied to an entire controller class, every route within it is protected. Applied globally in main.ts using app.useGlobalGuards(), every route across the entire application requires the guard's approval to proceed, which is a common pattern for a baseline authentication guard, often combined with an explicit @Public() decorator to selectively exempt specific routes like a login endpoint that must remain accessible without prior authentication.

Practical example: Virtually every production API with user accounts implements some version of the AuthGuard pattern shown here, verifying a JWT or session token before allowing access to protected routes.

Interview tip: Guards implement CanActivate with a canActivate() method returning true (allow) or false/throw (deny).

Revision hook: Guards exist to answer exactly one question: should this request be allowed to happen at all?

Q2. Where do guards run in the NestJS request lifecycle relative to middleware and pipes?

Short answer: Guards run after middleware has completed but before interceptors and pipes execute. This ordering ensures that authorization checks happen early, preventing unauthorized requests from triggering unnecessary data transformation, validation, or business logic that guards are specifically meant to block.

Detailed explanation: AuthGuard checks for a valid Authorization header, throwing an UnauthorizedException if it's missing, and in a real implementation would verify a JWT before attaching the decoded user object to the request for later use. The Roles decorator attaches role metadata directly onto a route handler using SetMetadata. RolesGuard then reads that same metadata back using the injected Reflector, comparing it against the roles attached to request.user by AuthGuard, and throws a ForbiddenException if the user lacks the required role. Applying both guards together on AdminController, with @Roles('admin') on the specific dashboard route, means a request must both be authenticated and specifically have the admin role to succeed, demonstrating how multiple guards commonly compose together to enforce layered security.

Practical example: Admin panels and internal tools almost universally combine authentication guards with role-based guards, exactly like the AdminController example, to restrict sensitive operations to specific staff roles.

Interview tip: Guards run after middleware, before interceptors and pipes — the correct stage for authorization checks.

Revision hook: Authentication and authorization are related but distinct concerns, often implemented as two separate, composable guards.

Q3. How would you implement role-based access control in NestJS?

Short answer: You create a custom decorator, commonly @Roles(...roles), using SetMetadata to attach required role information directly to a route handler. Then, a guard implementing CanActivate injects NestJS's Reflector class to read that metadata at runtime, compares it against the authenticated user's actual roles (usually attached to the request by an earlier authentication guard), and allows or denies access accordingly.

Detailed explanation: Guards in NestJS, built around the CanActivate interface, determine whether a given request is authorized to proceed to its route handler, running after middleware but before interceptors and pipes in the request lifecycle. A simple authentication guard checks for valid credentials, such as a bearer token, throwing an UnauthorizedException if missing or invalid. More advanced role-based access control combines a custom @Roles() decorator, which attaches metadata to a route handler using SetMetadata, with a guard that reads that metadata using NestJS's Reflector class and compares it against the authenticated user's actual roles. Guards can be applied at the method, controller, or global level using @UseGuards() or app.useGlobalGuards(), forming the backbone of route-level security in virtually every real-world NestJS application.

Practical example: Multi-tenant SaaS platforms often extend guards further to check not just roles but resource ownership, ensuring a user can only access data belonging to their own organization or account.

Interview tip: Role-based access control combines a custom @Roles() decorator (via SetMetadata) with a guard using Reflector to read that metadata.

Revision hook: The @Roles() decorator plus Reflector pattern is a standard, reusable approach to role-based access control across the entire NestJS ecosystem.

Q4. What is the purpose of the Reflector class in NestJS?

Short answer: Reflector is a utility class that allows guards (and other parts of NestJS) to read custom metadata attached to route handlers or controllers via decorators like SetMetadata. It bridges the gap between declarative metadata, like which roles are required for a route, and the runtime logic in a guard that needs to check that metadata.

Detailed explanation: A guard in NestJS is a class implementing the CanActivate interface, which requires a single method: canActivate(context: ExecutionContext). This method must return true if the request should be allowed to proceed, or false (or throw an exception) if it should be blocked. Guards run after middleware but before interceptors and pipes, making them the appropriate place to answer authorization-related questions before any further request processing, including data transformation and validation, takes place. The most fundamental guard pattern is an authentication guard, which checks whether a request carries valid credentials, typically a bearer token in the Authorization header. Inside canActivate(), the guard extracts the underlying HTTP request from the provided ExecutionContext using context.switchToHttp().getRequest(), inspects the relevant header, and either confirms the token is valid (often by verifying a JWT) and returns true, or throws an UnauthorizedException if the token is missing or invalid. A more advanced and extremely common pattern in real applications is role-based access control, where different routes require the authenticated user to have a specific role, such as 'admin' or 'editor'. This is typically implemented using a combination of a custom decorator and NestJS's Reflector class. First, you create a simple custom decorator, commonly named @Roles(...roles: string[]), that attaches an array of allowed roles as metadata onto a specific route handler. Then, inside a RolesGuard's canActivate() method, you use the injected Reflector to read that metadata back off the current route handler, compare it against the roles present on the authenticated user (usually attached to the request by an earlier authentication step), and return true or false accordingly. Guards can be applied at several levels of scope, exactly like pipes and exception filters. Applied to a single route handler using @UseGuards(AuthGuard), only that specific route is protected. Applied to an entire controller class, every route within it is protected. Applied globally in main.ts using app.useGlobalGuards(), every route across the entire application requires the guard's approval to proceed, which is a common pattern for a baseline authentication guard, often combined with an explicit @Public() decorator to selectively exempt specific routes like a login endpoint that must remain accessible without prior authentication.

Practical example: Public APIs with tiered access levels (free, pro, enterprise) frequently use custom guards to check a user's subscription tier before allowing access to premium-only endpoints.

Interview tip: @UseGuards() applies guards at the method or controller level; app.useGlobalGuards() applies them application-wide.

Revision hook: Ordering matters: an authentication guard should generally run before a role-based guard that depends on knowing who the user is.

NestJS Guards MCQs and Practice Questions

1. Which interface must a class implement to function as a NestJS guard?

  1. PipeTransform
  2. CanActivate
  3. NestMiddleware
  4. ExceptionFilter

Answer: B. CanActivate

Explanation: A guard must implement the CanActivate interface, providing a canActivate() method that returns true or false to determine whether a request should proceed.

Concept link: Guards implement CanActivate with a canActivate() method returning true (allow) or false/throw (deny).

Why this matters: Guards exist to answer exactly one question: should this request be allowed to happen at all?

2. Where do guards execute relative to middleware and interceptors in the request lifecycle?

  1. Before middleware, after interceptors
  2. After middleware, before interceptors
  3. After both middleware and interceptors
  4. Guards and middleware run simultaneously

Answer: B. After middleware, before interceptors

Explanation: Guards run after middleware has completed processing a request but before interceptors and pipes execute, positioning them as the appropriate stage for authorization decisions.

Concept link: Guards run after middleware, before interceptors and pipes — the correct stage for authorization checks.

Why this matters: Authentication and authorization are related but distinct concerns, often implemented as two separate, composable guards.

3. Which NestJS class is used by guards to read custom metadata attached via decorators like @Roles()?

  1. Injector
  2. Reflector
  3. ModuleRef
  4. ExecutionContext

Answer: B. Reflector

Explanation: Reflector is specifically designed to retrieve metadata attached to classes or methods through decorators like SetMetadata, which custom guards commonly use to implement role-based access control.

Concept link: Role-based access control combines a custom @Roles() decorator (via SetMetadata) with a guard using Reflector to read that metadata.

Why this matters: The @Roles() decorator plus Reflector pattern is a standard, reusable approach to role-based access control across the entire NestJS ecosystem.

4. What decorator is used to apply a guard to a specific controller or route handler?

  1. @Guard()
  2. @UseGuards()
  3. @Protect()
  4. @CanActivate()

Answer: B. @UseGuards()

Explanation: @UseGuards() is the decorator used to attach one or more guards to a specific route handler or an entire controller, ensuring their canActivate() logic runs before the associated routes execute.

Concept link: @UseGuards() applies guards at the method or controller level; app.useGlobalGuards() applies them application-wide.

Why this matters: Ordering matters: an authentication guard should generally run before a role-based guard that depends on knowing who the user is.

Common NestJS Guards Mistakes to Avoid

  • Confusing guards with pipes, using a guard to transform data or a pipe to make authorization decisions, when each has a distinct, specific responsibility.
  • Forgetting to apply an authentication guard before a role-based guard, causing the role-based guard to fail since request.user was never set.
  • Hardcoding required roles directly inside a guard's logic instead of using a reusable decorator and Reflector, making the guard inflexible across different routes.
  • Applying a guard globally without an exemption mechanism (like a @Public() decorator), accidentally locking out routes like login or health checks that must remain accessible.

NestJS Guards: Interview Notes and Exam Tips

  • Guards implement CanActivate with a canActivate() method returning true (allow) or false/throw (deny).
  • Guards run after middleware, before interceptors and pipes — the correct stage for authorization checks.
  • Role-based access control combines a custom @Roles() decorator (via SetMetadata) with a guard using Reflector to read that metadata.
  • @UseGuards() applies guards at the method or controller level; app.useGlobalGuards() applies them application-wide.

Key NestJS Guards Takeaways

  • Guards exist to answer exactly one question: should this request be allowed to happen at all?
  • Authentication and authorization are related but distinct concerns, often implemented as two separate, composable guards.
  • The @Roles() decorator plus Reflector pattern is a standard, reusable approach to role-based access control across the entire NestJS ecosystem.
  • Ordering matters: an authentication guard should generally run before a role-based guard that depends on knowing who the user is.

NestJS Guards: Summary

Guards in NestJS, built around the CanActivate interface, determine whether a given request is authorized to proceed to its route handler, running after middleware but before interceptors and pipes in the request lifecycle. A simple authentication guard checks for valid credentials, such as a bearer token, throwing an UnauthorizedException if missing or invalid. More advanced role-based access control combines a custom @Roles() decorator, which attaches metadata to a route handler using SetMetadata, with a guard that reads that metadata using NestJS's Reflector class and compares it against the authenticated user's actual roles. Guards can be applied at the method, controller, or global level using @UseGuards() or app.useGlobalGuards(), forming the backbone of route-level security in virtually every real-world NestJS application.

Frequently Asked Questions

While both can check for valid credentials, guards are NestJS's purpose-built mechanism for authorization decisions and have access to NestJS's richer ExecutionContext, including metadata set by decorators. Middleware runs earlier and works with raw request/response objects, making guards the more idiomatic and commonly recommended choice specifically for access-control logic within NestJS. In interviews, tie this back to: Guards implement CanActivate with a canActivate() method returning true (allow) or false/throw (deny). In real applications, consider this example: Virtually every production API with user accounts implements some version of the AuthGuard pattern shown here, verifying a JWT or session token before allowing access to protected routes. Key revision takeaway: Guards exist to answer exactly one question: should this request be allowed to happen at all?

Yes. @UseGuards() accepts multiple guard classes, such as @UseGuards(AuthGuard, RolesGuard), and NestJS executes them in order, only proceeding to the route handler if every guard in the chain returns true, allowing you to compose authentication and authorization checks cleanly. In interviews, tie this back to: Guards run after middleware, before interceptors and pipes — the correct stage for authorization checks. In real applications, consider this example: Admin panels and internal tools almost universally combine authentication guards with role-based guards, exactly like the AdminController example, to restrict sensitive operations to specific staff roles. Key revision takeaway: Authentication and authorization are related but distinct concerns, often implemented as two separate, composable guards.

A guard can either return false, which NestJS will typically translate into a generic 403 Forbidden response, or throw a specific exception, such as UnauthorizedException or ForbiddenException, which is generally preferred since it gives the client a more precise, meaningful error message and status code. In interviews, tie this back to: Role-based access control combines a custom @Roles() decorator (via SetMetadata) with a guard using Reflector to read that metadata. In real applications, consider this example: Multi-tenant SaaS platforms often extend guards further to check not just roles but resource ownership, ensuring a user can only access data belonging to their own organization or account. Key revision takeaway: The @Roles() decorator plus Reflector pattern is a standard, reusable approach to role-based access control across the entire NestJS ecosystem.

A common pattern is creating a custom @Public() decorator using SetMetadata, similar to the @Roles() pattern, and having your global guard check for this metadata using Reflector, returning true immediately (skipping further checks) if the route is marked as public. In interviews, tie this back to: @UseGuards() applies guards at the method or controller level; app.useGlobalGuards() applies them application-wide. In real applications, consider this example: Public APIs with tiered access levels (free, pro, enterprise) frequently use custom guards to check a user's subscription tier before allowing access to premium-only endpoints. Key revision takeaway: Ordering matters: an authentication guard should generally run before a role-based guard that depends on knowing who the user is.

Yes, indirectly. Since a guard's canActivate() method receives the ExecutionContext, you can call context.switchToHttp().getRequest() to access the full underlying request object, including its params, query, and body properties, exactly as you would in middleware. In interviews, tie this back to: Guards implement CanActivate with a canActivate() method returning true (allow) or false/throw (deny). In real applications, consider this example: Virtually every production API with user accounts implements some version of the AuthGuard pattern shown here, verifying a JWT or session token before allowing access to protected routes. Key revision takeaway: Guards exist to answer exactly one question: should this request be allowed to happen at all?

Yes. NestJS's official Passport integration (@nestjs/passport) works by providing pre-built guards, such as AuthGuard('jwt') or AuthGuard('local'), that wrap Passport's authentication strategies, letting you apply battle-tested authentication logic using the exact same @UseGuards() pattern covered in this lesson. In interviews, tie this back to: Guards run after middleware, before interceptors and pipes — the correct stage for authorization checks. In real applications, consider this example: Admin panels and internal tools almost universally combine authentication guards with role-based guards, exactly like the AdminController example, to restrict sensitive operations to specific staff roles. Key revision takeaway: Authentication and authorization are related but distinct concerns, often implemented as two separate, composable guards.