Skip to content

Health

Contract

@modularityjs/health defines the HealthIndicator interface, HealthService, and pool-based indicator discovery.

typescript
interface HealthIndicator {
  readonly name: string;
  check(): Promise<HealthStatus>;
}
typescript
interface HealthStatus {
  healthy: boolean;
  details?: Record<string, unknown>;
}
typescript
interface AggregatedHealth {
  healthy: boolean;
  indicators: Record<string, HealthStatus>;
}

Setup

typescript
import { HealthModule } from '@modularityjs/health';

const modules = [HealthModule];

No driver is required — HealthModule declares the HealthIndicatorsPool contract and provides HealthService. Indicators are contributed by other modules.

Defining Indicators

Implement the HealthIndicator interface and register via HealthIndicatorsPool:

typescript
import { Inject, Injectable } from '@modularityjs/di';
import { HealthIndicatorsPool, HealthModule } from '@modularityjs/health';
import type { HealthIndicator } from '@modularityjs/health';
import { Module } from '@modularityjs/modularity';
import { DatabaseService } from './database.service.js';

@Injectable()
class DatabaseHealthIndicator implements HealthIndicator {
  readonly name = 'database';

  constructor(@Inject(DatabaseService) private readonly db: DatabaseService) {}

  async check() {
    const start = performance.now();
    await this.db.ping();
    const responseTimeMs = Math.round((performance.now() - start) * 100) / 100;
    return { healthy: true, details: { responseTimeMs } };
  }
}

@Module({
  name: 'database-health',
  imports: [HealthModule],
  providers: [DatabaseHealthIndicator],
  pools: [
    {
      pool: HealthIndicatorsPool,
      key: 'database',
      useClass: DatabaseHealthIndicator,
    },
  ],
})
class DatabaseHealthModule {}

The HealthService catches exceptions thrown by indicators and marks them as unhealthy automatically — indicators don't need their own try/catch. Each indicator is also raced against HealthConfig.indicatorTimeoutMs (default 5000); a slow indicator fails with Health check timed out rather than blocking the aggregate.

Built-in Indicators

Redis (@modularityjs/redis-health)

PING-based Redis liveness check with response time reporting. Requires @modularityjs/redis.

typescript
import { HealthModule } from '@modularityjs/health';
import { RedisHealthModule } from '@modularityjs/redis-health';
import { RedisModule } from '@modularityjs/redis';

const modules = [
  RedisModule.forRoot({ host: 'localhost', port: 6379 }),
  HealthModule,
  RedisHealthModule,
];

Programmatic Access

typescript
import { Inject, Injectable } from '@modularityjs/di';
import { HealthService } from '@modularityjs/health';

@Injectable()
class StatusController {
  constructor(@Inject(HealthService) private readonly health: HealthService) {}

  async getHealth() {
    const result = await this.health.check();
    // { healthy: true, indicators: { database: { healthy: true, details: { responseTimeMs: 2.31 } }, redis: { healthy: true, details: { responseTimeMs: 0.85 } } } }
    return result;
  }
}

CLI

@modularityjs/health-cli adds the health:status command:

typescript
import { HealthModule } from '@modularityjs/health';
import { HealthCliModule } from '@modularityjs/health-cli';

const modules = [HealthModule, HealthCliModule];
$ myapp health:status
HEALTHY

├── ✓ database (responseTimeMs: 2.31)
└── ✓ redis (responseTimeMs: 0.85)