Suggest an editImprove this articleRefine the answer for “What are lifecycle hooks in NestJS?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**NestJS lifecycle hooks** are interfaces that let providers run code at specific points during app startup and shutdown. `OnModuleInit` fires after a module's providers are ready; `OnApplicationBootstrap` fires after all modules are initialized, before the app starts listening. ```typescript @Injectable() export class DatabaseService implements OnModuleInit { async onModuleInit() { await this.connect(); // runs once, after DI resolves } } ``` **Key:** call `app.enableShutdownHooks()` in `main.ts` or shutdown hooks will never run.Shown above the full answer for quick recall.Answer (EN)Image**NestJS lifecycle hooks** are interfaces that let providers run custom logic at specific points during module initialization and application shutdown. ## Theory ### TL;DR - `OnModuleInit` fires after a module's providers are ready, before the app starts listening - `OnApplicationBootstrap` fires after ALL modules are initialized, right before `app.listen()` - Shutdown hooks require `app.enableShutdownHooks()` in `main.ts` - without it, SIGTERM silently kills the process - Execution order: child modules first, then parent, then app-level; shutdown runs in reverse - Decision rule: use `OnModuleInit` for DB connections, `OnApplicationBootstrap` for tasks that need all modules up ### Quick example ```typescript import { Injectable, OnModuleInit, Logger } from '@nestjs/common'; @Injectable() export class DatabaseService implements OnModuleInit { private readonly logger = new Logger(DatabaseService.name); async onModuleInit() { await this.connect(); this.logger.log('DB connected after DI resolved'); // Runs once, after providers are ready, before app.listen() } private async connect() { /* establish connection pool */ } } ``` NestJS calls `onModuleInit` once, after the DI container resolves all dependencies for that module. No HTTP request has been accepted at this point. ### Module-level vs application-level There are two categories. Module-level hooks (`OnModuleInit`, `OnModuleDestroy`) fire per-module as NestJS bootstraps each one. Application-level hooks (`OnApplicationBootstrap`, `BeforeApplicationShutdown`, `OnApplicationShutdown`) fire once across the whole app, after every module is ready. This matters when you have cross-module dependencies. If `ServiceA` needs something from `ServiceB` defined in a different module, and both implement `OnModuleInit`, you cannot rely on order inside that hook. Use `OnApplicationBootstrap` instead. By then, all modules are done. ### All five hooks | Hook | Method | Timing | |------|--------|--------| | `OnModuleInit` | `onModuleInit()` | After module's providers are created | | `OnApplicationBootstrap` | `onApplicationBootstrap()` | After all modules initialized, before listening | | `OnModuleDestroy` | `onModuleDestroy()` | After `app.close()` is called | | `BeforeApplicationShutdown` | `beforeApplicationShutdown(signal?)` | Before connections close, receives OS signal | | `OnApplicationShutdown` | `onApplicationShutdown(signal?)` | After all connections are closed | ### When to use - **DB or Redis connect**: `OnModuleInit` - providers are ready, no cross-module deps needed - **Background workers or cron jobs**: `OnApplicationBootstrap` - all modules up, order guaranteed - **Graceful shutdown (drain pools, flush logs)**: `OnApplicationShutdown` + `app.enableShutdownHooks()` - **Fast prep before shutdown (stop accepting jobs)**: `BeforeApplicationShutdown`, keep it sync or fast async - **Per-request logic**: skip hooks entirely, use guards or interceptors ### How it works internally NestJS scans providers for implemented hook interfaces during module instantiation. It collects them in an ordered list based on scope (module vs app-wide) and calls each via `await` during `NestApplication.init()` or `close()`. For shutdown, `process.on('SIGTERM')` and `process.on('SIGINT')` trigger the chain, but only after you call `app.enableShutdownHooks()`. The DI container tracks which providers implement each interface via Reflect metadata, so the `implements` keyword is not just a TypeScript formality. Without it on the prototype, NestJS will not call the hook at runtime. Startup order: child modules' `OnModuleInit` fires first, then parent modules, then `OnApplicationBootstrap` across everything. Shutdown: `BeforeApplicationShutdown` runs first (fast prep), then the server stops accepting connections, then `OnModuleDestroy`, then `OnApplicationShutdown`. ### Common mistakes **Mistake 1: hooks in request-scoped providers** ```typescript // WRONG @Injectable({ scope: Scope.REQUEST }) export class UserService implements OnModuleInit { onModuleInit() { /* connects to DB */ } } // hook fires on every HTTP request - connection leak // RIGHT: move to a singleton-scoped service ``` Request-scoped providers get created and destroyed per request. The hook fires thousands of times. Move initialization logic to a module-scoped service. **Mistake 2: blocking bootstrap with synchronous heavy work** ```typescript // WRONG onModuleInit() { const data = fs.readFileSync('./config.json'); // blocks event loop } // RIGHT async onModuleInit() { const data = await fs.promises.readFile('./config.json'); } ``` A sync CPU-heavy task in `onModuleInit` freezes the entire bootstrap. On Heroku or similar platforms, this causes a dyno timeout before the app even starts. **Mistake 3: cross-module deps in OnModuleInit** ```typescript // WRONG - OtherModuleService may not be ready yet @Injectable() export class AppService implements OnModuleInit { constructor(@Inject(OtherModuleService) private svc: OtherModuleService) {} onModuleInit() { this.svc.doSetup(); // race condition } } // RIGHT - use OnApplicationBootstrap, all modules guaranteed ready ``` **Mistake 4: missing app.enableShutdownHooks()** ```typescript // main.ts async function bootstrap() { const app = await NestFactory.create(AppModule); app.enableShutdownHooks(); // without this, shutdown hooks never run await app.listen(3000); } ``` In every production NestJS codebase I have reviewed, this was the most common omission. The app works fine locally, deploys to Kubernetes, and nobody notices that graceful shutdown never actually runs - until a rolling deploy drops active connections. **Mistake 5: not awaiting async operations in shutdown** ```typescript // WRONG - process exits before the promise resolves onApplicationShutdown(signal: string) { closeDB(); // no await } // RIGHT async onApplicationShutdown(signal: string) { if (signal === 'SIGTERM') { await closeDB(); } } ``` One team lost 10k queued jobs this way before adding `async/await` to their shutdown hook. ### Real-world usage - **TypeORM**: `OnModuleInit` for `dataSource.initialize()` - same pattern as the official NestJS docs - **@nestjs/bull queues**: `OnApplicationBootstrap` starts Redis workers after all modules are up - **Prisma**: `BeforeApplicationShutdown` for `prisma.$disconnect()` - **Winston logger**: `OnModuleDestroy` flushes transports before the process exits - **Redis cache**: `OnModuleInit` to connect, `OnModuleDestroy` to drain the pool ### Follow-up questions **Q:** What is the execution order across multiple modules? **A:** Depth-first on startup: child modules' `OnModuleInit` fires before the parent's. `OnApplicationBootstrap` runs after all `OnModuleInit` calls finish. Shutdown reverses the order. **Q:** Do lifecycle hooks work in dynamic modules? **A:** Yes. Hooks register per instance, so dynamically loaded modules register their hooks when they load. Lazy-loaded modules delay hook registration until `LazyModuleLoader.load()` is called. **Q:** What happens if a hook throws an error? **A:** The error bubbles up and aborts the bootstrap. NestJS logs the stack trace. Wrap risky operations in `try/catch` inside the hook if you want the app to fail gracefully rather than crash. **Q:** Why not use `process.on('SIGTERM')` directly instead of shutdown hooks? **A:** You can, but NestJS hooks give you automatic ordering and DI context. Raw `process.on` listeners run outside the NestJS lifecycle, so you lose the guarantee that all modules finished their work before cleanup runs. **Q:** How does NestJS detect which providers implement a hook interface at runtime? (senior-level) **A:** It checks prototypes via inspection like `'onModuleInit' in provider` combined with Reflect metadata during provider discovery. This means omitting `implements OnModuleInit` in TypeScript does not stop the hook from firing - if the method exists on the prototype, NestJS calls it. ## Examples ### Redis with full connect and cleanup ```typescript import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; import { createClient } from 'redis'; @Injectable() export class CacheService implements OnModuleInit, OnModuleDestroy { private client = createClient(); async onModuleInit() { await this.client.connect(); console.log('Redis ready'); } async onModuleDestroy() { await this.client.quit(); // drains in-flight commands before closing socket console.log('Redis pool drained'); } } // On SIGTERM output: Redis pool drained ``` `OnModuleInit` and `OnModuleDestroy` as a pair give you a clean connect/disconnect contract. The `quit()` call drains in-flight commands before closing the socket, preventing data loss. ### Graceful queue shutdown with signal handling ```typescript // main.ts async function bootstrap() { const app = await NestFactory.create(AppModule); app.enableShutdownHooks(); // required for SIGTERM/SIGINT await app.listen(3000); } // queue.service.ts import { Injectable, BeforeApplicationShutdown, OnApplicationShutdown } from '@nestjs/common'; @Injectable() export class QueueService implements BeforeApplicationShutdown, OnApplicationShutdown { private accepting = true; beforeApplicationShutdown(signal: string) { console.log(`Signal received: ${signal}`); this.accepting = false; // stop queuing new jobs, fast and sync } async onApplicationShutdown(signal: string) { if (signal === 'SIGTERM') { await this.drainActiveJobs(); // slow async cleanup } console.log('Queue drained, shutting down'); } private drainActiveJobs() { return new Promise<void>(resolve => setTimeout(resolve, 2000)); } } ``` `BeforeApplicationShutdown` handles the fast synchronous prep (stopping intake), while `OnApplicationShutdown` handles the slow async part (draining). Splitting the work this way avoids blocking the shutdown sequence. ### Database seeding in development with OnApplicationBootstrap ```typescript import { Injectable, OnApplicationBootstrap } from '@nestjs/common'; @Injectable() export class SeedService implements OnApplicationBootstrap { constructor( private readonly usersService: UsersService, private readonly configService: ConfigService, ) {} async onApplicationBootstrap() { if (this.configService.get('NODE_ENV') !== 'development') return; const count = await this.usersService.count(); if (count === 0) { await this.usersService.create({ name: 'Admin', email: 'admin@app.com' }); console.log('Dev seed complete'); } } } ``` `OnApplicationBootstrap` is the right choice here because `SeedService` depends on `UsersService` from another module. By the time `onApplicationBootstrap` fires, the entire DI graph is resolved and every module has finished `OnModuleInit`.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.