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
-
Single responsibility - Each guard/interceptor should have one clear purpose
-
Use metadata - Leverage decorators and Reflector for configuration
-
Chain appropriately - Understand execution order when combining multiple guards/interceptors
-
Error handling - Always handle errors gracefully in guards and interceptors
-
Async operations - Use async/await for database calls in guards
-
Global vs local - Apply guards/interceptors at appropriate scope (global, controller, method)
-
Test thoroughly - Write unit tests for all guards and interceptors
-
Performance - Keep guards and interceptors lightweight
-
Logging - Use Logger service instead of console.log
-
Type safety - Use TypeScript generics for type-safe interceptors
NestJS Guards and Interceptors Common Pitfalls
-
Wrong execution order - Not understanding middleware → guards → interceptors → pipes flow
-
Forgetting async - Not using async when guards perform database operations
-
Missing error handling - Guards that don't throw appropriate exceptions
-
Interceptor mutation - Mutating data in interceptors instead of transforming
-
Circular dependencies - Guards that create circular dependency chains
-
Global scope issues - Applying too many global guards/interceptors hurts performance
-
Missing metadata - Forgetting to use Reflector to read custom metadata
-
Pipe placement - Using pipes in wrong order with validation
-
Exception filter scope - Not understanding filter precedence
-
Memory leaks - Not properly cleaning up subscriptions in interceptors
Resources
-
NestJS Guards Documentation
-
NestJS Interceptors Documentation
-
NestJS Pipes Documentation
-
NestJS Exception Filters
-
NestJS Middleware Documentation
-
NestJS Execution Context
-
RxJS Operators Guide
-
NestJS Custom Decorators