Server Lifecycle
Startup and shutdown hooks, in the order NitroStack calls them
Your services can run code at fixed points while the server starts and stops: open a database pool before the first request, or close it cleanly when the process is told to exit. You do this by implementing lifecycle hooks on any class decorated with @Injectable() or @Controller().
The sequence
NitroStack calls each hook on every instance that implements it, in this order.
Hook interfaces
@nitrostack/core exports one interface per hook. Implement the ones you need.
| Interface | Method | When it runs | Use it to |
|---|---|---|---|
OnModuleInit | onModuleInit() | After the container has created your instance | Open database connections, compile schemas, warm caches |
OnApplicationBootstrap | onApplicationBootstrap() | After every instance is initialized, before the server accepts connections | Verify that dependencies are ready, register with a service registry |
OnModuleDestroy | onModuleDestroy() | First, when shutdown starts | Stop accepting new work, flush queues |
BeforeApplicationShutdown | beforeApplicationShutdown(signal?) | After onModuleDestroy, before the transports close | Close connection pools, stop worker processes |
OnApplicationShutdown | onApplicationShutdown(signal?) | Last, after the transports have closed | Final logging and cleanup |
Every method can be synchronous or return a promise; NitroStack waits for it before moving on.
Example
This connection pool implements all five hooks. The numbers in the log messages show the order they run in.
import {
Injectable,
OnModuleInit,
OnApplicationBootstrap,
BeforeApplicationShutdown,
OnModuleDestroy,
OnApplicationShutdown,
} from '@nitrostack/core';
@Injectable()
export class DatabaseConnectionPool implements
OnModuleInit,
OnApplicationBootstrap,
BeforeApplicationShutdown,
OnModuleDestroy,
OnApplicationShutdown {
private isConnected = false;
async onModuleInit(): Promise<void> {
console.log('[DatabaseConnectionPool] 1. Initializing connection pool...');
// Establish connection to database
this.isConnected = true;
}
async onApplicationBootstrap(): Promise<void> {
console.log('[DatabaseConnectionPool] 2. Application ready; verifying connection readiness...');
if (!this.isConnected) {
throw new Error('Database pool failed to initialize during startup');
}
}
async onModuleDestroy(): Promise<void> {
console.log('[DatabaseConnectionPool] 3. Shutdown started; stop taking new queries...');
}
async beforeApplicationShutdown(signal?: string): Promise<void> {
console.log(`[DatabaseConnectionPool] 4. Closing pool connections (${signal})...`);
this.isConnected = false;
}
async onApplicationShutdown(signal?: string): Promise<void> {
console.log(`[DatabaseConnectionPool] 5. Teardown complete. Exiting via ${signal}.`);
}
}Errors and signals
Hooks run one instance at a time. If an onModuleInit hook throws, startup stops and the server never listens, so a half-initialized server can't take requests. Shutdown hooks are more forgiving: an error in one is logged and the remaining hooks still run.
With shutdownHooks: true in @McpApp, which is the default, NitroStack listens for SIGINT and SIGTERM and runs the shutdown sequence when either arrives. That's what docker stop and most platforms send before they stop a process.
Request lifecycle
Once the server is listening, every tool call passes through the same pipeline. Guards run first and can reject the call; middleware, interceptors, and pipes wrap the handler; exception filters turn any error into a response.
Request Lifecycle in Core Concepts explains each stage.
