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.

Create a free account to save this page, take notes and track your progress. Sign in

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 → onApplicationBootstrap at startup; onModuleDestroy → beforeApplicationShutdown → onApplicationShutdown at shutdown.
  • app.enableShutdownHooks() makes Nest listen for SIGTERM/SIGINT and 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

Startup and shutdown hooks on SIGTERM
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(), so onModuleDestroy never runs on SIGTERM and connections are cut abruptly.
  • Closing the database in onModuleDestroy: it runs before beforeApplicationShutdown, so consumers that drain there would find the pool already closed. Close shared connections in onApplicationShutdown.
  • 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.

Was this page helpful?