Skip to content
nestjs-guards-interceptors logo

NestJS Guards and Interceptors

nestjs-guards-interceptors

Use when nestJS guards and interceptors for auth, logging, and transformation. Use when implementing cross-cutting concerns.

TheBushidoCollective/han0installs199stars

SKILL.md

Full skill instructions

NestJS Guards and Interceptors

Master NestJS guards and interceptors for implementing authentication, authorization, logging, and request/​response transformation.

Guards Fundamentals

Understanding CanActivate and ExecutionContext.

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/​common';
import { Observable } from 'rxjs';

@Injectable()
export class BasicGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    return this.validateRequest(request);
  }

  private validateRequest(request: any): boolean {
    // Simple validation logic
    return !!request.headers.authorization;
  }
}

// ExecutionContext provides context about current request
@Injectable()
export class ContextAwareGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    // Get HTTP context
    const httpContext = context.switchToHttp();
    const request = httpContext.getRequest();
    const response = httpContext.getResponse();

    // Get handler and class information
    const handler = context.getHandler();
    const controller = context.getClass();

    console.log(`Handler: ${handler.name}`);
    console.log(`Controller: ${controller.name}`);

    return true;
  }
}

// Usage in controller
import { Controller, Get, UseGuards } from '@nestjs/​common';

@Controller('users')
@UseGuards(BasicGuard)
export class UserController {
  @Get()
  findAll() {
    return [];
  }

  @Get('profile')
  @UseGuards(ContextAwareGuard)  // Method-level guard
  getProfile() {
    return { name: 'John' };
  }
}

Authentication Guards

JWT, session, and API key authentication patterns.

import { Injectable, UnauthorizedException } from '@nestjs/​common';
import { JwtService } from '@nestjs/​jwt';

@Injectable()
export class JwtAuthGuard implements CanActivate {
  constructor(private jwtService: JwtService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const token = this.extractTokenFromHeader(request);

    if (!token) {
      throw new UnauthorizedException('No token provided');
    }

    try {
      const payload = await this.jwtService.verifyAsync(token, {
        secret: process.env.JWT_SECRET,
      });
      // Attach user to request
      request['user'] = payload;
    } catch {
      throw new UnauthorizedException('Invalid token');
    }

    return true;
  }

  private extractTokenFromHeader(request: any): string | undefined {
    const [type, token] = request.headers.authorization?.split(' ') ?? [];
    return type === 'Bearer' ? token : undefined;
  }
}

// Session-based authentication
@Injectable()
export class SessionAuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();

    if (!request.session || !request.session.userId) {
      throw new UnauthorizedException('Not authenticated');
    }

    return true;
  }
}

// API Key authentication
@Injectable()
export class ApiKeyGuard implements CanActivate {
  constructor(private configService: ConfigService) {}

  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const apiKey = request.headers['x-api-key'];

    if (!apiKey) {
      throw new UnauthorizedException('API key required');
    }

    const validApiKey = this.configService.get('API_KEY');
    if (apiKey !== validApiKey) {
      throw new UnauthorizedException('Invalid API key');
    }

    return true;
  }
}

// Multiple auth strategies
@Injectable()
export class MultiAuthGuard implements CanActivate {
  constructor(
    private jwtService: JwtService,
    private configService: ConfigService,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();

    // Try JWT first
    const token = this.extractTokenFromHeader(request);
    if (token) {
      try {
        const payload = await this.jwtService.verifyAsync(token);
        request['user'] = payload;
        return true;
      } catch {}
    }

    // Fall back to API key
    const apiKey = request.headers['x-api-key'];
    if (apiKey === this.configService.get('API_KEY')) {
      return true;
    }

    throw new UnauthorizedException();
  }

  private extractTokenFromHeader(request: any): string | undefined {
    const [type, token] = request.headers.authorization?.split(' ') ?? [];
    return type === 'Bearer' ? token : undefined;
  }
}

Role-Based Authorization Guards

RBAC patterns with decorators.

import { SetMetadata } from '@nestjs/​common';
import { Reflector } from '@nestjs/​core';

// Define roles
export enum Role {
  USER = 'user',
  ADMIN = 'admin',
  MODERATOR = 'moderator',
}

// Roles decorator
export const ROLES_KEY = 'roles';
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);

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

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (!requiredRoles) {
      return true;  // No roles required
    }

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

    if (!user) {
      throw new UnauthorizedException('User not authenticated');
    }

    const hasRole = requiredRoles.some((role) => user.roles?.includes(role));
    if (!hasRole) {
      throw new ForbiddenException('Insufficient permissions');
    }

    return true;
  }
}

// Usage
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
export class AdminController {
  @Get('users')
  @Roles(Role.ADMIN)
  getAllUsers() {
    return [];
  }

  @Get('moderate')
  @Roles(Role.ADMIN, Role.MODERATOR)
  moderateContent() {
    return { message: 'Moderation tools' };
  }
}

// Permission-based authorization
export const PERMISSIONS_KEY = 'permissions';
export const RequirePermissions = (...permissions: string[]) =>
  SetMetadata(PERMISSIONS_KEY, permissions);

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

  canActivate(context: ExecutionContext): boolean {
    const requiredPermissions = this.reflector.getAllAndOverride<string[]>(
      PERMISSIONS_KEY,
      [context.getHandler(), context.getClass()],
    );

    if (!requiredPermissions) {
      return true;
    }

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

    const hasPermission = requiredPermissions.every((permission) =>
      user.permissions?.includes(permission),
    );

    if (!hasPermission) {
      throw new ForbiddenException('Missing required permissions');
    }

    return true;
  }
}

// Resource ownership guard
@Injectable()
export class ResourceOwnerGuard implements CanActivate {
  constructor(private usersService: UsersService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    const resourceId = request.params.id;

    const resource = await this.usersService.findOne(resourceId);

    if (!resource) {
      throw new NotFoundException('Resource not found');
    }

    if (resource.userId !== user.id && !user.roles.includes(Role.ADMIN)) {
      throw new ForbiddenException('You do not own this resource');
    }

    // Attach resource to request for later use
    request['resource'] = resource;
    return true;
  }
}

Interceptors Fundamentals

NestInterceptor and response transformation.

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/​common';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/​operators';

// Basic interceptor
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log('Before...');

    const now = Date.now();
    return next
      .handle()
      .pipe(tap(() => console.log(`After... ${Date.now() - now}ms`)));
  }
}

// Transform response
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
  intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Observable<Response<T>> {
    return next.handle().pipe(
      map((data) => ({
        data,
        timestamp: new Date().toISOString(),
        path: context.switchToHttp().getRequest().url,
      })),
    );
  }
}

interface Response<T> {
  data: T;
  timestamp: string;
  path: string;
}

// Error handling in interceptor
@Injectable()
export class ErrorsInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      catchError((err) => {
        console.error('Error caught in interceptor:', err);
        throw new InternalServerErrorException('Something went wrong');
      }),
    );
  }
}

// Usage
@Controller('users')
@UseInterceptors(LoggingInterceptor)
export class UserController {
  @Get()
  @UseInterceptors(TransformInterceptor)
  findAll() {
    return [{ id: 1, name: 'John' }];
  }
}

Logging Interceptors

Advanced logging patterns.

import { Logger } from '@nestjs/​common';

@Injectable()
export class RequestLoggingInterceptor implements NestInterceptor {
  private readonly logger = new Logger(RequestLoggingInterceptor.name);

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const { method, url, body } = request;
    const userAgent = request.get('user-agent') || '';

    this.logger.log(`Incoming Request: ${method} ${url}`);
    this.logger.debug(`User Agent: ${userAgent}`);
    this.logger.debug(`Body: ${JSON.stringify(body)}`);

    const now = Date.now();
    return next.handle().pipe(
      tap({
        next: (data) => {
          const response = context.switchToHttp().getResponse();
          this.logger.log(
            `Response: ${method} ${url} ${response.statusCode} - ${Date.now() - now}ms`,
          );
        },
        error: (err) => {
          this.logger.error(
            `Error: ${method} ${url} - ${err.message}`,
            err.stack,
          );
        },
      }),
    );
  }
}

// Performance monitoring
@Injectable()
export class PerformanceInterceptor implements NestInterceptor {
  private readonly logger = new Logger(PerformanceInterceptor.name);

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const { method, url } = request;
    const startTime = Date.now();

    return next.handle().pipe(
      tap(() => {
        const duration = Date.now() - startTime;

        if (duration > 1000) {
          this.logger.warn(`Slow request: ${method} ${url} - ${duration}ms`);
        } else {
          this.logger.log(`${method} ${url} - ${duration}ms`);
        }
      }),
    );
  }
}

Response Transformation Interceptors

Shaping API responses consistently.

// Wrap all responses
@Injectable()
export class ResponseWrapperInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map((data) => {
        const response = context.switchToHttp().getResponse();
        return {
          statusCode: response.statusCode,
          message: 'Success',
          data,
        };
      }),
    );
  }
}

// Pagination wrapper
interface PaginatedResponse<T> {
  items: T[];
  total: number;
  page: number;
  pageSize: number;
  totalPages: number;
}

@Injectable()
export class PaginationInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map((data) => {
        if (data && typeof data === 'object' && 'items' in data) {
          const { items, total } = data;
          const request = context.switchToHttp().getRequest();
          const page = parseInt(request.query.page) || 1;
          const pageSize = parseInt(request.query.pageSize) || 10;

          return {
            items,
            total,
            page,
            pageSize,
            totalPages: Math.ceil(total / pageSize),
          };
        }
        return data;
      }),
    );
  }
}

// Exclude null fields
@Injectable()
export class ExcludeNullInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map((data) => {
        return this.removeNullValues(data);
      }),
    );
  }

  private removeNullValues(obj: any): any {
    if (Array.isArray(obj)) {
      return obj.map((item) => this.removeNullValues(item));
    }

    if (obj !== null && typeof obj === 'object') {
      return Object.entries(obj).reduce((acc, [key, value]) => {
        if (value !== null) {
          acc[key] = this.removeNullValues(value);
        }
        return acc;
      }, {});
    }

    return obj;
  }
}

Caching Interceptors

Implementing caching strategies.

import { CACHE_MANAGER } from '@nestjs/​cache-manager';
import { Cache } from 'cache-manager';

@Injectable()
export class CacheInterceptor implements NestInterceptor {
  constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}

  async intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Promise<Observable<any>> {
    const request = context.switchToHttp().getRequest();
    const cacheKey = `${request.method}:${request.url}`;

    // Check cache
    const cachedResponse = await this.cacheManager.get(cacheKey);
    if (cachedResponse) {
      return of(cachedResponse);
    }

    // Execute handler and cache result
    return next.handle().pipe(
      tap(async (response) => {
        await this.cacheManager.set(cacheKey, response, 60000); // 60s TTL
      }),
    );
  }
}

// Conditional caching
export const CACHE_KEY_METADATA = 'cache_key';
export const CacheKey = (key: string) => SetMetadata(CACHE_KEY_METADATA, key);

@Injectable()
export class SmartCacheInterceptor implements NestInterceptor {
  constructor(
    @Inject(CACHE_MANAGER) private cacheManager: Cache,
    private reflector: Reflector,
  ) {}

  async intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Promise<Observable<any>> {
    const cacheKey = this.reflector.get(CACHE_KEY_METADATA, context.getHandler());

    if (!cacheKey) {
      return next.handle();
    }

    const cached = await this.cacheManager.get(cacheKey);
    if (cached) {
      return of(cached);
    }

    return next.handle().pipe(
      tap(async (response) => {
        await this.cacheManager.set(cacheKey, response);
      }),
    );
  }
}

// Usage
@Controller('products')
export class ProductsController {
  @Get()
  @CacheKey('all-products')
  findAll() {
    return this.productsService.findAll();
  }
}

Timeout Interceptors

Handling request timeouts.

import { timeout, catchError } from 'rxjs/​operators';
import { throwError, TimeoutError } from 'rxjs';

@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      timeout(5000), // 5 second timeout
      catchError((err) => {
        if (err instanceof TimeoutError) {
          return throwError(() => new RequestTimeoutException());
        }
        return throwError(() => err);
      }),
    );
  }
}

// Dynamic timeout based on endpoint
export const TIMEOUT_METADATA = 'timeout';
export const Timeout = (milliseconds: number) =>
  SetMetadata(TIMEOUT_METADATA, milliseconds);

@Injectable()
export class DynamicTimeoutInterceptor implements NestInterceptor {
  constructor(private reflector: Reflector) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const timeoutValue =
      this.reflector.get(TIMEOUT_METADATA, context.getHandler()) || 5000;

    return next.handle().pipe(
      timeout(timeoutValue),
      catchError((err) => {
        if (err instanceof TimeoutError) {
          return throwError(() => new RequestTimeoutException());
        }
        return throwError(() => err);
      }),
    );
  }
}

// Usage
@Controller('reports')
export class ReportsController {
  @Get('generate')
  @Timeout(30000)  // 30 second timeout for long-running report
  generateReport() {
    return this.reportsService.generate();
  }
}

Pipes

Validation and transformation pipes.

import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/​common';
import { validate } from 'class-validator';
import { plainToInstance } from 'class-transformer';

// Built-in validation pipe
import { ValidationPipe } from '@nestjs/​common';

@Controller('users')
export class UserController {
  @Post()
  create(@Body(new ValidationPipe()) createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }
}

// Custom validation pipe
@Injectable()
export class CustomValidationPipe implements PipeTransform<any> {
  async transform(value: any, { metatype }: ArgumentMetadata) {
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }

    const object = plainToInstance(metatype, value);
    const errors = await validate(object);

    if (errors.length > 0) {
      const messages = errors.map((err) => ({
        property: err.property,
        constraints: err.constraints,
      }));
      throw new BadRequestException({ errors: messages });
    }

    return value;
  }

  private toValidate(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}

// Transformation pipes
@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed (numeric string expected)');
    }
    return val;
  }
}

// Built-in pipes usage
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
  return this.usersService.findOne(id);
}

// Strip fields pipe
@Injectable()
export class StripFieldsPipe implements PipeTransform {
  constructor(private readonly fieldsToStrip: string[]) {}

  transform(value: any) {
    if (typeof value !== 'object' || value === null) {
      return value;
    }

    const result = { ...value };
    this.fieldsToStrip.forEach((field) => {
      delete result[field];
    });

    return result;
  }
}

// Default value pipe
@Injectable()
export class DefaultValuePipe implements PipeTransform {
  constructor(private readonly defaultValue: any) {}

  transform(value: any) {
    return value !== undefined && value !== null ? value : this.defaultValue;
  }
}

Exception Filters

Custom exception handling.

import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
} from '@nestjs/​common';
import { Request, Response } from 'express';

// HTTP exception filter
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message: exception.message,
    });
  }
}

// All exceptions filter
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  private readonly logger = new Logger(AllExceptionsFilter.name);

  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    const message =
      exception instanceof HttpException
        ? exception.message
        : 'Internal server error';

    this.logger.error(
      `${request.method} ${request.url}`,
      exception instanceof Error ? exception.stack : 'Unknown error',
    );

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message,
    });
  }
}

// Validation exception filter
@Catch(BadRequestException)
export class ValidationExceptionFilter implements ExceptionFilter {
  catch(exception: BadRequestException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();

    const exceptionResponse = exception.getResponse();
    const errors =
      typeof exceptionResponse === 'object' && 'message' in exceptionResponse
        ? exceptionResponse['message']
        : exceptionResponse;

    response.status(HttpStatus.BAD_REQUEST).json({
      statusCode: HttpStatus.BAD_REQUEST,
      timestamp: new Date().toISOString(),
      path: request.url,
      errors,
    });
  }
}

// Usage
@Controller('users')
@UseFilters(new HttpExceptionFilter())
export class UserController {}

// Global filter
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new AllExceptionsFilter());
  await app.listen(3000);
}

Middleware

Function and class middleware.

import { Injectable, NestMiddleware } from '@nestjs/​common';
import { Request, Response, NextFunction } from 'express';

// Class middleware
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  private logger = new Logger('HTTP');

  use(req: Request, res: Response, next: NextFunction) {
    const { method, originalUrl } = req;
    const startTime = Date.now();

    res.on('finish', () => {
      const { statusCode } = res;
      const duration = Date.now() - startTime;

      this.logger.log(`${method} ${originalUrl} ${statusCode} - ${duration}ms`);
    });

    next();
  }
}

// Function middleware
export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`Request: ${req.method} ${req.url}`);
  next();
}

// Authentication middleware
@Injectable()
export class AuthMiddleware implements NestMiddleware {
  constructor(private authService: AuthService) {}

  async use(req: Request, res: Response, next: NextFunction) {
    const token = req.headers.authorization?.split(' ')[1];

    if (!token) {
      throw new UnauthorizedException('No token provided');
    }

    try {
      const user = await this.authService.validateToken(token);
      req['user'] = user;
      next();
    } catch (error) {
      throw new UnauthorizedException('Invalid token');
    }
  }
}

// CORS middleware
@Injectable()
export class CorsMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');

    if (req.method === 'OPTIONS') {
      res.sendStatus(200);
    } else {
      next();
    }
  }
}

// Apply middleware in module
import { Module, MiddlewareConsumer, RequestMethod } from '@nestjs/​common';

@Module({
  imports: [],
  controllers: [UserController],
})
export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('*');

    consumer
      .apply(AuthMiddleware)
      .exclude(
        { path: 'auth/​login', method: RequestMethod.POST },
        { path: 'health', method: RequestMethod.GET },
      )
      .forRoutes('*');
  }
}

Request Lifecycle and Execution Order

Understanding the order of execution.

// Order of execution:
// 1. Middleware
// 2. Guards
// 3. Interceptors (before)
// 4. Pipes
// 5. Controller method
// 6. Interceptors (after)
// 7. Exception filters

@Controller('demo')
export class DemoController {
  private readonly logger = new Logger(DemoController.name);

  @Post()
  @UseGuards(DemoGuard)
  @UseInterceptors(DemoInterceptor)
  @UsePipes(DemoPipe)
  create(@Body() data: any) {
    this.logger.log('5. Controller method executed');
    return data;
  }
}

@Injectable()
export class DemoGuard implements CanActivate {
  private readonly logger = new Logger(DemoGuard.name);

  canActivate(context: ExecutionContext): boolean {
    this.logger.log('2. Guard executed');
    return true;
  }
}

@Injectable()
export class DemoInterceptor implements NestInterceptor {
  private readonly logger = new Logger(DemoInterceptor.name);

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    this.logger.log('3. Interceptor before');
    return next.handle().pipe(
      tap(() => this.logger.log('6. Interceptor after')),
    );
  }
}

@Injectable()
export class DemoPipe implements PipeTransform {
  private readonly logger = new Logger(DemoPipe.name);

  transform(value: any) {
    this.logger.log('4. Pipe executed');
    return value;
  }
}

Testing Guards and Interceptors

Unit testing patterns.

import { Test, TestingModule } from '@nestjs/​testing';
import { ExecutionContext } from '@nestjs/​common';

describe('JwtAuthGuard', () => {
  let guard: JwtAuthGuard;
  let jwtService: JwtService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        JwtAuthGuard,
        {
          provide: JwtService,
          useValue: {
            verifyAsync: jest.fn(),
          },
        },
      ],
    }).compile();

    guard = module.get<JwtAuthGuard>(JwtAuthGuard);
    jwtService = module.get<JwtService>(JwtService);
  });

  it('should allow valid token', async () => {
    const mockContext = {
      switchToHttp: () => ({
        getRequest: () => ({
          headers: { authorization: 'Bearer valid-token' },
        }),
      }),
    } as ExecutionContext;

    jest.spyOn(jwtService, 'verifyAsync').mockResolvedValue({ userId: 1 });

    const result = await guard.canActivate(mockContext);
    expect(result).toBe(true);
  });

  it('should reject invalid token', async () => {
    const mockContext = {
      switchToHttp: () => ({
        getRequest: () => ({
          headers: { authorization: 'Bearer invalid-token' },
        }),
      }),
    } as ExecutionContext;

    jest.spyOn(jwtService, 'verifyAsync').mockRejectedValue(new Error());

    await expect(guard.canActivate(mockContext)).rejects.toThrow(
      UnauthorizedException,
    );
  });
});

describe('TransformInterceptor', () => {
  let interceptor: TransformInterceptor;

  beforeEach(() => {
    interceptor = new TransformInterceptor();
  });

  it('should transform response', (done) => {
    const mockContext = {
      switchToHttp: () => ({
        getRequest: () => ({ url: '/​test' }),
      }),
    } as ExecutionContext;

    const mockCallHandler = {
      handle: () => of({ name: 'Test' }),
    };

    interceptor.intercept(mockContext, mockCallHandler).subscribe((result) => {
      expect(result).toHaveProperty('data');
      expect(result).toHaveProperty('timestamp');
      expect(result).toHaveProperty('path');
      expect(result.data).toEqual({ name: 'Test' });
      done();
    });
  });
});

When to Use This Skill

Use nestjs-guards-interceptors when:

  • Implementing authentication and authorization
  • Adding logging and monitoring to your application
  • Transforming request/​response data consistently
  • Implementing caching strategies
  • Adding timeouts to requests
  • Handling cross-cutting concerns
  • Building middleware for request processing
  • Creating reusable validation logic
  • Implementing RBAC or ABAC patterns
  • Adding performance monitoring

NestJS Guards and Interceptors Best Practices

  1. Single responsibility - Each guard/​interceptor should have one clear purpose
  2. Use metadata - Leverage decorators and Reflector for configuration
  3. Chain appropriately - Understand execution order when combining multiple guards/​interceptors
  4. Error handling - Always handle errors gracefully in guards and interceptors
  5. Async operations - Use async/​await for database calls in guards
  6. Global vs local - Apply guards/​interceptors at appropriate scope (global, controller, method)
  7. Test thoroughly - Write unit tests for all guards and interceptors
  8. Performance - Keep guards and interceptors lightweight
  9. Logging - Use Logger service instead of console.log
  10. Type safety - Use TypeScript generics for type-safe interceptors

NestJS Guards and Interceptors Common Pitfalls

  1. Wrong execution order - Not understanding middleware → guards → interceptors → pipes flow
  2. Forgetting async - Not using async when guards perform database operations
  3. Missing error handling - Guards that don't throw appropriate exceptions
  4. Interceptor mutation - Mutating data in interceptors instead of transforming
  5. Circular dependencies - Guards that create circular dependency chains
  6. Global scope issues - Applying too many global guards/​interceptors hurts performance
  7. Missing metadata - Forgetting to use Reflector to read custom metadata
  8. Pipe placement - Using pipes in wrong order with validation
  9. Exception filter scope - Not understanding filter precedence
  10. Memory leaks - Not properly cleaning up subscriptions in interceptors

Resources

More skills from TheBushidoCollective

typescript-utility-types logo
TheBushidoCollective/han

typescript-utility-types

Use when typeScript utility types, mapped types, and advanced type manipulation. Use when creating flexible, type-safe TypeScript code.

199 0
View
shell-portability logo
TheBushidoCollective/han

shell-portability

Use when writing shell scripts that need to run across different systems, shells, or environments. Covers POSIX compatibility and platform differences.

199 0
View
tailwind-performance logo
TheBushidoCollective/han

tailwind-performance

Use when optimizing Tailwind CSS for production, reducing bundle size, and improving performance. Covers PurgeCSS, JIT mode, and build optimization.

199 0
View
figma-generate-component logo
TheBushidoCollective/han

figma-generate-component

Generate production-ready code from a Figma component or frame using the Figma MCP server

199 0
View
dialyzer-integration logo
TheBushidoCollective/han

dialyzer-integration

Use when integrating Dialyzer into development workflows and CI/CD pipelines for Erlang/Elixir projects.

199 0
View
rubocop-configuration logo
TheBushidoCollective/han

rubocop-configuration

Use when ruboCop configuration including YAML config, departments, inheritance, and customization.

199 0
View
elixir-otp-patterns logo
TheBushidoCollective/han

elixir-otp-patterns

Use when Elixir OTP patterns including GenServer, Supervisor, Agent, and Task. Use when building concurrent, fault-tolerant Elixir applications.

199 0
View
ameba-custom-rules logo
TheBushidoCollective/han

ameba-custom-rules

Use when creating custom Ameba rules for Crystal code analysis including rule development, AST traversal, issue reporting, and rule testing.

199 0
View
Swift Optionals Patterns logo
TheBushidoCollective/han

Swift Optionals Patterns

Use when swift's optional handling patterns including optional binding, chaining, nil coalescing, and modern approaches to safely working with optional values while avoiding common pitfalls and force unwrapping.

199 0
View
code-annotation-patterns logo
TheBushidoCollective/han

code-annotation-patterns

Use when annotating code with structured metadata, tags, and markers for AI-assisted development workflows. Covers annotation formats, semantic tags, and integration with development tools.

199 0
View
Scala Collections logo
TheBushidoCollective/han

Scala Collections

Use when scala collections including immutable/mutable variants, List, Vector, Set, Map operations, collection transformations, lazy evaluation with views, parallel collections, and custom collection builders for efficient data processing.

199 0
View
reactflow-custom-nodes logo
TheBushidoCollective/han

reactflow-custom-nodes

Use when creating custom React Flow nodes, edges, and handles. Covers custom node components, resizable nodes, toolbars, and advanced customization.

199 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View