βš™οΈAdvanced Nest Patterns

Implement scalable, enterprise-grade architecture patterns and NestJS internals.

🧩 Custom Providers & Dynamic Modules

Welcome to Custom Providers & Dynamic Modules in NestJS! This chapter will guide you through advanced patterns for creating reusable and configurable modules using factory-based approaches. You'll learn how to leverage providers like useClass, useFactory, and useValue with custom tokens.

πŸ’‘ Understanding Custom Providers

In NestJS, providers are used to bind services to the container. Custom providers allow you to create more flexible and reusable modules by using factory functions or values.

  • Use Class Providers (useClass): Bind a service to a class implementation.
  • Use Factory Providers (useFactory): Create instances dynamically using factory functions.
  • Use Value Providers (useValue): Provide literal values or configurations.

βœ… Example: useClass Provider

import { Injectable } from '@nestjs/common';

@Injectable()
export class LoggerService {
  log(message: string) {
    console.log(`Logger: ${message}`);
  }
}

export const loggerProvider = {
  provide: 'LOGGER',
  useClass: LoggerService,
};

βœ… Example: useFactory Provider

export const cacheProvider = {
  provide: 'CACHE_CONFIG',
  useFactory: (config) => ({
    ttl: config.TTL,
    maxItems: config.MAX_ITEMS,
  }),
  inject: ['APP_CONFIG'],
};

πŸ’‘ Understanding Dynamic Modules

Dynamic modules allow you to create reusable and configurable modules that can be loaded at runtime. They use factory-based approaches to generate providers based on input configurations.

βœ… Example: Dynamic Module Configuration

import { Module } from '@nestjs/common';

export class CacheModule {
  static forRoot(config) {
    return {
      module: CacheModule,
      providers: [
        {
          provide: 'CACHE_CONFIG',
          useValue: config,
        },
      ],
    };
  }
}

πŸ’‘ Best Practices for Custom Providers & Dynamic Modules

  • Always use custom tokens (e.g., 'LOGGER') instead of class references for better control.
  • Keep factory functions simple and focused on a single responsibility.
  • Use useValue providers for configuration values that should be easily overridden.
  • Document your module's configuration options and expected types.

❌ Common Mistakes to Avoid

  • Don't use class references directly without proper abstraction.
  • Avoid creating overly complex factory functions that are hard to maintain.
  • Don't expose internal module configurations as public APIs.

πŸ’‘ Real-World Applications

  • Logging Modules: Configure different logging strategies based on environment.
  • Caching Modules: Create configurable cache providers for Redis, Memcached, etc.
  • External API Integrations: Build modules that accept multiple API configurations.

🏭 Interceptors, Middleware & Hooks

In this chapter, we'll explore interceptors, middleware, and hooks in NestJS. These powerful tools help you manage request/response lifecycle and keep your application clean and maintainable.

πŸ’‘ What are Interceptors?

  • Interceptors are a way to tap into the request/response lifecycle of your NestJS application.
  • They're similar to Express middleware but provide more fine-grained control over specific routes or services.
  • Use cases: logging, authentication, rate limiting, response transformation

βœ… Interceptor Example

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log('Request received');
    const start = Date.now();

    return next.handle().pipe(
      tap(() => {
        console.log(`Response sent in ${Date.now() - start}ms`);
      })
    );
  }
}

πŸ’‘ What is Middleware?

Middleware functions are used to perform tasks that are common across your application such as request logging, authentication, and error handling.

βœ… Middleware vs Interceptors

  • Middleware is typically used for global operations (affects all routes)
  • Interceptors are more targeted and can be applied to specific services
  • Middleware runs in the request phase only, while interceptors run on both request and response phases

πŸ’‘ What about Hooks?

Hooks allow you to execute code at specific points in the application lifecycle. Nest provides hooks for modules, controllers, and services.

❌ Common Mistakes to Avoid

  • Don't use middleware for tasks that would be better handled by interceptors (and vice versa)
  • Avoid creating bloated middleware that adds unnecessary delays to your requests
  • Don't forget to properly chain middleware using next()

πŸ’‘ Best Practices for Interceptors & Middleware

  • Use interceptors for service-specific logic
  • Reserve middleware for global operations
  • Keep your middleware and interceptors focused on a single responsibility
  • Always test performance impact when adding new middleware

Quiz

Question 1 of 10

Which provider type is used to bind a service to a class implementation?

  • useClass
  • useFactory
  • useValue
  • useToken