NestJS in Production · Configuration, Testing, Data and Operations
Lifecycle Hooks, Health and Graceful Shutdown
Open and close resources at the right time with lifecycle hooks, and let NestJS shut down cleanly on SIGTERM.
What you'll build
A QueueConsumer that starts consuming in onApplicationBootstrap and drains in beforeApplicationShutdown, and a database client closed in onApplicationShutdown, the last hook, so the pool is still open while the consumer finishes its job. With enableShutdownHooks(), SIGTERM triggers exactly these hooks; the example calls app.close(), which runs the same sequence, so it can print the log at the end.
The idea
- Hooks run in order:
onModuleInit→onApplicationBootstrapat startup;onModuleDestroy→beforeApplicationShutdown→onApplicationShutdownat shutdown. app.enableShutdownHooks()makes Nest listen forSIGTERM/SIGINTand run the shutdown hooks (off by default because it adds signal listeners).- Stop accepting work first, finish in-flight work, then close connections.
The code
import "reflect-metadata";
import { BeforeApplicationShutdown, Injectable, Module, OnApplicationBootstrap, OnApplicationShutdown, OnModuleInit } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
const log: string[] = [];
@Injectable()
class DbClient implements OnModuleInit, OnApplicationShutdown {
onModuleInit() { log.push("db: connection pool opened"); }
onApplicationShutdown() { log.push("db: connection pool closed"); } // the last hook: consumers have drained by now
}
@Injectable()
class QueueConsumer implements OnApplicationBootstrap, BeforeApplicationShutdown {
private accepting = false;
private current: Promise<void> | null = null;
constructor(private db: DbClient) {}
onApplicationBootstrap() {
this.accepting = true;
log.push("queue: consuming");
this.current = new Promise((r) => setTimeout(() => { log.push("queue: job 1 finished"); r(); }, 200));
}
async beforeApplicationShutdown(signal?: string) {
this.accepting = false;
log.push(`queue: shutdown (${signal ?? "app.close()"}), not taking new jobs, waiting for job 1`);
await this.current;
}
}
@Module({ providers: [DbClient, QueueConsumer] })
class AppModule {}
async function main() {
const app = await NestFactory.createApplicationContext(AppModule, { logger: false });
app.enableShutdownHooks(); // in production: SIGTERM runs the shutdown hooks below, then the process exits
await app.init();
await new Promise((r) => setTimeout(r, 50));
await app.close(); // runs exactly the same hooks, so we can print the log afterwards
console.log(log.join("\n"));
}
main();
Output
db: connection pool opened queue: consuming queue: shutdown (app.close()), not taking new jobs, waiting for job 1 queue: job 1 finished db: connection pool closed
Run this on your computer — see “Get Started” for setup.
Common mistakes
- Not calling
enableShutdownHooks(), soonModuleDestroynever runs on SIGTERM and connections are cut abruptly. - Closing the database in
onModuleDestroy: it runs beforebeforeApplicationShutdown, so consumers that drain there would find the pool already closed. Close shared connections inonApplicationShutdown. - Long shutdown work exceeding the platform's grace period (30 s in Kubernetes by default).
Key points
- Use lifecycle hooks to open and close resources.
- enableShutdownHooks() runs them on SIGTERM.
- Stop new work, finish current work, then close connections.
Further reading: Lifecycle events · Health checks (Terminus)
Practice questions
What does app.enableShutdownHooks() do?
Answer
Registers listeners for termination signals (SIGTERM, SIGINT) so Nest runs onModuleDestroy, beforeApplicationShutdown and onApplicationShutdown before exiting.
In what order do NestJS shutdown hooks run?
Answer
onModuleDestroy, then beforeApplicationShutdown (after connections are closed by app.close), then onApplicationShutdown.
Why stop accepting new work before closing connections?
Answer
So in-flight work can finish using the still-open connections, and no new work starts that would be cut off.
Open in the app to mark lessons complete, take notes and track your progress.