Health Wiring
Recipes are working wiring examples with the sharp edges annotated. This page and the
health-wiringrecipe served to coding agents by the MCP server share one source (@modularityjs/manifest), so they never drift apart.
Two packages compose, plus ready-made indicator extensions:
@modularityjs/health—HealthService.check(probe?) → AggregatedHealth, theHealthIndicatorinterface ({ name, probe?, check() }), andHealthIndicatorsPool. No driver needed — indicators are the contributions. Each indicator declaresprobe: 'liveness' | 'readiness'(omitted =readiness);check('readiness')runs only that probe's indicators, in parallel, each raced againstHealthConfig.indicatorTimeoutMs(default 5s). A throwing or timed-out indicator becomes{ healthy: false, details: { error } }— no try/catch needed inside indicators.@modularityjs/http-health— mounts/health(all indicators, diagnostics),/health/live(liveness only),/health/ready(readiness only) on the abstract HTTP contract.200when healthy,503otherwise.exposeDetailsdefaults toprocess.env.NODE_ENV !== 'production', so production bodies are just{ healthy }— these routes carry no auth and probes only read the status code. Set it explicitly totruewhen a private probe network makes the detail worth having. The controller is@Version(null), so enablingHttpModuleConfig.versioningnever moves the probe URLs out from under your deployment manifest.- Built-in indicators —
@modularityjs/redis-health(PING),@modularityjs/database-health(DatabaseConnection.ping()— a liveSELECT 1),@modularityjs/search-elasticsearch-health(cluster ping). All register as readiness; wire the ones matching your drivers.
Classify probes by consequence, not by importance. Readiness failure ⇒ the orchestrator stops routing traffic to the instance until it recovers. Liveness failure ⇒ the orchestrator restarts the process. Backend dependencies (database, Redis, ES) must be readiness — classifying them as liveness turns a transient dependency blip into a fleet-wide restart storm. Reserve liveness for process-level self-checks.
import { Injectable } from '@modularityjs/di';
import { inversify } from '@modularityjs/di-inversify';
import {
HealthIndicatorsPool,
HealthModule,
type HealthIndicator,
type HealthStatus,
} from '@modularityjs/health';
import { HttpModule } from '@modularityjs/http';
import { HttpFastifyModule } from '@modularityjs/http-fastify';
import { HttpHealthModule } from '@modularityjs/http-health';
import { createApp, Module, ModularityModule } from '@modularityjs/modularity';
import { RedisModule } from '@modularityjs/redis';
import { RedisHealthModule } from '@modularityjs/redis-health';
@Injectable()
class EventLoopIndicator implements HealthIndicator {
readonly name = 'event-loop';
readonly probe = 'liveness' as const; // restart-worthy: the process itself is wedged
async check(): Promise<HealthStatus> {
const start = performance.now();
await new Promise((resolve) => setTimeout(resolve, 0));
const lagMs = Math.round((performance.now() - start) * 100) / 100;
return { healthy: lagMs < 1000, details: { lagMs } };
}
}
@Module({
name: 'app-health',
imports: [HealthModule],
providers: [EventLoopIndicator],
pools: [
{
pool: HealthIndicatorsPool,
key: 'event-loop',
useClass: EventLoopIndicator,
},
],
})
class AppHealthModule {}
const app = await createApp({
di: inversify,
modules: [
ModularityModule,
HttpModule.forRoot({ port: 3000 }),
HttpFastifyModule,
RedisModule.forRoot({ url: 'redis://localhost:6379' }),
HealthModule,
RedisHealthModule, // readiness: Redis PING
HttpHealthModule, // /health, /health/live, /health/ready
AppHealthModule, // liveness: event-loop self-check
],
});Kubernetes probes map one-to-one onto the two filtered endpoints:
livenessProbe:
httpGet: { path: /health/live, port: 3000 }
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet: { path: /health/ready, port: 3000 }
periodSeconds: 5
failureThreshold: 2Zero indicators for a probe is healthy, not an error. An app with only readiness indicators still answers /health/live with 200 { healthy: true } — the process is up and serving; that's all liveness claims until you contribute a real self-check.
Don't ping backends from your own code paths. The health:status CLI (@modularityjs/health-cli) and the HTTP endpoints share the same pool — contribute an indicator once and every surface (CLI, HTTP, dev console) sees it.