Skip to content

Lifecycle Hooks

OzeanJs provides a Lifecycle Hooks system, a powerful mechanism to "hook" into key moments of the application's lifecycle. This feature allows you to manage resources (like database connections) systematically and safely.

What Are Lifecycle Hooks?

Lifecycle Hooks are special methods that you can add to your Provider or Controller classes. OzeanJs will automatically call these methods at specific, predetermined moments.

Key Hooks:

  • onModuleInit(): Called after a module and all of its dependencies have been created and resolved. Ideal for logic that needs to run once everything in the module is ready.

  • onApplicationBootstrap(): Called after the entire application has finished bootstrapping and is ready to receive requests. Ideal for one-time setup tasks at the very beginning.

  • onApplicationShutdown(): Called when the application receives a shutdown signal (e.g., from Ctrl+C). This is the perfect place for closing connections or cleaning up resources (Graceful Shutdown).

Using onModuleInit and onApplicationBootstrap

Let's look at an example of creating a DatabaseService that establishes a connection and a SeederService that populates initial data.

Step 1: Create DatabaseService with onModuleInit

We use onModuleInit to ensure the database connection is established only after all dependencies (like a ConfigService) are available.

typescript
// src/database/database.service.ts
import { Injectable, OnModuleInit } from 'ozean';

@Injectable()
export class DatabaseService implements OnModuleInit {
  private isConnected = false;

  // The constructor runs first
  constructor() {
    console.log('[DatabaseService] constructor: Service is being created.');
  }

  // onModuleInit is called after the constructor and all DI is complete
  onModuleInit() {
    console.log('[DatabaseService] onModuleInit: Module is ready. Connecting to database...');
    // Place your database connection logic here
    this.isConnected = true;
    console.log('[DatabaseService] Database connection established.');
  }

  // A method for other services to use
  async seedUsers(users: any[]) {
    if (!this.isConnected) throw new Error('Database not connected.');
    console.log('[DatabaseService] Seeding users data...');
    // ... logic to insert data ...
    return { success: true, count: users.length };
  }
}

Step 2: Create SeederService with onApplicationBootstrap

We use onApplicationBootstrap to run the data seeding logic only once after the entire application has started up completely.

typescript
// src/database/seeder.service.ts
import { Injectable, OnApplicationBootstrap } from 'ozean';
import { DatabaseService } from './database.service';

@Injectable()
export class SeederService implements OnApplicationBootstrap {
  // Inject the DatabaseService
  constructor(private readonly databaseService: DatabaseService) {}

  // This hook is called after onModuleInit has completed for all modules
  async onApplicationBootstrap() {
    console.log('[SeederService] onApplicationBootstrap: App is ready. Seeding initial data...');
    const initialUsers = [{ name: 'Admin User' }, { name: 'Editor User' }];
    await this.databaseService.seedUsers(initialUsers);
    console.log('[SeederService] Data seeding complete.');
  }
}

Using OnApplicationShutdown (Graceful Shutdown)

This is the most common and important example: using a hook to properly close database connections when the application shuts down.

Step 1: Implement OnApplicationShutdown in a Service

We will add OnApplicationShutdown to our DatabaseService.

typescript
// src/database/database.service.ts (Complete version)
import { Injectable, OnModuleInit, OnApplicationShutdown } from 'ozean';

@Injectable()
export class DatabaseService implements OnModuleInit, OnApplicationShutdown {
  private isConnected = false;

  constructor() {
    console.log('[DatabaseService] constructor called.');
  }

  onModuleInit() {
    console.log('[DatabaseService] onModuleInit: Connecting to database...');
    this.isConnected = true;
  }

  // This hook will be called automatically when the app shuts down
  async onApplicationShutdown(signal?: string) {
    console.log(
      `[DatabaseService] onApplicationShutdown: Received signal (${signal}). Closing connections...`
    );
    // Place your connection pool closing logic here
    // await this.pool.end();
    this.isConnected = false;
    console.log('[DatabaseService] Connections closed gracefully.');
  }

  // ... other methods ...
}

Step 2: Register and Enable

Finally, don't forget to register all services in a Module and enable the Shutdown Hooks in your main.ts file.

typescript
// src/app.module.ts
@Module({
  providers: [DatabaseService, SeederService], // <-- Register both services
})
export class AppModule {}

// src/main.ts
async function bootstrap() {
  const app = new App(AppModule);

  // Enable Graceful Shutdown
  app.enableShutdownHooks();

  app.listen(3000);
}

bootstrap();

Using Lifecycle Hooks makes your application more robust and allows you to manage resources correctly and safely throughout all stages of its life.