This chapter follows Dynamic modules in NestJS.
The request lifecycle describes one request. The application lifecycle describes the process: Nest builds the module graph, creates every provider, starts listening, and, when told to stop, tears it all down again. Providers, controllers, and modules can implement hook interfaces to run code at each of these points. That is where a cache warms up, a consumer subscribes to a queue, and a connection is closed before the process exits.
The hooks in order
| Hook | Called |
|---|---|
onModuleInit() |
Once the host module’s dependencies are resolved |
onApplicationBootstrap() |
Once all modules are initialized, before the app starts listening |
onModuleDestroy() |
After a shutdown signal is received |
beforeApplicationShutdown(signal) |
After all onModuleDestroy() handlers have finished; connections are then closed |
onApplicationShutdown(signal) |
After connections are closed |
sequenceDiagram participant Main as main.ts participant Nest participant Svc as Your providers Main->>Nest: NestFactory.create(AppModule) Nest->>Svc: onModuleInit() Nest->>Svc: onApplicationBootstrap() Main->>Nest: app.listen() Note over Nest: Serving requests Nest->>Svc: onModuleDestroy() Nest->>Svc: beforeApplicationShutdown(signal) Note over Nest: HTTP server closes Nest->>Svc: onApplicationShutdown(signal)
Every hook may be async. Nest awaits the returned promise before it moves to the next step, so startup waits for an onModuleInit() that connects to a broker.
Running work at startup
Constructors should only store their dependencies. Work that is asynchronous, or that needs another provider to be fully set up, belongs in a hook:
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';import { RatesClient } from './rates.client';
@Injectable()export class ExchangeRatesService implements OnModuleInit { private readonly logger = new Logger(ExchangeRatesService.name); private rates = new Map<string, number>();
constructor(private readonly client: RatesClient) {}
async onModuleInit() { this.rates = await this.client.fetchAll(); this.logger.log(`Loaded ${this.rates.size} exchange rates`); }}onModuleInit() runs per module, as soon as that module’s providers exist. onApplicationBootstrap() runs after every module is ready, so it is the safer hook for work that touches several modules, such as starting a scheduler that calls services from all over the app.
Cleaning up at shutdown
The destroy and shutdown hooks release what startup acquired: stop consuming from a queue, flush buffered logs or metrics, close a client.
import { Injectable, OnApplicationBootstrap, OnModuleDestroy } from '@nestjs/common';import { QueueClient } from './queue.client';
@Injectable()export class OrderConsumer implements OnApplicationBootstrap, OnModuleDestroy { constructor(private readonly queue: QueueClient) {}
async onApplicationBootstrap() { await this.queue.subscribe('orders', (message) => this.handle(message)); }
async onModuleDestroy() { await this.queue.unsubscribe('orders'); }
private async handle(message: unknown) {}}Order matters here. Stop taking new work in onModuleDestroy(), let in-flight work finish, and close shared connections last, in onApplicationShutdown(). Library modules such as @nestjs/mongoose already close their own connections this way.
Enabling shutdown hooks
The destroy and shutdown hooks only run when Nest knows the process is stopping. app.close() triggers them. Signals such as SIGTERM do not, unless you opt in:
async function bootstrap() { const app = await NestFactory.create(AppModule); app.enableShutdownHooks(); await app.listen(process.env.PORT ?? 3000);}bootstrap();Without this line, a container platform that stops a pod with SIGTERM kills the process without calling any hook, and open connections are simply dropped. Nest leaves it off by default because each listener costs a little memory, and because several Nest apps in one process, as in some test setups, would each register their own. The platform’s grace period, 30 seconds by default on Kubernetes, is the upper bound for all shutdown hooks together.
Where hooks are not called
Hooks run for singleton providers, controllers, and modules. They are not called on request-scoped or transient providers, because those are created per request or per consumer, not at startup. A request-scoped provider that holds a resource has to release it some other way, typically by not holding long-lived resources at all.
Hooks also run in the order modules are initialized, which follows the import graph: a module’s dependencies are initialized before the module itself. Shutdown follows the reverse order. Do not rely on any order between two modules that do not depend on each other.
Check your understanding
Question 1 of 5
0 of 5 checked