lifecycle
NestJS Lifecycle Events
When to Use This Skill
Use this skill when:
- Implementing initialization logic for providers or modules
- Setting up database connections at startup
- Cleaning up resources at shutdown
- Running code after application bootstrap
- Gracefully shutting down services
- Managing connection pools, caches, or external services
- Implementing health checks on startup
- Registering event listeners or schedulers
- Pre-warming caches or performing initial data loads
- Handling application lifecycle in microservices
What are Lifecycle Events?
Lifecycle events (hooks) are methods that are called at specific points during the application lifecycle. They allow you to run initialization, cleanup, and shutdown logic at the appropriate times.
Application Lifecycle Order
Application Initialization
↓
1. onModuleInit() - Module initialized
↓
2. onApplicationBootstrap() - Application fully started
↓
Application Running...
↓
3. onModuleDestroy() - Module cleanup (on shutdown signal)
↓
4. beforeApplicationShutdown() - Before app shutdown
↓
5. onApplicationShutdown() - Application shutting down
↓
Application Terminated
Lifecycle Hooks Overview
| Hook | Called When | Use For |
|---|---|---|
onModuleInit() |
Module dependencies resolved | Initialize connections, setup |
onApplicationBootstrap() |
After all modules initialized | Final setup, pre-warming |
onModuleDestroy() |
Before module destroyed | Cleanup module resources |
beforeApplicationShutdown() |
Before shutdown signal | Critical cleanup operations |
onApplicationShutdown() |
After shutdown signal received | Final cleanup |
OnModuleInit
Called after the module's dependencies have been resolved but before the application is fully bootstrapped.
import { Injectable, OnModuleInit } from '@nestjs/common';
@Injectable()
export class CatsService implements OnModuleInit {
onModuleInit() {
console.log('CatsService initialized');
}
}
Async Initialization
import { Injectable, OnModuleInit } from '@nestjs/common';
@Injectable()
export class DatabaseService implements OnModuleInit {
private connection: Connection;
async onModuleInit() {
this.connection = await createConnection({
host: 'localhost',
port: 5432,
});
console.log('Database connection established');
}
}
Use Cases
- Establishing database connections
- Initializing cache connections (Redis, Memcached)
- Setting up message queue connections
- Loading configuration
- Registering event listeners
- Validating environment variables
Complete Example
import { Injectable, OnModuleInit, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class RedisService implements OnModuleInit {
private client: RedisClient;
private readonly logger = new Logger(RedisService.name);
constructor(private configService: ConfigService) {}
async onModuleInit() {
try {
this.client = await createRedisClient({
host: this.configService.get('REDIS_HOST'),
port: this.configService.get('REDIS_PORT'),
});
this.logger.log('Redis connection established');
this.client.on('error', (err) => {
this.logger.error('Redis error:', err);
});
} catch (error) {
this.logger.error('Failed to connect to Redis', error);
throw error;
}
}
getClient(): RedisClient {
return this.client;
}
}
OnApplicationBootstrap
Called after all modules have been initialized, right before the application starts listening for connections.
import { Injectable, OnApplicationBootstrap } from '@nestjs/common';
@Injectable()
export class CatsService implements OnApplicationBootstrap {
onApplicationBootstrap() {
console.log('Application has fully started');
}
}
Async Bootstrap
import { Injectable, OnApplicationBootstrap, Logger } from '@nestjs/common';
@Injectable()
export class CacheWarmupService implements OnApplicationBootstrap {
private readonly logger = new Logger(CacheWarmupService.name);
constructor(
private cacheService: CacheService,
private dataService: DataService,
) {}
async onApplicationBootstrap() {
this.logger.log('Warming up cache...');
const data = await this.dataService.getFrequentlyAccessedData();
await this.cacheService.set('popular_items', data);
this.logger.log('Cache warmup completed');
}
}
Use Cases
- Pre-warming caches
- Running health checks
- Registering scheduled tasks
- Starting background workers
- Performing initial data synchronization
- Sending application startup notifications
- Initializing feature flags
Complete Example
import {
Injectable,
OnApplicationBootstrap,
Logger,
} from '@nestjs/common';
import { SchedulerRegistry } from '@nestjs/schedule';
import { CronJob } from 'cron';
@Injectable()
export class TaskSchedulerService implements OnApplicationBootstrap {
private readonly logger = new Logger(TaskSchedulerService.name);
constructor(
private schedulerRegistry: SchedulerRegistry,
private taskService: TaskService,
) {}
async onApplicationBootstrap() {
this.logger.log('Initializing scheduled tasks...');
const job = new CronJob('0 0 * * *', () => {
this.taskService.runDailyCleanup();
});
this.schedulerRegistry.addCronJob('daily-cleanup', job);
job.start();
this.logger.log('Scheduled tasks initialized');
}
}
OnModuleDestroy
Called when the module is being destroyed (during application shutdown).
import { Injectable, OnModuleDestroy } from '@nestjs/common';
@Injectable()
export class CatsService implements OnModuleDestroy {
onModuleDestroy() {
console.log('CatsService is being destroyed');
}
}
Async Cleanup
import { Injectable, OnModuleDestroy, Logger } from '@nestjs/common';
@Injectable()
export class DatabaseService implements OnModuleDestroy {
private readonly logger = new Logger(DatabaseService.name);
private connection: Connection;
async onModuleDestroy() {
this.logger.log('Closing database connection...');
await this.connection.close();
this.logger.log('Database connection closed');
}
}
Use Cases
- Closing database connections
- Disconnecting from message queues
- Cleaning up file handles
- Stopping background tasks
- Unregistering event listeners
- Releasing resources
Complete Example
import { Injectable, OnModuleDestroy, Logger } from '@nestjs/common';
@Injectable()
export class RabbitMQService implements OnModuleDestroy {
private connection: Connection;
private channel: Channel;
private readonly logger = new Logger(RabbitMQService.name);
async onModuleDestroy() {
this.logger.log('Shutting down RabbitMQ connection...');
try {
if (this.channel) {
await this.channel.close();
this.logger.log('RabbitMQ channel closed');
}
if (this.connection) {
await this.connection.close();
this.logger.log('RabbitMQ connection closed');
}
} catch (error) {
this.logger.error('Error during RabbitMQ shutdown', error);
}
}
}
OnApplicationShutdown
Called when the application receives a termination signal (SIGTERM, SIGINT).
import { Injectable, OnApplicationShutdown } from '@nestjs/common';
@Injectable()
export class CatsService implements OnApplicationShutdown {
onApplicationShutdown(signal?: string) {
console.log(`Application shutting down with signal: ${signal}`);
}
}
Graceful Shutdown
import { Injectable, OnApplicationShutdown, Logger } from '@nestjs/common';
@Injectable()
export class WorkerService implements OnApplicationShutdown {
private readonly logger = new Logger(WorkerService.name);
private isShuttingDown = false;
private activeJobs = 0;
async onApplicationShutdown(signal?: string) {
this.logger.log(`Shutdown signal received: ${signal}`);
this.isShuttingDown = true;
while (this.activeJobs > 0) {
this.logger.log(`Waiting for ${this.activeJobs} jobs to complete...`);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
this.logger.log('All jobs completed, safe to shutdown');
}
}
Use Cases
- Completing in-progress requests
- Flushing logs or metrics
- Saving application state
- Notifying external systems of shutdown
- Graceful worker termination
- Final cleanup operations
Complete Example
import {
Injectable,
OnApplicationShutdown,
Logger,
} from '@nestjs/common';
@Injectable()
export class MetricsService implements OnApplicationShutdown {
private readonly logger = new Logger(MetricsService.name);
private metricsBuffer: Metric[] = [];
async onApplicationShutdown(signal?: string) {
this.logger.log(`Flushing metrics before shutdown (${signal})...`);
if (this.metricsBuffer.length > 0) {
try {
await this.sendMetrics(this.metricsBuffer);
this.logger.log(`${this.metricsBuffer.length} metrics flushed`);
} catch (error) {
this.logger.error('Failed to flush metrics', error);
}
}
}
private async sendMetrics(metrics: Metric[]): Promise<void> {
// Send to monitoring service
}
}
BeforeApplicationShutdown
Called before onApplicationShutdown, useful for cleanup that must happen before shutdown handlers.
import { Injectable, BeforeApplicationShutdown } from '@nestjs/common';
@Injectable()
export class CatsService implements BeforeApplicationShutdown {
beforeApplicationShutdown(signal?: string) {
console.log(`Preparing for shutdown: ${signal}`);
}
}
Enabling Shutdown Hooks
By default, shutdown hooks are disabled. Enable them in main.ts:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Enable shutdown hooks
app.enableShutdownHooks();
await app.listen(3000);
}
bootstrap();
Important: Enabling shutdown hooks consumes memory. Only enable if you need lifecycle hooks for shutdown.
Multiple Lifecycle Hooks
A class can implement multiple lifecycle interfaces:
import {
Injectable,
OnModuleInit,
OnApplicationBootstrap,
OnModuleDestroy,
OnApplicationShutdown,
Logger,
} from '@nestjs/common';
@Injectable()
export class LifecycleService
implements
OnModuleInit,
OnApplicationBootstrap,
OnModuleDestroy,
OnApplicationShutdown
{
private readonly logger = new Logger(LifecycleService.name);
onModuleInit() {
this.logger.log('Module initialized');
}
onApplicationBootstrap() {
this.logger.log('Application bootstrapped');
}
onModuleDestroy() {
this.logger.log('Module destroyed');
}
onApplicationShutdown(signal?: string) {
this.logger.log(`Application shutdown (${signal})`);
}
}
Lifecycle in Modules
Modules can also implement lifecycle hooks:
import { Module, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule implements OnModuleInit, OnModuleDestroy {
constructor(private catsService: CatsService) {}
onModuleInit() {
console.log('CatsModule initialized');
}
onModuleDestroy() {
console.log('CatsModule destroyed');
}
}
Practical Examples
Database Connection Management
import {
Injectable,
OnModuleInit,
OnModuleDestroy,
Logger,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Pool } from 'pg';
@Injectable()
export class DatabaseService implements OnModuleInit, OnModuleDestroy {
private pool: Pool;
private readonly logger = new Logger(DatabaseService.name);
constructor(private configService: ConfigService) {}
async onModuleInit() {
this.logger.log('Initializing database connection pool...');
this.pool = new Pool({
host: this.configService.get('DB_HOST'),
port: this.configService.get('DB_PORT'),
user: this.configService.get('DB_USER'),
password: this.configService.get('DB_PASSWORD'),
database: this.configService.get('DB_NAME'),
max: 20,
idleTimeoutMillis: 30000,
});
await this.pool.connect();
this.logger.log('Database connection pool ready');
}
async onModuleDestroy() {
this.logger.log('Closing database connection pool...');
await this.pool.end();
this.logger.log('Database connection pool closed');
}
getPool(): Pool {
return this.pool;
}
}
Cache Pre-warming
import {
Injectable,
OnApplicationBootstrap,
Logger,
} from '@nestjs/common';
@Injectable()
export class CachePrewarmService implements OnApplicationBootstrap {
private readonly logger = new Logger(CachePrewarmService.name);
constructor(
private cacheService: CacheService,
private productsService: ProductsService,
private categoriesService: CategoriesService,
) {}
async onApplicationBootstrap() {
this.logger.log('Starting cache pre-warming...');
const startTime = Date.now();
await Promise.all([
this.warmPopularProducts(),
this.warmCategories(),
this.warmFeaturedItems(),
]);
const duration = Date.now() - startTime;
this.logger.log(`Cache pre-warming completed in ${duration}ms`);
}
private async warmPopularProducts() {
const products = await this.productsService.getPopular(100);
await this.cacheService.set('popular_products', products, 3600);
}
private async warmCategories() {
const categories = await this.categoriesService.findAll();
await this.cacheService.set('categories', categories, 7200);
}
private async warmFeaturedItems() {
const featured = await this.productsService.getFeatured();
await this.cacheService.set('featured_items', featured, 1800);
}
}
Graceful Shutdown with Queue Processing
import {
Injectable,
OnApplicationShutdown,
Logger,
} from '@nestjs/common';
@Injectable()
export class QueueService implements OnApplicationShutdown {
private readonly logger = new Logger(QueueService.name);
private isShuttingDown = false;
private processingCount = 0;
async onApplicationShutdown(signal?: string) {
this.logger.log(`Shutdown initiated (${signal})`);
this.isShuttingDown = true;
if (this.processingCount > 0) {
this.logger.log(
`Waiting for ${this.processingCount} items to complete...`,
);
await this.waitForCompletion(30000);
}
this.logger.log('Queue processing stopped');
}
private async waitForCompletion(timeout: number): Promise<void> {
const startTime = Date.now();
while (this.processingCount > 0) {
if (Date.now() - startTime > timeout) {
this.logger.warn(
`Shutdown timeout reached, ${this.processingCount} items still processing`,
);
break;
}
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
async processItem(item: any) {
if (this.isShuttingDown) {
throw new Error('Service is shutting down');
}
this.processingCount++;
try {
// Process item
} finally {
this.processingCount--;
}
}
}
Health Check on Startup
import {
Injectable,
OnApplicationBootstrap,
Logger,
} from '@nestjs/common';
@Injectable()
export class HealthCheckService implements OnApplicationBootstrap {
private readonly logger = new Logger(HealthCheckService.name);
constructor(
private databaseService: DatabaseService,
private redisService: RedisService,
private externalApiService: ExternalApiService,
) {}
async onApplicationBootstrap() {
this.logger.log('Running startup health checks...');
const checks = [
this.checkDatabase(),
this.checkRedis(),
this.checkExternalApi(),
];
const results = await Promise.allSettled(checks);
results.forEach((result, index) => {
const checkName = ['Database', 'Redis', 'External API'][index];
if (result.status === 'fulfilled') {
this.logger.log(`✓ ${checkName} check passed`);
} else {
this.logger.error(`✗ ${checkName} check failed: ${result.reason}`);
}
});
const failedChecks = results.filter((r) => r.status === 'rejected');
if (failedChecks.length > 0) {
throw new Error('Startup health checks failed');
}
this.logger.log('All health checks passed');
}
private async checkDatabase(): Promise<void> {
await this.databaseService.query('SELECT 1');
}
private async checkRedis(): Promise<void> {
await this.redisService.ping();
}
private async checkExternalApi(): Promise<void> {
await this.externalApiService.healthCheck();
}
}
Best Practices
-
Use async/await - Lifecycle hooks support async operations
async onModuleInit() { await this.initialize(); } -
Log lifecycle events - Aid in debugging and monitoring
private readonly logger = new Logger(ServiceName.name); onModuleInit() { this.logger.log('Service initialized'); } -
Handle errors gracefully - Don't let lifecycle hooks crash the app
async onModuleInit() { try { await this.connect(); } catch (error) { this.logger.error('Initialization failed', error); throw error; } } -
Clean up resources - Always implement cleanup hooks
async onModuleDestroy() { await this.connection.close(); } -
Set timeouts for shutdown - Don't wait forever
async onApplicationShutdown() { await Promise.race([ this.cleanup(), new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 10000) ), ]); } -
Only enable shutdown hooks when needed - They consume memory
app.enableShutdownHooks(); // Only if necessary -
Order matters - Modules initialize in dependency order
// DatabaseModule initializes before CatsModule @Module({ imports: [DatabaseModule], }) export class CatsModule {} -
Test lifecycle hooks - Include in unit tests
it('should initialize on module init', async () => { await service.onModuleInit(); expect(service.isInitialized()).toBe(true); }); -
Use for side effects only - Keep business logic in methods
// Good onModuleInit() { this.connect(); } // Bad - business logic in lifecycle hook onModuleInit() { this.processAllUsers(); } -
Document initialization requirements - Be clear about dependencies
/** * Requires DATABASE_URL environment variable * Connects on module initialization */ async onModuleInit() { await this.connect(); }
Common Patterns
Retry Logic on Initialization
async onModuleInit() {
let retries = 3;
while (retries > 0) {
try {
await this.connect();
break;
} catch (error) {
retries--;
if (retries === 0) throw error;
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
}
Conditional Initialization
async onModuleInit() {
const environment = this.configService.get('NODE_ENV');
if (environment === 'production') {
await this.initializeMonitoring();
}
}
Dependency Validation
onModuleInit() {
if (!this.configService.get('API_KEY')) {
throw new Error('API_KEY is required');
}
}