Skip to content
NestJS ns patterns 4 min read

Lifecycle Hooks

Every NestJS application moves through a well-defined lifecycle: modules are instantiated, dependencies are resolved, the app starts listening, and eventually it shuts down. Lifecycle hooks let you tap into these moments to open database connections, warm caches, register message consumers, and—critically—release those resources cleanly on exit. Using them correctly is the difference between a service that restarts gracefully and one that leaks connections or drops in-flight requests.

The lifecycle in order

Nest fires hooks in a deterministic sequence. On startup it initializes each module, then bootstraps the application; on shutdown it reverses direction and tears everything down. The table below lists the hooks in execution order.

PhaseInterfaceMethodWhen it runs
StartupOnModuleInitonModuleInit()After a host module’s dependencies are resolved
StartupOnApplicationBootstraponApplicationBootstrap()After all modules are initialized, before listening
ShutdownOnModuleDestroyonModuleDestroy()After a termination signal is received
ShutdownBeforeApplicationShutdownbeforeApplicationShutdown(signal?)After all onModuleDestroy handlers complete
ShutdownOnApplicationShutdownonApplicationShutdown(signal?)After connections close, just before exit

Within a single phase, hooks respect module dependency order: a provider in an imported module runs its startup hook before the importing module, and the reverse on shutdown.

Startup hooks

OnModuleInit is the right place for per-module setup that depends on injected providers being ready—establishing a connection pool, for example. OnApplicationBootstrap runs once every module is wired up, so it suits cross-cutting work like subscribing to events or kicking off a scheduler.

Each hook may return a Promise, and Nest awaits it before proceeding. This guarantees your app is fully ready before it accepts traffic.

import { Injectable, Logger, OnModuleInit, OnApplicationBootstrap } from '@nestjs/common';
import { DataSource } from 'typeorm';

@Injectable()
export class DatabaseService implements OnModuleInit, OnApplicationBootstrap {
  private readonly logger = new Logger(DatabaseService.name);

  constructor(private readonly dataSource: DataSource) {}

  async onModuleInit(): Promise<void> {
    if (!this.dataSource.isInitialized) {
      await this.dataSource.initialize();
    }
    this.logger.log('Database connection established');
  }

  async onApplicationBootstrap(): Promise<void> {
    const count = await this.dataSource.query('SELECT count(*) FROM migrations');
    this.logger.log(`Bootstrap complete: ${count[0].count} migrations applied`);
  }
}

Output:

[Nest] LOG [DatabaseService] Database connection established
[Nest] LOG [DatabaseService] Bootstrap complete: 14 migrations applied
[Nest] LOG [NestApplication] Nest application successfully started

Shutdown hooks

Shutdown hooks only fire if you explicitly opt in by calling enableShutdownHooks(). This is a deliberate guard: attaching SIGTERM/SIGINT listeners has a small cost and side effects, so Nest does not do it by default.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableShutdownHooks(); // required for OnModuleDestroy & friends
  await app.listen(3000);
}
bootstrap();

OnModuleDestroy is where you stop accepting new work and close resources owned by that module. BeforeApplicationShutdown runs after every module has destroyed, making it ideal for tasks that need all providers still alive but must run last. OnApplicationShutdown receives the terminating signal and is the final chance to flush logs or notify a service registry.

import {
  Injectable,
  Logger,
  OnModuleDestroy,
  BeforeApplicationShutdown,
  OnApplicationShutdown,
} from '@nestjs/common';
import { Kafka, Consumer } from 'kafkajs';

@Injectable()
export class EventConsumer
  implements OnModuleDestroy, BeforeApplicationShutdown, OnApplicationShutdown
{
  private readonly logger = new Logger(EventConsumer.name);
  private consumer: Consumer = new Kafka({ brokers: ['localhost:9092'] }).consumer({
    groupId: 'orders',
  });

  async onModuleDestroy(): Promise<void> {
    await this.consumer.stop();
    this.logger.log('Stopped consuming new messages');
  }

  async beforeApplicationShutdown(signal?: string): Promise<void> {
    this.logger.log(`Draining in-flight work (signal: ${signal})`);
  }

  async onApplicationShutdown(signal?: string): Promise<void> {
    await this.consumer.disconnect();
    this.logger.log(`Consumer disconnected, exiting on ${signal}`);
  }
}

Output:

[Nest] LOG [EventConsumer] Stopped consuming new messages
[Nest] LOG [EventConsumer] Draining in-flight work (signal: SIGTERM)
[Nest] LOG [EventConsumer] Consumer disconnected, exiting on SIGTERM

Graceful shutdown in containers

In Kubernetes and Docker, the orchestrator sends SIGTERM and then waits a grace period before sending SIGKILL. A graceful service stops health checks first, lets the load balancer drain it, finishes outstanding requests, and only then closes connections. With shutdown hooks enabled, Nest awaits each async handler, so your cleanup completes within the grace window.

@Injectable()
export class HealthState implements OnModuleDestroy {
  private healthy = true;

  isHealthy(): boolean {
    return this.healthy;
  }

  onModuleDestroy(): void {
    // Fail readiness probes so the LB stops routing traffic here.
    this.healthy = false;
  }
}

Set terminationGracePeriodSeconds in your pod spec to comfortably exceed your longest cleanup task. If a hook hangs, the process is killed mid-shutdown and you lose the graceful guarantees.

Best practices

  • Call app.enableShutdownHooks() exactly once in bootstrap(); without it, no shutdown hook will ever fire.
  • Keep hooks idempotent and fast—long-running cleanup risks exceeding the orchestrator grace period and getting SIGKILLed.
  • Use OnModuleInit for module-local setup and OnApplicationBootstrap for work that needs the entire app wired up.
  • Always await async work inside hooks; returning a promise lets Nest sequence startup and teardown correctly.
  • Close resources in the hook of the module that owns them so dependency ordering tears things down in the right sequence.
  • Flip readiness/health state to unhealthy first on shutdown so load balancers drain traffic before connections close.
  • Avoid heavy logic in onApplicationShutdown—treat it as a last-chance flush, since providers may already be partially destroyed.
Last updated June 14, 2026
Was this helpful?