Senior Tips & Tricks

A grab-bag of habits that separate production-grade Nest apps from tutorials: lifecycle hooks, graceful shutdown, and more.

The last mile of NestJS mastery is a set of small habits. None is hard; together they mark code written by someone who has run Nest in production.

Lifecycle hooks

Providers can implement OnModuleInit (warm a cache, verify a connection) and OnModuleDestroy / OnApplicationShutdown (flush buffers, close pools). Call app.enableShutdownHooks() in main.ts so SIGTERM from your orchestrator triggers them - the difference between a clean rollout and dropped requests.

@Injectable()
export class Cache implements OnModuleInit {
  async onModuleInit() {
    await this.warm(); // run once, at startup
  }
}

A handful of high-leverage habits

  • Global validation, always. new ValidationPipe({ whitelist: true, transform: true }) app-wide stops a whole class of bugs and injection vectors.
  • Serialize with intent. ClassSerializerInterceptor plus @Exclude() on entity fields keeps password hashes out of responses.
  • Correlation ids. Stamp every request with an id in middleware and log it everywhere - your future self debugging prod will thank you.
  • Prefer composition decorators. applyDecorators() bundles @UseGuards, @ApiBearerAuth, and @Roles into one @Auth() so controllers stay readable.
  • Don't inject the request everywhere. Reach for AsyncLocalStorage to carry request context without request-scoping your whole graph.

Health checks and observability

@nestjs/terminus gives you /health endpoints for liveness and readiness probes. Pair it with structured logging (pino) and you have the observability a real deployment needs.

The meta-lesson

Nest rewards discipline. Keep controllers thin, push logic into services, validate at the edges, and let the DI container - not new - wire everything. Do that consistently and the framework's structure scales with your team.

Example

Example · typescript
import {
  Injectable,
  OnModuleInit,
  OnApplicationShutdown,
  applyDecorators,
  UseGuards,
  SetMetadata,
} from '@nestjs/common';

// 1. Lifecycle hooks for warmup + graceful shutdown.
@Injectable()
export class DbPool implements OnModuleInit, OnApplicationShutdown {
  async onModuleInit() {
    console.log('opening pool...');
  }
  async onApplicationShutdown(signal?: string) {
    console.log(`draining pool on ${signal}`);
  }
}

// 2. A composition decorator: one @Auth() bundles several concerns.
declare class JwtAuthGuard {}
declare class RolesGuard {}
export const ROLES_KEY = 'roles';

export function Auth(...roles: string[]) {
  return applyDecorators(
    SetMetadata(ROLES_KEY, roles),
    UseGuards(JwtAuthGuard, RolesGuard),
    // could also add @ApiBearerAuth() for Swagger
  );
}

// Usage — readable and consistent across every protected route:
// @Auth('admin')
// @Delete(':id')
// remove(@Param('id') id: string) { ... }

// 3. In main.ts, opt into graceful shutdown:
// const app = await NestFactory.create(AppModule);
// app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
// app.enableShutdownHooks(); // SIGTERM -> onApplicationShutdown
// await app.listen(3000);

When to use it

  • A developer implements OnModuleInit and OnModuleDestroy lifecycle hooks on a custom connection provider to manage resource setup and cleanup.
  • A team enables graceful shutdown so in-flight HTTP requests complete before the process exits when Kubernetes sends a SIGTERM signal.
  • An engineer registers a custom Logger in bootstrap to replace the default Nest logger with a structured JSON logger (e.g., Pino) for production log aggregation.

More examples

OnModuleInit and OnModuleDestroy

Implements the OnModuleInit and OnModuleDestroy lifecycle hooks to open and close a queue connection alongside the NestJS module lifecycle.

Example · ts
import { Injectable, OnModuleInit, OnModuleDestroy, Logger } from '@nestjs/common';

@Injectable()
export class QueueService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(QueueService.name);

  async onModuleInit() {
    await this.connect();
    this.logger.log('Queue connected');
  }

  async onModuleDestroy() {
    await this.disconnect();
    this.logger.log('Queue disconnected gracefully');
  }

  private connect() { /* ... */ }
  private disconnect() { /* ... */ }
}

Graceful shutdown configuration

Enables shutdown hooks so OnModuleDestroy lifecycle methods run when the process receives SIGTERM, allowing graceful cleanup before exit.

Example · ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Enable Nest's built-in shutdown hooks
  app.enableShutdownHooks();

  // Give in-flight requests 5s to complete before exiting
  process.on('SIGTERM', async () => {
    await app.close();
    process.exit(0);
  });

  await app.listen(3000);
}
bootstrap();

Custom logger in bootstrap

Replaces the default NestJS logger with nestjs-pino for structured JSON logging, using bufferLogs to capture bootstrap-time logs as well.

Example · ts
import { NestFactory } from '@nestjs/core';
import { Logger } from 'nestjs-pino';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, { bufferLogs: true });
  app.useLogger(app.get(Logger));  // replace built-in logger with Pino
  app.flushLogs();
  await app.listen(3000);
}
bootstrap();

Discussion

  • Be the first to comment on this lesson.