@quanticjs/health
v8.1.0
Published
Health check module — liveness, readiness, startup probes with auto-detection, caching, and multiple transports
Readme
@quanticjs/health
Health check module for QuanticJS applications. Provides liveness, readiness, and startup probes with auto-detection, caching, and multiple transports for both HTTP and non-HTTP apps.
Installation
Already included in the @quanticjs/quanticjs umbrella. For standalone use:
npm install @quanticjs/healthQuick Start
HTTP API (most common)
import { Module } from '@nestjs/common';
import { QuanticCoreModule } from '@quanticjs/core';
import { QuanticHealthModule } from '@quanticjs/health';
@Module({
imports: [
QuanticCoreModule.forRoot(),
QuanticHealthModule.forRoot({
transport: { type: 'controller' },
}),
],
})
export class AppModule {}This registers three endpoints on your existing NestJS server:
| Endpoint | Probe | Purpose |
|----------|-------|---------|
| GET /health/live | Liveness | Is the process alive? Restart if not. |
| GET /health/ready | Readiness | Can this instance serve traffic? Remove from LB if not. |
| GET /health/startup | Startup | Has the app finished initializing? |
All endpoints are public (no auth required) and return:
{
"status": "ok",
"checks": {
"database": { "status": "ok", "latency_ms": 2 },
"redis": { "status": "ok", "latency_ms": 1 }
},
"timestamp": "2026-05-18T10:32:01.123Z"
}Status code is 200 when all checks pass, 503 when any check fails.
Auto-Detection
When autoDetect is enabled (the default), the module scans the NestJS DI container on startup and registers checks automatically:
| Dependency | Detection | Probe | Check |
|------------|-----------|-------|-------|
| TypeORM DataSource | typeorm installed + DataSource in DI | readiness | SELECT 1 |
| Redis (REDIS_CLIENT) | QuanticRedisModule imported | readiness | redis.ping() |
| Event loop | Always | liveness | setImmediate responsiveness |
No configuration needed for framework-managed dependencies. The module logs what it discovers:
[HealthRegistry] Auto-detected DataSource → database readiness check
[HealthRegistry] Auto-detected Redis → redis readiness check
[HealthRegistry] readiness: [database, redis]
[HealthRegistry] liveness: [event_loop]Disable with autoDetect: false.
Custom Checks
Add product-specific checks for dependencies the framework doesn't know about.
Function checks
QuanticHealthModule.forRoot({
transport: { type: 'controller' },
readiness: [
{
name: 'minio',
check: async () => {
const exists = await minioClient.bucketExists('my-bucket');
if (!exists) throw new Error('Bucket not found');
},
timeoutMs: 5000, // optional, default 3000
},
],
startup: [
{
name: 'migrations',
check: async () => {
if (!migrationService.isComplete()) throw new Error('Migrations pending');
},
},
],
})The check function should resolve when healthy and throw when unhealthy.
HTTP checks
For external services, pass a url instead of a check function:
QuanticHealthModule.forRoot({
transport: { type: 'controller' },
readiness: [
{ name: 'ai-gateway', url: 'http://ai-gateway:3005/health' },
{ name: 'kogito', url: 'http://kogito:8080/q/health/live', timeoutMs: 5000 },
],
})HTTP checks expect a 2xx response. Any other status or network error marks the check as unhealthy.
Transports
The transport determines how probes reach the app.
controller — HTTP API apps
Mounts on the existing NestJS HTTP server. Endpoints: /health/live, /health/ready, /health/startup.
QuanticHealthModule.forRoot({
transport: { type: 'controller' },
})standalone — Workers, consumers, queue processors
Spins up a minimal HTTP server on a separate port. No NestJS overhead — just raw http.createServer.
QuanticHealthModule.forRoot({
transport: { type: 'standalone', port: 9091 },
})Endpoints: /live, /ready, /startup (no /health prefix since this is a dedicated health server).
Use this for apps that have no HTTP server but still need K8s/Docker health probes.
file — Cron jobs, one-shot migration scripts
Writes a JSON file when healthy, deletes it when unhealthy. Probes check file existence.
QuanticHealthModule.forRoot({
transport: {
type: 'file',
path: '/tmp/.healthy', // optional, default '/tmp/.healthy'
intervalMs: 10000, // optional, default 10000
},
})Docker healthcheck: test: ["CMD", "test", "-f", "/tmp/.healthy"]
none — Programmatic only
No endpoints, no files. Inject HealthRegistry directly for custom usage:
@Injectable()
export class MyService {
constructor(private readonly health: HealthRegistry) {}
async checkDeps() {
const report = await this.health.evaluate('readiness');
if (report.status === 'error') {
// handle unhealthy state
}
}
}Result Caching
By default, check results are cached for 5 seconds to avoid hammering dependencies on high-frequency probe intervals.
QuanticHealthModule.forRoot({
transport: { type: 'controller' },
cacheTtlMs: 10000, // cache for 10s
})Set to 0 to disable caching (every probe hit runs all checks).
Shutdown Integration
When the app receives SIGTERM, the module:
- Flips readiness to
503immediately (beforeGracefulShutdownServiceruns) - Waits
shutdownDelayMs(default 5s) for the load balancer to observe the change - Then
GracefulShutdownServiceproceeds with its drain + cleanup
{
"status": "error",
"reason": "shutting_down",
"checks": {},
"timestamp": "2026-05-18T10:35:00.000Z"
}This ensures no new traffic arrives while the app is draining in-flight work.
QuanticHealthModule.forRoot({
transport: { type: 'controller' },
shutdownAware: true, // default true
shutdownDelayMs: 5000, // default 5000
})Disable with shutdownAware: false if you don't need this behavior.
Docker Compose
backend:
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:3000/health/ready"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
worker:
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:9091/ready"]
interval: 10s
timeout: 5s
retries: 3
start_period: 15s
migration-job:
healthcheck:
test: ["CMD", "test", "-f", "/tmp/.healthy"]
interval: 5s
retries: 3Kubernetes
# HTTP API
spec:
containers:
- name: backend
livenessProbe:
httpGet:
path: /health/live
port: 3000
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: 3000
periodSeconds: 10
failureThreshold: 3
startupProbe:
httpGet:
path: /health/startup
port: 3000
periodSeconds: 5
failureThreshold: 30
---
# Worker (standalone transport)
spec:
containers:
- name: worker
livenessProbe:
httpGet:
path: /live
port: 9091
readinessProbe:
httpGet:
path: /ready
port: 9091Registering Checks Programmatically
Inject HealthRegistry to register checks at runtime:
@Injectable()
export class KafkaConsumer implements OnModuleInit {
constructor(private readonly health: HealthRegistry) {}
onModuleInit() {
this.health.register('readiness', 'kafka', async () => {
if (!this.consumer.isConnected()) throw new Error('Kafka disconnected');
});
}
}Full Options Reference
interface HealthModuleOptions {
transport: HealthTransport;
autoDetect?: boolean; // default: true
cacheTtlMs?: number; // default: 5000
shutdownAware?: boolean; // default: true
shutdownDelayMs?: number; // default: 5000
readiness?: HealthCheckConfig[];
startup?: HealthCheckConfig[];
liveness?: HealthCheckConfig[];
}
// Custom function check
interface CustomHealthCheck {
name: string;
check: () => Promise<void>; // resolve = healthy, throw = unhealthy
timeoutMs?: number; // default: 3000
}
// HTTP endpoint check
interface HttpHealthCheck {
name: string;
url: string;
timeoutMs?: number; // default: 3000
}
type HealthTransport =
| { type: 'controller' }
| { type: 'standalone'; port: number }
| { type: 'file'; path?: string; intervalMs?: number }
| { type: 'none' };Complete Example (AutoFlux-style)
import { Module } from '@nestjs/common';
import { QuanticCoreModule } from '@quanticjs/core';
import { QuanticRedisModule } from '@quanticjs/redis';
import { QuanticHealthModule } from '@quanticjs/health';
@Module({
imports: [
QuanticCoreModule.forRoot(),
QuanticRedisModule.forRoot(),
// TypeOrmModule.forRoot({ ... }),
QuanticHealthModule.forRoot({
transport: { type: 'controller' },
// Auto-detects: database (TypeORM), redis, event_loop
// Add product-specific checks below:
readiness: [
{ name: 'minio', check: async () => {
const exists = await minioClient.bucketExists('autoflux');
if (!exists) throw new Error('Bucket missing');
}},
{ name: 'ai-gateway', url: 'http://ai-gateway:3005/health' },
{ name: 'kogito', url: 'http://kogito:8080/q/health/live', timeoutMs: 5000 },
],
startup: [
{ name: 'templates-seeded', check: async () => {
if (!seederService.isComplete()) throw new Error('Seeding in progress');
}},
],
cacheTtlMs: 5000,
shutdownDelayMs: 5000,
}),
],
})
export class AppModule {}