DEV Community

Cover image for Advanced NestJS: Hidden Gems for Senior Backend Engineers
Boyinbode Ebenezer Ayomide
Boyinbode Ebenezer Ayomide

Posted on

Advanced NestJS: Hidden Gems for Senior Backend Engineers

Most times when we think of NestJS, we picture decorators, dependency injection, and clean architecture. But beneath the surface lies a treasure trove of advanced features that can transform how you build scalable backend applications. After years of building enterprise-grade NestJS applications, I've discovered patterns and techniques that rarely make it into tutorials but are essential for senior-level development.


Custom Metadata Reflection: Building Intelligent Decorators

Standard decorators are limited in their ability to store and retrieve complex configuration data. You need a way to attach sophisticated metadata to classes and methods that can be accessed at runtime.

// metadata.constants.ts
export const CACHE_CONFIG_METADATA = Symbol('cache-config');
export const RATE_LIMIT_METADATA = Symbol('rate-limit');

// cache-config.decorator.ts
import { SetMetadata } from '@nestjs/common';

export interface CacheConfig {
  ttl: number;
  key?: string;
  condition?: (args: any[]) => boolean;
  tags?: string[];
}

export const CacheConfig = (config: CacheConfig) => 
  SetMetadata(CACHE_CONFIG_METADATA, config);

// Advanced usage with conditional caching
@CacheConfig({
  ttl: 300,
  key: 'user-profile-{{userId}}',
  condition: (args) => args[0]?.cacheEnabled !== false,
  tags: ['user', 'profile']
})
async getUserProfile(userId: string, options?: GetUserOptions) {
  // method implementation
}

Enter fullscreen mode Exit fullscreen mode

Smart Interceptor Using Metadata

// cache.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class SmartCacheInterceptor implements NestInterceptor {
  constructor(
    private reflector: Reflector,
    private cacheService: CacheService
  ) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const cacheConfig = this.reflector.get<CacheConfig>(
      CACHE_CONFIG_METADATA,
      context.getHandler()
    );

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

    const request = context.switchToHttp().getRequest();
    const args = context.getArgs();

    // Check condition
    if (cacheConfig.condition && !cacheConfig.condition(args)) {
      return next.handle();
    }

    // Generate cache key with template replacement
    const cacheKey = this.generateCacheKey(cacheConfig.key, request, args);

    // Try to get from cache
    const cachedResult = this.cacheService.get(cacheKey);
    if (cachedResult) {
      return of(cachedResult);
    }

    // Execute and cache result
    return next.handle().pipe(
      tap(result => {
        this.cacheService.set(cacheKey, result, {
          ttl: cacheConfig.ttl,
          tags: cacheConfig.tags
        });
      })
    );
  }

  private generateCacheKey(template: string, request: any, args: any[]): string {
    let key = template;

    // Replace template variables
    key = key.replace(/\{\{(\w+)\}\}/g, (match, prop) => {
      return request.params?.[prop] || 
             request.query?.[prop] || 
             args[0]?.[prop] || 
             match;
    });

    return key;
  }
}
Enter fullscreen mode Exit fullscreen mode

Advanced Execution Context Manipulation

The ExecutionContext is more powerful than you realize. Here's how to leverage it for sophisticated request handling.

// advanced-auth.guard.ts
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

export interface AuthContext {
  user?: any;
  permissions?: string[];
  rateLimit?: {
    remaining: number;
    resetTime: number;
  };
}

@Injectable()
export class AdvancedAuthGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
    private authService: AuthService,
    private rateLimitService: RateLimitService
  ) {}

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

    // Get handler and class metadata
    const handler = context.getHandler();
    const controllerClass = context.getClass();

    // Check if route is public
    const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [
      handler,
      controllerClass,
    ]);

    if (isPublic) {
      return true;
    }

    // Extract and validate token
    const token = this.extractTokenFromHeader(request);
    if (!token) {
      throw new UnauthorizedException('Token not found');
    }

    try {
      // Validate token and get user
      const user = await this.authService.validateToken(token);

      // Check rate limiting
      const rateLimitKey = `rate_limit:${user.id}:${handler.name}`;
      const rateLimit = await this.rateLimitService.checkLimit(rateLimitKey);

      if (!rateLimit.allowed) {
        response.set('X-RateLimit-Remaining', '0');
        response.set('X-RateLimit-Reset', rateLimit.resetTime.toString());
        throw new UnauthorizedException('Rate limit exceeded');
      }

      // Create enhanced auth context
      const authContext: AuthContext = {
        user,
        permissions: user.permissions,
        rateLimit: {
          remaining: rateLimit.remaining,
          resetTime: rateLimit.resetTime
        }
      };

      // Attach to request for use in downstream handlers
      request.authContext = authContext;

      // Set rate limit headers
      response.set('X-RateLimit-Remaining', rateLimit.remaining.toString());
      response.set('X-RateLimit-Reset', rateLimit.resetTime.toString());

      return true;
    } catch (error) {
      throw new UnauthorizedException('Invalid token');
    }
  }

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

Dynamic Module Factory Patterns

Creating modules that adapt their behavior based on runtime conditions is a powerful pattern for building flexible applications.

// feature-flag.interface.ts
export interface FeatureFlagConfig {
  provider: 'redis' | 'database' | 'memory';
  refreshInterval?: number;
  defaultFlags?: Record<string, boolean>;
  remoteConfig?: {
    url: string;
    apiKey: string;
  };
}

// feature-flag.module.ts
import { DynamicModule, Module, Provider } from '@nestjs/common';
import { FeatureFlagService } from './feature-flag.service';

@Module({})
export class FeatureFlagModule {
  static forRoot(config: FeatureFlagConfig): DynamicModule {
    const providers: Provider[] = [
      {
        provide: 'FEATURE_FLAG_CONFIG',
        useValue: config,
      },
    ];

    // Conditionally add providers based on configuration
    switch (config.provider) {
      case 'redis':
        providers.push({
          provide: 'FEATURE_FLAG_STORAGE',
          useClass: RedisFeatureFlagStorage,
        });
        break;
      case 'database':
        providers.push({
          provide: 'FEATURE_FLAG_STORAGE',
          useClass: DatabaseFeatureFlagStorage,
        });
        break;
      default:
        providers.push({
          provide: 'FEATURE_FLAG_STORAGE',
          useClass: MemoryFeatureFlagStorage,
        });
    }

    // Add remote config provider if configured
    if (config.remoteConfig) {
      providers.push({
        provide: 'REMOTE_CONFIG_CLIENT',
        useFactory: () => new RemoteConfigClient(config.remoteConfig),
      });
    }

    providers.push(FeatureFlagService);

    return {
      module: FeatureFlagModule,
      providers,
      exports: [FeatureFlagService],
      global: true,
    };
  }

  static forRootAsync(options: {
    useFactory: (...args: any[]) => Promise<FeatureFlagConfig> | FeatureFlagConfig;
    inject?: any[];
  }): DynamicModule {
    return {
      module: FeatureFlagModule,
      providers: [
        {
          provide: 'FEATURE_FLAG_CONFIG',
          useFactory: options.useFactory,
          inject: options.inject || [],
        },
        {
          provide: 'FEATURE_FLAG_STORAGE',
          useFactory: async (config: FeatureFlagConfig) => {
            switch (config.provider) {
              case 'redis':
                return new RedisFeatureFlagStorage();
              case 'database':
                return new DatabaseFeatureFlagStorage();
              default:
                return new MemoryFeatureFlagStorage();
            }
          },
          inject: ['FEATURE_FLAG_CONFIG'],
        },
        FeatureFlagService,
      ],
      exports: [FeatureFlagService],
      global: true,
    };
  }
}

// Usage in AppModule
@Module({
  imports: [
    FeatureFlagModule.forRootAsync({
      useFactory: (configService: ConfigService) => ({
        provider: configService.get('FEATURE_FLAG_PROVIDER') as 'redis' | 'database',
        refreshInterval: configService.get('FEATURE_FLAG_REFRESH_INTERVAL', 30000),
        remoteConfig: configService.get('FEATURE_FLAG_REMOTE_URL') ? {
          url: configService.get('FEATURE_FLAG_REMOTE_URL'),
          apiKey: configService.get('FEATURE_FLAG_API_KEY'),
        } : undefined,
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Request Scope Memory Management

Understanding REQUEST scope deeply is crucial for preventing memory leaks in high-traffic applications.

// user-context.service.ts
import { Injectable, Scope, OnModuleDestroy } from '@nestjs/common';
import { EventEmitter } from 'events';

@Injectable({ scope: Scope.REQUEST })
export class UserContextService implements OnModuleDestroy {
  private readonly eventEmitter = new EventEmitter();
  private readonly subscriptions: (() => void)[] = [];
  private userData: Map<string, any> = new Map();

  constructor() {
    // Set max listeners to prevent memory leak warnings
    this.eventEmitter.setMaxListeners(100);
  }

  setUserData(key: string, value: any): void {
    this.userData.set(key, value);
    this.eventEmitter.emit('userDataChanged', { key, value });
  }

  getUserData(key: string): any {
    return this.userData.get(key);
  }

  onUserDataChange(callback: (data: { key: string; value: any }) => void): void {
    this.eventEmitter.on('userDataChanged', callback);

    // Store cleanup function
    const cleanup = () => this.eventEmitter.off('userDataChanged', callback);
    this.subscriptions.push(cleanup);
  }

  // Critical: Clean up resources when request ends
  onModuleDestroy(): void {
    // Remove all event listeners
    this.subscriptions.forEach(cleanup => cleanup());
    this.eventEmitter.removeAllListeners();

    // Clear data
    this.userData.clear();

    console.log('UserContextService destroyed for request');
  }
}

// Usage with proper cleanup
@Injectable()
export class UserService {
  constructor(private userContext: UserContextService) {}

  async processUser(userId: string): Promise<void> {
    // This will be automatically cleaned up when request ends
    this.userContext.onUserDataChange((data) => {
      console.log(`User data changed: ${data.key} = ${data.value}`);
    });

    this.userContext.setUserData('userId', userId);
    this.userContext.setUserData('lastActivity', new Date());
  }
}
Enter fullscreen mode Exit fullscreen mode

Advanced Exception Filter Chaining

Create sophisticated error handling with hierarchical exception filters.

// base-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, Logger } from '@nestjs/common';
import { Request, Response } from 'express';

export interface ErrorContext {
  correlationId: string;
  userId?: string;
  endpoint: string;
  userAgent?: string;
  ip: string;
}

@Catch()
export abstract class BaseExceptionFilter implements ExceptionFilter {
  protected readonly logger = new Logger(this.constructor.name);

  abstract canHandle(exception: any): boolean;
  abstract handleException(exception: any, host: ArgumentsHost): void;

  catch(exception: any, host: ArgumentsHost): void {
    if (this.canHandle(exception)) {
      this.handleException(exception, host);
    } else {
      // Pass to next filter in chain
      this.delegateToNext(exception, host);
    }
  }

  protected delegateToNext(exception: any, host: ArgumentsHost): void {
    // This would be handled by the next filter in the chain
    // or the default NestJS exception handler
    throw exception;
  }

  protected createErrorContext(host: ArgumentsHost): ErrorContext {
    const ctx = host.switchToHttp();
    const request = ctx.getRequest<Request>();

    return {
      correlationId: request.headers['x-correlation-id'] as string || 
                    Math.random().toString(36).substring(7),
      userId: (request as any).authContext?.user?.id,
      endpoint: `${request.method} ${request.url}`,
      userAgent: request.headers['user-agent'],
      ip: request.ip,
    };
  }
}

// validation-exception.filter.ts
@Catch(ValidationException)
export class ValidationExceptionFilter extends BaseExceptionFilter {
  canHandle(exception: any): boolean {
    return exception instanceof ValidationException;
  }

  handleException(exception: ValidationException, host: ArgumentsHost): void {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const errorContext = this.createErrorContext(host);

    this.logger.warn('Validation error', {
      ...errorContext,
      errors: exception.getErrors(),
    });

    response.status(400).json({
      statusCode: 400,
      message: 'Validation failed',
      errors: exception.getErrors(),
      correlationId: errorContext.correlationId,
      timestamp: new Date().toISOString(),
    });
  }
}

// business-exception.filter.ts
@Catch(BusinessException)
export class BusinessExceptionFilter extends BaseExceptionFilter {
  canHandle(exception: any): boolean {
    return exception instanceof BusinessException;
  }

  handleException(exception: BusinessException, host: ArgumentsHost): void {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const errorContext = this.createErrorContext(host);

    this.logger.error('Business logic error', {
      ...errorContext,
      errorCode: exception.getErrorCode(),
      message: exception.message,
    });

    response.status(422).json({
      statusCode: 422,
      message: exception.message,
      errorCode: exception.getErrorCode(),
      correlationId: errorContext.correlationId,
      timestamp: new Date().toISOString(),
    });
  }
}

// global-exception.filter.ts
@Catch()
export class GlobalExceptionFilter extends BaseExceptionFilter {
  canHandle(exception: any): boolean {
    return true; // Global filter handles everything
  }

  handleException(exception: any, host: ArgumentsHost): void {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const errorContext = this.createErrorContext(host);

    // Handle different types of exceptions
    if (exception instanceof HttpException) {
      this.handleHttpException(exception, response, errorContext);
    } else {
      this.handleUnknownException(exception, response, errorContext);
    }
  }

  private handleHttpException(
    exception: HttpException, 
    response: Response, 
    context: ErrorContext
  ): void {
    const status = exception.getStatus();
    const exceptionResponse = exception.getResponse();

    this.logger.error('HTTP Exception', {
      ...context,
      status,
      response: exceptionResponse,
    });

    response.status(status).json({
      statusCode: status,
      message: exception.message,
      correlationId: context.correlationId,
      timestamp: new Date().toISOString(),
    });
  }

  private handleUnknownException(
    exception: any, 
    response: Response, 
    context: ErrorContext
  ): void {
    this.logger.error('Unhandled Exception', {
      ...context,
      error: exception.message,
      stack: exception.stack,
    });

    response.status(500).json({
      statusCode: 500,
      message: 'Internal server error',
      correlationId: context.correlationId,
      timestamp: new Date().toISOString(),
    });
  }
}

// Register filters in correct order
// main.ts
app.useGlobalFilters(
  new ValidationExceptionFilter(),
  new BusinessExceptionFilter(),
  new GlobalExceptionFilter(), // This should be last
);
Enter fullscreen mode Exit fullscreen mode

Advanced Health Check Orchestration

Build sophisticated health monitoring that goes beyond simple HTTP checks.

// health-check.service.ts
import { Injectable } from '@nestjs/common';
import { HealthIndicator, HealthIndicatorResult, HealthCheckError } from '@nestjs/terminus';

@Injectable()
export class AdvancedHealthIndicator extends HealthIndicator {
  constructor(
    private readonly databaseService: DatabaseService,
    private readonly redisService: RedisService,
    private readonly externalApiService: ExternalApiService,
  ) {
    super();
  }

  async checkDatabase(key: string): Promise<HealthIndicatorResult> {
    try {
      const startTime = Date.now();
      await this.databaseService.executeQuery('SELECT 1');
      const responseTime = Date.now() - startTime;

      const isHealthy = responseTime < 1000; // 1 second threshold

      const result = this.getStatus(key, isHealthy, {
        responseTime: `${responseTime}ms`,
        threshold: '1000ms',
        timestamp: new Date().toISOString(),
      });

      if (!isHealthy) {
        throw new HealthCheckError('Database response time too high', result);
      }

      return result;
    } catch (error) {
      throw new HealthCheckError('Database connection failed', {
        [key]: {
          status: 'down',
          error: error.message,
          timestamp: new Date().toISOString(),
        },
      });
    }
  }

  async checkRedis(key: string): Promise<HealthIndicatorResult> {
    try {
      const startTime = Date.now();
      await this.redisService.ping();
      const responseTime = Date.now() - startTime;

      const result = this.getStatus(key, true, {
        responseTime: `${responseTime}ms`,
        timestamp: new Date().toISOString(),
      });

      return result;
    } catch (error) {
      throw new HealthCheckError('Redis connection failed', {
        [key]: {
          status: 'down',
          error: error.message,
          timestamp: new Date().toISOString(),
        },
      });
    }
  }

  async checkExternalDependencies(key: string): Promise<HealthIndicatorResult> {
    const checks = await Promise.allSettled([
      this.checkExternalApi('payment-gateway', 'https://api.payment.com/health'),
      this.checkExternalApi('notification-service', 'https://api.notifications.com/health'),
      this.checkExternalApi('analytics-service', 'https://api.analytics.com/health'),
    ]);

    const results = checks.map((check, index) => ({
      name: ['payment-gateway', 'notification-service', 'analytics-service'][index],
      status: check.status === 'fulfilled' ? 'up' : 'down',
      details: check.status === 'fulfilled' ? check.value : check.reason,
    }));

    const failedServices = results.filter(r => r.status === 'down');
    const isHealthy = failedServices.length === 0;

    const result = this.getStatus(key, isHealthy, {
      services: results,
      failedCount: failedServices.length,
      totalCount: results.length,
      timestamp: new Date().toISOString(),
    });

    if (!isHealthy) {
      throw new HealthCheckError('External dependencies failing', result);
    }

    return result;
  }

  private async checkExternalApi(name: string, url: string): Promise<any> {
    const startTime = Date.now();
    const response = await fetch(url, { 
      method: 'GET',
      timeout: 5000 // 5 second timeout
    });
    const responseTime = Date.now() - startTime;

    return {
      name,
      status: response.ok ? 'up' : 'down',
      responseTime: `${responseTime}ms`,
      statusCode: response.status,
    };
  }
}

// health.controller.ts
@Controller('health')
export class HealthController {
  constructor(
    private health: HealthCheckService,
    private advancedHealth: AdvancedHealthIndicator,
  ) {}

  @Get()
  @HealthCheck()
  check() {
    return this.health.check([
      () => this.advancedHealth.checkDatabase('database'),
      () => this.advancedHealth.checkRedis('redis'),
    ]);
  }

  @Get('detailed')
  @HealthCheck()
  detailedCheck() {
    return this.health.check([
      () => this.advancedHealth.checkDatabase('database'),
      () => this.advancedHealth.checkRedis('redis'),
      () => this.advancedHealth.checkExternalDependencies('external-services'),
    ]);
  }

  @Get('readiness')
  @HealthCheck()
  readinessCheck() {
    // More strict checks for readiness
    return this.health.check([
      () => this.advancedHealth.checkDatabase('database'),
      () => this.advancedHealth.checkRedis('redis'),
      () => this.advancedHealth.checkExternalDependencies('external-services'),
    ]);
  }

  @Get('liveness')
  @HealthCheck()
  livenessCheck() {
    // Basic checks for liveness (pod restart criteria)
    return this.health.check([
      () => this.advancedHealth.checkDatabase('database'),
    ]);
  }
}
Enter fullscreen mode Exit fullscreen mode

Provider Overriding in Tests: Surgical Test Isolation

Advanced testing patterns that provide precise control over dependencies.

// user.service.spec.ts
describe('UserService', () => {
  let service: UserService;
  let app: TestingModule;
  let mockUserRepository: jest.Mocked<UserRepository>;
  let mockEventEmitter: jest.Mocked<EventEmitter2>;
  let mockCacheService: jest.Mocked<CacheService>;

  beforeEach(async () => {
    // Create sophisticated mocks
    mockUserRepository = {
      save: jest.fn(),
      findOne: jest.fn(),
      update: jest.fn(),
      delete: jest.fn(),
      find: jest.fn(),
    } as any;

    mockEventEmitter = {
      emit: jest.fn(),
      on: jest.fn(),
      off: jest.fn(),
    } as any;

    mockCacheService = {
      get: jest.fn(),
      set: jest.fn(),
      del: jest.fn(),
      reset: jest.fn(),
    } as any;

    app = await Test.createTestingModule({
      imports: [
        // Import actual modules but override specific providers
        DatabaseModule,
        CacheModule,
        EventEmitterModule.forRoot(),
      ],
      providers: [
        UserService,
        NotificationService,
      ],
    })
    // Override specific providers surgically
    .overrideProvider(getRepositoryToken(User))
    .useValue(mockUserRepository)

    .overrideProvider(EventEmitter2)
    .useValue(mockEventEmitter)

    .overrideProvider(CACHE_TOKENS.SESSION_CACHE)
    .useValue(mockCacheService)

    // Override guards for testing without authentication
    .overrideGuard(JwtAuthGuard)
    .useValue({ canActivate: () => true })

    // Override interceptors to disable caching during tests
    .overrideInterceptor(CacheInterceptor)
    .useValue({ intercept: (context, next) => next.handle() })

    .compile();

    service = app.get<UserService>(UserService);
  });

  describe('createUser', () => {
    it('should create user and emit event', async () => {
      // Arrange
      const userData = { email: 'test@example.com', name: 'Test User' };
      const createdUser = { id: '1', ...userData };
      mockUserRepository.save.mockResolvedValue(createdUser);

      // Act
      const result = await service.createUser(userData);

      // Assert
      expect(mockUserRepository.save).toHaveBeenCalledWith(userData);
      expect(mockEventEmitter.emit).toHaveBeenCalledWith('user.created', {
        userId: '1',
        email: 'test@example.com',
        preferences: undefined
      });
      expect(result).toEqual(createdUser);
    });

    it('should handle cache failure gracefully', async () => {
      // Arrange
      const userData = { email: 'test@example.com', name: 'Test User' };
      const createdUser = { id: '1', ...userData };
      mockUserRepository.save.mockResolvedValue(createdUser);
      mockCacheService.set.mockRejectedValue(new Error('Cache unavailable'));

      // Act & Assert - should not throw
      const result = await service.createUser(userData);
      expect(result).toEqual(createdUser);
    });
  });

  // Test with different provider overrides per test
  describe('with different cache configuration', () => {
    beforeEach(async () => {
      // Override with different cache implementation
      await app.close();

      app = await Test.createTestingModule({
        imports: [UserModule],
      })
      .overrideProvider(CACHE_TOKENS.SESSION_CACHE)
      .useFactory({
        factory: () => new MemoryCacheService({ maxSize: 10 }),
      })
      .compile();

      service = app.get<UserService>(UserService);
    });

    it('should work with memory cache', async () => {
      // Test implementation with actual memory cache
    });
  });

  afterEach(async () => {
    await app.close();
  });
});
Enter fullscreen mode Exit fullscreen mode

The key insight is that NestJS provides the primitives, but senior engineers know how to compose them into powerful, maintainable systems. These patterns have been battle-tested in production environments handling millions of requests.
Master these techniques, and you'll find yourself building more robust, scalable, and maintainable backend applications that can handle enterprise-level complexity with ease.

Let's Connect

Linkedln | Github | Twitter |

Top comments (0)