Skill v1.0.1
currentLLM-judged scan100/1004 files
version: "1.0.1" name: nestjs-guards-interceptors user-invocable: false description: Use when nestJS guards and interceptors for auth, logging, and transformation. Use when implementing cross-cutting concerns. allowed-tools:
- Bash
- Read
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 logicreturn !!request.headers.authorization;}}// ExecutionContext provides context about current request@Injectable()export class ContextAwareGuard implements CanActivate {canActivate(context: ExecutionContext): boolean {// Get HTTP contextconst httpContext = context.switchToHttp();const request = httpContext.getRequest();const response = httpContext.getResponse();// Get handler and class informationconst handler = context.getHandler();const controller = context.getClass();console.log(`Handler: ${handler.name}`);console.log(`Controller: ${controller.name}`);return true;}}// Usage in controllerimport { Controller, Get, UseGuards } from '@nestjs/common';@Controller('users')@UseGuards(BasicGuard)export class UserController {@Get()findAll() {return [];}@Get('profile')@UseGuards(ContextAwareGuard) // Method-level guardgetProfile() {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 requestrequest['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 firstconst token = this.extractTokenFromHeader(request);if (token) {try {const payload = await this.jwtService.verifyAsync(token);request['user'] = payload;return true;} catch {}}// Fall back to API keyconst 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 rolesexport enum Role {USER = 'user',ADMIN = 'admin',MODERATOR = 'moderator',}// Roles decoratorexport 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 authorizationexport 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 userequest['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 wrapperinterface 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 cacheconst cachedResponse = await this.cacheManager.get(cacheKey);if (cachedResponse) {return of(cachedResponse);}// Execute handler and cache resultreturn next.handle().pipe(tap(async (response) => {await this.cacheManager.set(cacheKey, response, 60000); // 60s TTL}),);}}// Conditional cachingexport 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 timeoutcatchError((err) => {if (err instanceof TimeoutError) {return throwError(() => new RequestTimeoutException());}return throwError(() => err);}),);}}// Dynamic timeout based on endpointexport 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 reportgenerateReport() {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 pipeimport { 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 filterasync 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 middlewareexport 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 moduleimport { 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