Lifecycle Events
Open a connection in onModuleInit and seed data in onApplicationBootstrap, and see from the recorded order why the two hooks exist.
What you'll learn
- Implement OnModuleInit and OnApplicationBootstrap and place each hook in the boot sequence
- Explain why hooks of one module run together, and why work that needs another provider ready belongs in onApplicationBootstrap
- Describe the shutdown hooks, what enables them, and why a browser run never reaches them
An application is not ready the moment its classes exist. A connection must be opened, a cache warmed, seed data written, and at the other end a connection should be closed before the process goes away. Lesson 3 did the first kind of work in an async provider. Lifecycle hooks do it inside the classes that own the resource: a method with a known name that Nest calls at a known point in the application's life, and waits for.
The sequence
An application passes through three phases, initialising, running, terminating, and Nest calls one hook at each step:
| Hook | When |
|---|---|
onModuleInit() | this module's dependencies are resolved and its providers exist |
onApplicationBootstrap() | every module has initialised, just before the application starts listening |
onModuleDestroy() | a termination signal arrived, or app.close() was called |
beforeApplicationShutdown() | every onModuleDestroy() has completed; connections are still open |
onApplicationShutdown() | connections are closed; the last thing that runs |
A class opts in by implementing the interface of the same name from @nestjs/common and writing the method:
@Injectable()
export class SearchIndex implements OnModuleInit {
constructor(private readonly client: SearchClient) {}
async onModuleInit(): Promise<void> {
await this.client.connect();
}
}
Hooks may be async, and Nest awaits them: a Promise returned from onModuleInit() delays everything after it. The interface is optional at runtime, since Nest looks for the method by name, but it is what makes a typo a compile error rather than a hook that silently never runs.
Who runs first
Two rules decide the order, and both matter for the exercise.
Across modules, hooks run in dependency order: a module's onModuleInit hooks run after those of every module it imports. CatsModule initialises before the AppModule that imports it, so by the time the root module's providers wake up, the feature modules are ready.
Within a module, Nest 12 groups a module's providers by their hierarchy level, their depth in the module's dependency tree, and runs the hooks one level at a time, awaiting each level before the next. A provider that depends on nothing is level 0 and goes first; a provider that injects it comes after, and its onModuleInit can rely on the dependency's hook having finished, even an async one. Providers on the same level run together, in parallel. Earlier Nest versions ran a whole module's hooks in parallel, which is why older advice says never to rely on order within a module; in Nest 12 the order follows the dependencies, and only providers that do not depend on each other are unordered.
onApplicationBootstrap then runs, module by module in the same order, and only after every onModuleInit in the application has completed. That is the difference between the two hooks and the reason both exist: onModuleInit is for getting this class ready, and it may rely on what this class depends on; onApplicationBootstrap is for work that needs the whole application ready, whatever module it lives in.
Shutting down
The three shutdown hooks run only when the application is asked to stop: app.close() in code, or a signal such as SIGTERM from the platform, which Nest listens for only after app.enableShutdownHooks() in main.ts, because listening costs a little memory. The signal is passed to the hooks, so a class can tell a deploy from a crash:
@Injectable()
export class Queue implements OnApplicationShutdown {
async onApplicationShutdown(signal?: string): Promise<void> {
await this.drain();
console.log(`queue drained on ${signal}`);
}
}
In this browser runtime a run ends by discarding the worker, and nothing is ever closed, so the shutdown hooks never fire here. In a container that receives SIGTERM before being killed, they are how in-flight work finishes.
Your task
The shelter's database must be opened before anything is stored, and the shelter starts with two cats.
- Open the database in the hook that runs once
CatsModule's dependencies are resolved. Recorddatabase:openingbefore anddatabase:openafter. - In
CatsService, recordcats:initin the same hook, and seed Tom (3) and Luna (5) in the hook that runs once every module has initialised, recordingcats:seededafter.
Announcer in the root module records app:init and app:bootstrap, and GET /cats/lifecycle returns everything in the order it happened. Read the order before you decide where the seed goes: putting it in onModuleInit would work, since the database's hook has finished by then, but it would run before the root module has initialised, and the order shows it.
When it fails
the database is not open, thrown at boot: the seed ran before the database's hook completed. Either the seed is in a hook that runs too early, oronModuleInitinDatabasedoes notawait connect().cats:seededcomes beforeapp:init: the seed is inonModuleInit. It works, and it is the wrong hook: it did not wait for the rest of the application.- A hook never runs: the method's name is misspelt. Implement the interface and the compiler will tell you.
database:opencomes aftercats:init:onModuleInitinDatabasereturns before the connection is open, becauseconnect()was called withoutawait.
Remember
onModuleInitwhen this module's dependencies are ready;onApplicationBootstrapwhen every module is; async hooks are awaited.- Across modules, imports first. Within a module, dependencies first, level by level; unrelated providers in parallel.
- Shutdown hooks need
app.close()or a signal plusenableShutdownHooks(), and receive the signal. - Implement the interface so a misspelt hook is a compile error.
Stuck? Show a hint
Database implements OnModuleInit: an async onModuleInit() that records 'database:opening', awaits connect(), records 'database:open'. CatsService implements OnModuleInit and OnApplicationBootstrap: onModuleInit() records 'cats:init'; onApplicationBootstrap() creates Tom and Luna, then records 'cats:seeded'. The seed has to be in the bootstrap hook: the recorded order proves which hook it ran in.