Core Concepts

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.

StartupMcpApplicationFactory.create(), then server.start()
1Resolve dependenciesThe DI container builds every provider and controller
2onModuleInit()Open connections, warm caches
3onApplication​Bootstrap()Everything is initialized; final checks
4Transports listenSTDIO and HTTP accept requests
ShutdownSIGINT, SIGTERM, or server.stop()
5onModuleDestroy()Stop taking new work
6beforeApplication​Shutdown(signal)Release pools and workers
7Transports closeSessions and listeners shut down
8onApplication​Shutdown(signal)Last logs before the process exits
Startup hooks run before the server accepts requests. Shutdown hooks run before and after the transports close.

Hook interfaces

@nitrostack/core exports one interface per hook. Implement the ones you need.

InterfaceMethodWhen it runsUse it to
OnModuleInitonModuleInit()After the container has created your instanceOpen database connections, compile schemas, warm caches
OnApplicationBootstraponApplicationBootstrap()After every instance is initialized, before the server accepts connectionsVerify that dependencies are ready, register with a service registry
OnModuleDestroyonModuleDestroy()First, when shutdown startsStop accepting new work, flush queues
BeforeApplicationShutdownbeforeApplicationShutdown(signal?)After onModuleDestroy, before the transports closeClose connection pools, stop worker processes
OnApplicationShutdownonApplicationShutdown(signal?)Last, after the transports have closedFinal 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.

Before your handler
Guards
Middleware
Interceptors
Pipes
Handler
After your handler
Interceptors
Exception filters
Response
If a guard returns false, the call stops there and nothing after it runs.

Request Lifecycle in Core Concepts explains each stage.

Next steps

On this page