Skip to content

Lifecycle Hooks

Lifecycle hooks let modules run code at specific points during the application lifecycle. Stratal provides two hooks: one that fires after module initialization and one that fires during shutdown.

The OnInitialize interface defines an onInitialize() method that runs after all of a module’s providers have been registered in the DI container. Implement it directly on your module class:

import { Module, OnInitialize, ModuleContext } from 'stratal/module'
@Module({
providers: [CacheService],
})
export class CacheModule implements OnInitialize {
onInitialize({ container, logger }: ModuleContext) {
logger.info('CacheModule initialized')
const cache = container.resolve(CacheService)
cache.warmUp()
}
}

The onInitialize method can also be async:

@Module({
providers: [DatabaseService],
})
export class DatabaseModule implements OnInitialize {
async onInitialize({ container, logger }: ModuleContext) {
const db = container.resolve(DatabaseService)
await db.runMigrations()
logger.info('Database migrations complete')
}
}

Common use cases for onInitialize:

  • Seed initial data or warm caches
  • Run database migrations
  • Register conditional bindings (see Providers)
  • Extend services with decorators
  • Connect to external resources
  • Register additional providers dynamically

The OnShutdown interface defines an onShutdown() method that runs when the application is shutting down:

import { Module, OnShutdown, ModuleContext } from 'stratal/module'
@Module({
providers: [DatabaseService],
})
export class DatabaseModule implements OnShutdown {
onShutdown({ container, logger }: ModuleContext) {
const db = container.resolve(DatabaseService)
db.disconnect()
logger.info('Database connection closed')
}
}

Common use cases for onShutdown:

  • Close database connections
  • Flush pending writes
  • Clean up temporary resources
  • Deregister from external services

Both lifecycle hooks receive a ModuleContext object with two properties:

PropertyTypeDescription
containerContainerThe DI container, used to resolve and register providers
loggerLoggerServiceA logger instance scoped to the module, for structured log output
onInitialize({ container, logger }: ModuleContext) {
// Resolve any registered provider
const config = container.resolve(ConfigService)
// Register additional providers dynamically
container.registerValue(API_URL, config.get('API_URL'))
// Log with the module-scoped logger
logger.info('Feature module ready')
}

Module initialization follows the import tree. Stratal initializes imported modules before the module that imports them:

@Module({
imports: [DatabaseModule, CacheModule],
controllers: [AppController],
})
export class AppModule implements OnInitialize {
onInitialize({ logger }: ModuleContext) {
// DatabaseModule.onInitialize() has already run
// CacheModule.onInitialize() has already run
logger.info('AppModule ready')
}
}

This means leaf modules (those with no imports) initialize first, and the root module initializes last. This ordering guarantees that by the time a module’s onInitialize runs, all of its dependencies are fully set up.

A module can implement both OnInitialize and OnShutdown:

import { Module, OnInitialize, OnShutdown, ModuleContext } from 'stratal/module'
@Module({
providers: [ConnectionPool],
})
export class InfraModule implements OnInitialize, OnShutdown {
onInitialize({ container, logger }: ModuleContext) {
const pool = container.resolve(ConnectionPool)
pool.connect()
logger.info('Connection pool started')
}
onShutdown({ container, logger }: ModuleContext) {
const pool = container.resolve(ConnectionPool)
pool.disconnect()
logger.info('Connection pool stopped')
}
}
  • Modules to learn how modules are structured and composed.
  • Dependency Injection to understand the container that lifecycle hooks interact with.