Published on

Notes on NestJS

Authors

1. Project Structure & Core Concepts

1.1 Modules (@Module())

The fundamental organizational unit in NestJS. Modules are classes decorated with @Module(). They group related components (controllers, providers, other modules) and define their relationships.

  • Properties:

    • imports: An array of modules that are required by the current module. Exported providers from these imported modules become available.
    • controllers: An array of controllers defined within this module. These controllers handle incoming requests.
    • providers: An array of injectable classes (services, repositories, factories, helpers) that will be instantiated by the NestJS injector. These are local to the module unless exported.
    • exports: A subset of providers (or imported modules) that should be available for other modules that import this one. This is crucial for creating reusable modules.
  • Types of Modules:

    • Root Module (e.g., AppModule): The starting point of your application, bootstrapped by NestFactory.create(). It typically imports feature modules.
    • Feature Modules: Group logic for a specific feature (e.g., UsersModule, ProductsModule). They encapsulate controllers, services, and potentially other modules related to that feature.
    • Shared Modules: Modules designed to be imported by multiple other modules, exporting commonly used providers (e.g., a DatabaseModule exporting a UserRepository).
    • Global Modules (@Global()): Decorator applied to a module. Once a global module is registered (typically by being imported by the root module), its exports are available everywhere without needing to be re-imported in subsequent modules. Use sparingly to avoid tight coupling.
    • Dynamic Modules (forRoot(), forFeature()): Modules that can be configured programmatically at runtime. They return a DynamicModule object. Common for configuring third-party libraries (e.g., ConfigModule.forRoot(), TypeOrmModule.forFeature([])).

1.2 Controllers (@Controller())

Classes responsible for handling incoming HTTP requests and sending back responses. They define the endpoint routes.

  • Decorator Usage:
    • @Controller('prefix'): Defines a base path for all routes within the controller. If no prefix is provided, it defaults to /.
    • @Get(), @Post(), @Put(), @Delete(), @Patch(), @Options(), @Head(), @All(): Method decorators mapping HTTP methods to specific handler functions.
  • Request Parameter Decorators:
    • @Param('key'): Extracts a specific route parameter (e.g., /users/:id).
    • @Query('key'): Extracts a specific query parameter (e.g., /users?name=john).
    • @Body(): Extracts the entire request body.
    • @Body('key'): Extracts a specific property from the request body.
    • @Headers('key'): Extracts a specific header.
    • @Ip(), @Session(), @HostParam(), @User().
    • @Req(), @Res(): Access the underlying platform-specific request/response objects (Express req/res or Fastify request/reply). Using @Res() bypasses Nest's standard response handling.
  • Data Transfer Objects (DTOs):
    • Plain TypeScript classes used to define the shape of data passed through the network, typically for request bodies (@Body()).
    • Crucial for defining validation rules using class-validator decorators.

1.3 Providers (@Injectable())

Classes decorated with @Injectable() that can be injected into other classes (controllers, services, other providers) using Nest's Dependency Injection (DI) system.

  • Core Principle: Dependency Injection: Nest automatically resolves and provides instances of dependencies declared in a class's constructor. This promotes loose coupling and testability.

  • Services: The most common type of provider. Encapsulate business logic, data access (e.g., database interactions), and complex operations. They are designed to be reusable across controllers or other services.

  • Custom Providers: Beyond simple class instantiation, Nest allows fine-grained control over how providers are created:

    • useClass: Provide a custom class that will be instantiated as the provider for a specific token (e.g., an interface).

      { provide: 'CONNECTION', useClass: PostgreSQLConnection }
      
    • useValue: Provide a static, already-created value. Useful for injecting configuration objects or mock objects during testing.

      { provide: 'API_KEY', useValue: 'some-api-key' }
      
    • useFactory: Provide a value based on a factory function. This allows for dynamic creation of providers and injection of other providers into the factory function.

      {
        provide: 'DATABASE_CONNECTION',
        useFactory: (configService: ConfigService) => { /* ... create connection ... */ },
        inject: [ConfigService],
      }
      
    • useExisting: Create an alias for an existing provider.

      { provide: 'ALIEN_CONNECTION', useExisting: 'DATABASE_CONNECTION' }
      
  • Scopes: Determine how provider instances are managed:

    • DEFAULT (Singleton): Default behavior. Only one instance of the provider is created and reused throughout the application's lifecycle.
    • REQUEST: A new instance of the provider is created for each incoming request. Useful for request-scoped data like user authentication context.
    • TRANSIENT: A new instance of the provider is created every time it is injected into another class. This provides the least sharing.

1.4 Lifecycle Hooks

Interfaces that expose methods called at specific points during the application's lifecycle.

  • OnModuleInit: Called once the host module has been initialized.
  • OnApplicationBootstrap: Called once the entire application has been initialized and listening for connections.
  • OnModuleDestroy: Called when the host module is being destroyed.
  • BeforeApplicationShutdown: Called just before the application shuts down.
  • OnApplicationShutdown: Called after the application has shut down.
  • OnModuleInit (for services etc.), OnApplicationBootstrap (for overall app startup logic like seeding), OnApplicationShutdown (for graceful shutdown and resource cleanup) are commonly used.

2. Request Processing Pipeline

NestJS processes requests through a defined pipeline, allowing for modular and structured handling of concerns like authentication, validation, and error management.

  • Order of execution: Middleware -> Guards -> Interceptors (pre-controller) -> Pipes -> Controller Handler -> Interceptors (post-controller/response transformation) -> Exception Filters.

2.1 Middleware (NestMiddleware or function)

Functions that are executed before the route handler. Similar to Express middleware.

  • Implementation:

    • Implement NestMiddleware interface with a use() method.
    • Or use a simple function (req, res, next) => { ... }.
  • Registration: Configured in the module's configure() method.

    import { MiddlewareConsumer, NestModule, RequestMethod } from '@nestjs/common'
    // ...
    export class AppModule implements NestModule {
      configure(consumer: MiddlewareConsumer) {
        consumer
          .apply(LoggerMiddleware)
          .exclude(
            // Optionally exclude paths
            { path: 'cats', method: RequestMethod.GET },
            'cats/(.*)'
          )
          .forRoutes('cats', 'users') // Apply to specific routes/controllers
        // .forRoutes({ path: 'ab*cd', method: RequestMethod.ALL }); // Wildcards
      }
    }
    
  • Use Cases: Logging, authentication (e.g., parsing JWT before Passport), request body parsing, security headers.

2.2 Guards (CanActivate interface)

Determine whether a given request should be handled by the route handler. They implement the CanActivate interface and have a canActivate() method that returns a boolean or a promise/observable of a boolean.

  • Application: Applied using @UseGuards(). Can be applied globally, at the controller level, or at the method level.
  • Key Method: canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean>
    • ExecutionContext: Provides access to the current request context (e.g., context.switchToHttp().getRequest()).
  • Use Cases: Authentication (e.g., AuthGuard from @nestjs/passport), authorization (e.g., RolesGuard checking user roles).
  • Reflector: A utility class to read metadata set by @SetMetadata() decorators (e.g., defining roles on a route for a RolesGuard to read).

2.3 Interceptors (NestInterceptor interface)

Allow you to bind extra logic before and/or after method execution. They are a powerful feature for cross-cutting concerns, leveraging RxJS.

  • Application: Applied using @UseInterceptors(). Can be applied globally, at the controller level, or at the method level.
  • Key Method: intercept(context: ExecutionContext, next: CallHandler): Observable<any>
    • next.handle(): An Observable representing the route handler's execution. You manipulate this observable using RxJS operators.
  • Use Cases:
    • Transforming Responses: Map the result of a route handler before sending it back to the client.
    • Logging: Log request details, execution time, and response.
    • Caching: Intercept requests to serve cached data or cache responses.
    • Error Mapping: Catch exceptions and transform them into a standardized format.
    • Extending Controller Logic: Add additional logic before or after a method call without modifying the method itself.

2.4 Pipes (PipeTransform interface)

Provide a way to transform input data (e.g., from query, params, body) or validate it.

  • Application: Applied using @UsePipes(). Can be applied globally, at the controller level, method level, or parameter level.
  • Key Method: transform(value: any, metadata: ArgumentMetadata)
  • Built-in Pipes:
    • ValidationPipe: Automatically validates incoming data based on class-validator decorators on DTOs. Throws BadRequestException on failure. It also uses class-transformer for type conversion.
    • ParseIntPipe: Converts a string parameter to an integer.
    • ParseUUIDPipe: Validates if a parameter is a UUID.
    • ParseBoolPipe, ParseArrayPipe, ParseEnumPipe.
  • Custom Pipes: Create your own transformation or validation logic.

2.5 Exception Filters (ExceptionFilter interface)

Catch unhandled exceptions that occur in your application and transform them into appropriate HTTP responses.

  • Application: Applied using @UseFilters(). Can be applied globally, at the controller level, or at the method level.
  • Key Method: catch(exception: T, host: ArgumentsHost)
  • Handling:
    • Nest's default exception filter handles HttpException and its subclasses (e.g., NotFoundException, BadRequestException).
    • You can create custom filters to catch specific exceptions or all exceptions (@Catch()).
  • Use Cases: Standardizing error responses, logging specific error details, handling third-party library errors.

3. Advanced Features

3.1 Custom Decorators

NestJS allows creating custom parameter, method, or class decorators to abstract common logic or metadata assignment.

  • createParamDecorator: For custom parameter decorators (e.g., @User(), @Ip()).

    import { createParamDecorator, ExecutionContext } from '@nestjs/common'
    export const User = createParamDecorator((data: unknown, ctx: ExecutionContext) => {
      const request = ctx.switchToHttp().getRequest()
      return data ? request.user[data] : request.user
    })
    
  • @SetMetadata() / Reflector: To attach custom metadata to routes or classes, which can then be read by Guards or Interceptors.

    // roles.decorator.ts
    import { SetMetadata } from '@nestjs/common'
    export const Roles = (...roles: string[]) => SetMetadata('roles', roles)
    
    // roles.guard.ts
    import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'
    import { Reflector } from '@nestjs/core'
    @Injectable()
    export class RolesGuard implements CanActivate {
      constructor(private reflector: Reflector) {}
      canActivate(context: ExecutionContext): boolean {
        const roles = this.reflector.get<string[]>('roles', context.getHandler())
        // ... logic to check user roles against 'roles'
      }
    }
    

3.2 CLI (@nestjs/cli)

The NestJS CLI is a powerful tool for scaffolding projects, generating components, and running the application.

  • nest new <project-name>: Scaffolds a new NestJS project.
  • nest g <schematic> <name>: Generates components (e.g., nest g module users, nest g controller auth, nest g service products).
  • nest start, nest build, nest test.

3.3 Configuration (@nestjs/config)

Module for managing application configuration, based on dotenv.

  • ConfigModule.forRoot(): Loads environment variables from .env files. Can specify paths, ignore dotenv, and validate schema.
  • ConfigService: Injected provider to access configuration values (configService.get('DATABASE_URL')).
  • Supports partial registration and schema validation using Joi or class-validator.

3.4 Logging (@nestjs/common/Logger)

Built-in Logger class for structured logging.

  • Provides log(), error(), warn(), debug(), verbose() methods.
  • Can be injected and customized. Supports custom loggers.

3.5 Validation (class-validator & class-transformer)

Key libraries for robust data validation and transformation.

  • class-validator: Uses decorators (@IsString(), @IsNumber(), @IsEmail(), @ValidateNested()) on DTO properties to define validation rules.
  • class-transformer: Transforms plain JavaScript objects to instances of classes and vice versa. Essential for ValidationPipe to work correctly (e.g., creating DTO instances from raw request body).
  • Typically used together with ValidationPipe.

3.6 Authentication (Passport)

Nest provides a dedicated integration with Passport.js for various authentication strategies.

  • @nestjs/passport: Provides Passport strategies as Nest guards (AuthGuard).
  • @nestjs/jwt: Integration for JSON Web Tokens.
  • Common Flow:
    1. User credentials sent to a login endpoint.
    2. AuthGuard('local') uses LocalStrategy to validate credentials.
    3. If valid, a JWT is generated and returned to the client.
    4. Subsequent requests include the JWT in the Authorization header.
    5. AuthGuard('jwt') uses JwtStrategy to validate the token and extract user information.
    6. User object is attached to the request (req.user).

3.7 Database Integration

Nest provides specific modules for popular ORMs/ODMs.

  • @nestjs/typeorm: For TypeORM (SQL databases). Use TypeOrmModule.forRoot() for global connection and TypeOrmModule.forFeature([Entity1, Entity2]) in feature modules to register entities.
  • @nestjs/mongoose: For Mongoose (MongoDB). Similar MongooseModule.forRoot() and MongooseModule.forFeature().
  • @nestjs/sequelize: For Sequelize (SQL databases).
  • @nestjs/prisma: For Prisma ORM (via client generation).

3.8 HTTP Module (@nestjs/axios)

Wrapper around the popular Axios library for making HTTP requests to external services.

  • Provides an HttpService that can be injected into providers.

3.9 Task Scheduling (@nestjs/schedule)

Allows for defining cron jobs, timeouts, and intervals.

  • ScheduleModule.forRoot()

  • Decorators: @Cron('cron expression'), @Interval(ms), @Timeout(ms).

  • Example:

    @Injectable()
    export class TasksService {
      @Cron('0 * * * * *') // Every minute
      handleCron() {
        console.log('Task executed every minute!')
      }
    }
    

3.10 Caching (@nestjs/cache-manager)

Integration with cache-manager to easily add caching capabilities.

  • CacheModule.register(): Configures the cache store (in-memory, Redis, Memcached).
  • CacheInterceptor: An interceptor that handles caching logic for routes.
  • @CacheKey('key'), @CacheTTL(seconds): Decorators to customize cache key and time-to-live per route.
  • CacheManager: Injected to programmatically interact with the cache.

3.11 Serialization (class-transformer)

Used for transforming plain objects to class instances and vice versa. Critical for DTO validation and controlling output.

  • @Expose(): Marks a property as exposed during serialization (e.g., when converting a class instance to a plain JSON object for response).
  • @Exclude(): Marks a property as excluded during serialization (useful for hiding sensitive data like passwords).
  • @Type(() => ClassName): Used for nested objects to ensure class-transformer knows which class to instantiate for that property.

3.12 Versioning

NestJS supports various API versioning strategies.

  • URI Versioning: /@Controller({ version: '1' }) or forRoutes({ path: 'users', version: '1' })
  • Header Versioning: Based on a custom header (e.g., X-API-Version).
  • Media Type Versioning: Based on the Accept header.
  • Query Parameter Versioning: Based on a query parameter (e.g., ?v=1).

4. Specific Application Types

4.1 Microservices

NestJS provides built-in support for building scalable, decoupled microservices.

  • @nestjs/microservices: Core package.
  • Transporters: Define the communication protocol between services.
    • TCP (default, simple, fast for internal networks).
    • Redis (pub/sub, often used for event-based communication).
    • Kafka, RabbitMQ (AMQP), NATS (robust message brokers for high-throughput, guaranteed delivery).
    • gRPC (high-performance, uses Protocol Buffers).
  • Microservice Server: NestFactory.createMicroservice(AppModule, { transport: Transport.TCP, options: { port: 3001 } });
  • Client (ClientProxy): Used to communicate with microservices from an API gateway or another microservice.
    • Configured via ClientsModule.register().
    • client.send('pattern', payload): Request-response communication.
    • client.emit('event', payload): Event-based (one-way) communication.
  • Patterns:
    • @MessagePattern('cmd'): Maps incoming messages to a handler for request-response communication.
    • @EventPattern('event'): Maps incoming events to a handler for event-based communication.
  • API Gateway: A common pattern where an HTTP API acts as a facade, forwarding requests to appropriate microservices.

4.2 WebSockets (@nestjs/platform-socket.io, @nestjs/platform-ws)

Real-time bidirectional communication.

  • Gateways (@WebSocketGateway()): Classes that define WebSocket event handlers.
    • @WebSocketGateway(): Can specify port, namespace, or adapter.
    • @SubscribeMessage('event-name'): Maps incoming WebSocket messages to a handler method.
    • @ConnectedSocket(): Injects the client socket instance.
    • @MessageBody(): Injects the message payload.
  • WsAdapter: Allows choosing between socket.io (default) or native ws (WebSockets) for implementation.

4.3 GraphQL (@nestjs/graphql)

Integration with Apollo Server for building GraphQL APIs.

  • Approach:
    • Code-first (Recommended for TS): Define schema using TypeScript classes and decorators. Nest CLI can generate SDL.
      • @ObjectType(): Defines a GraphQL object type.
      • @InputType(): Defines a GraphQL input type.
      • @Field(): Defines a field within an object/input type.
      • @Query(), @Mutation(), @Subscription(): Define GraphQL operations.
      • @Args(): Extracts arguments for queries/mutations.
      • @Resolver(): Maps GraphQL types to their respective resolver classes.
    • Schema-first: Define schema using GraphQL Schema Definition Language (SDL), then implement resolvers separately.
  • GraphQLModule.forRoot(): Configures the GraphQL server.
  • @nestjs/apollo: Integration with Apollo Server.

4.4 CQRS & Event Sourcing (@nestjs/cqrs)

Provides tools for implementing Command Query Responsibility Segregation (CQRS) and Event Sourcing patterns.

  • CQRS: Separates write operations (commands) from read operations (queries) for better scalability, maintainability, and domain modeling.
  • Event Sourcing: Persists all changes to application state as a sequence of immutable events.
  • Key Components:
    • CommandBus: Dispatches commands to their handlers.
    • QueryBus: Dispatches queries to their handlers.
    • EventBus: Publishes events to their subscribers.
    • @CommandHandler(), @QueryHandler(), @EventHandler(): Decorators to map handlers to commands, queries, and events.
    • AggregateRoot: Base class for domain aggregates that emit events.

5. Testing

NestJS is built with testability in mind, making it easy to perform unit and end-to-end (E2E) tests.

  • Jest: Default testing framework.
  • Unit Tests: Test individual components (e.g., a service, a controller method) in isolation, mocking their dependencies.
  • E2E Tests: Test the full application flow, simulating HTTP requests and verifying responses.
  • Test.createTestingModule(): The core utility for creating isolated testing modules.
    • Allows overriding or mocking providers, controllers, and modules for specific test cases.
    • compile(): Compiles the testing module.
    • get(): Retrieves an instance of a provider or controller from the compiled module.
    • init(), close(): Lifecycle methods for E2E tests to start and stop the application.

6. Monorepos

Nest CLI supports creating monorepos, allowing you to manage multiple NestJS applications and reusable libraries within a single Git repository.

  • nest new <project-name> --monorepo: Initializes a new monorepo workspace.
  • Applications (apps/): Executable NestJS projects (e.g., an API gateway, a microservice).
  • Libraries (libs/): Non-executable NestJS modules or utility code that can be shared across multiple applications or other libraries within the monorepo.
    • nest g app <name>: Generates a new application.
    • nest g lib <name>: Generates a new library.
  • The CLI automatically configures TypeScript paths in tsconfig.json for easy importing of libraries (@app/my-lib).
  • Promotes code reuse, consistent tooling, and simplified dependency management across related projects.