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.
ClassSerializerInterceptorplus@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@Rolesinto one@Auth()so controllers stay readable. - Don't inject the request everywhere. Reach for
AsyncLocalStorageto 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
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.
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.
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.
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