health-chk
v2.0.0
Published
A lightweight, dependency-free health check, readiness, and liveness library for Node.js applications — with Express, Fastify, and plain HTTP adapters, dependency monitoring, timeouts/retries, and full TypeScript support.
Maintainers
Readme
health-chk
A lightweight, zero runtime dependency health check, readiness, and liveness library for Node.js applications — with first-class Express, Fastify, and plain http support, dependency monitoring, timeouts/retries, and full TypeScript types.
v2.0 is a breaking rewrite. See CHANGELOG.md for migration notes from v1.
Features
/health,/ready, and/liveendpoints with standard, predictable payloads- Register dependency checks (database, cache, external APIs, anything async)
- Per-check timeout and retry support so one hung dependency can't hang your probes
- Three status levels —
healthy/warning/unhealthy— with critical vs. non-critical checks - Adapters for Express, Fastify, and plain Node
http— zero required peer dependencies - Production-safe error handling (raw error details hidden outside development by default)
- Pluggable logging (console, custom, or disabled)
- Full TypeScript types, dual CommonJS + ESM build
Installation
npm install health-chkBasic Usage
const HealthCheck = require("health-chk").default; // or: const { HealthCheck } = require("health-chk");
const health = new HealthCheck({
service: "orders-api",
version: "1.4.0",
});
health.addCheck("mongodb", async () => {
await mongoose.connection.db.stats();
return true;
});
const result = await health.getHealth();
console.log(result);
// {
// status: "healthy",
// timestamp: "2026-07-28T12:00:00.000Z",
// uptime: 5123.4,
// service: "orders-api",
// version: "1.4.0",
// environment: "production",
// checks: { mongodb: { status: "up", responseTime: 12 } }
// }ESM / TypeScript
import HealthCheck from "health-chk";
const health = new HealthCheck({ service: "orders-api", version: "1.4.0" });Express Integration
const express = require("express");
const HealthCheck = require("health-chk").default;
const { expressHealthCheck } = require("health-chk");
const app = express();
const health = new HealthCheck({ service: "orders-api", version: "1.4.0" });
health.addCheck("mongodb", async () => {
await mongoose.connection.db.stats();
return true;
});
health.addCheck(
"payment-service",
async () => checkPaymentAPI(),
{ timeout: 3000, retries: 2, critical: true }
);
// Mounts GET /health, GET /ready, GET /live
app.use(expressHealthCheck(health));
app.listen(3000);Prefer to mount routes individually? Use expressHandlers:
const { expressHandlers } = require("health-chk");
const { health: healthHandler, ready, live } = expressHandlers(health);
app.get("/health", healthHandler);
app.get("/ready", ready);
app.get("/live", live);Custom paths:
app.use(expressHealthCheck(health, {
healthPath: "/healthz",
readyPath: "/readyz",
livePath: "/livez",
}));Fastify Integration
const Fastify = require("fastify");
const HealthCheck = require("health-chk").default;
const { fastifyHealthCheck } = require("health-chk");
const app = Fastify();
const health = new HealthCheck({ service: "orders-api" });
health.addCheck("redis", async () => redisClient.ping() === "PONG");
app.register(fastifyHealthCheck(health));
app.listen({ port: 3000 });Plain Node http Integration
const http = require("http");
const HealthCheck = require("health-chk").default;
const { httpHealthCheck } = require("health-chk");
const health = new HealthCheck({ service: "orders-api" });
const handleHealth = httpHealthCheck(health);
http.createServer((req, res) => {
if (handleHealth(req, res)) return;
// ...your app's routing
}).listen(3000);Kubernetes Usage
/health gives a full diagnostic snapshot; /ready and /live are the two probes Kubernetes actually needs — kept intentionally cheap and separate so a slow dependency check never takes your pod out of rotation via the liveness probe.
livenessProbe:
httpGet:
path: /live
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3- Liveness (
/live) always returns{ "status": "alive" }with a 200 — it never touches dependency checks, so a flaky database won't cause Kubernetes to kill and restart a perfectly healthy process. - Readiness (
/ready) runs all registered checks and returns503when any critical check is down, so the pod is pulled from the load balancer until it recovers.
Custom Checks
health.addCheck(
"payment-service",
async () => {
const ok = await checkPaymentAPI();
return ok; // boolean
},
{ timeout: 3000, retries: 2, critical: true }
);
// Or register with more detail via .register()
health.register({
name: "redis",
critical: false, // failure only produces a "warning", never "unhealthy"
check: async () => {
const pong = await redisClient.ping();
return { status: pong === "PONG" ? "up" : "down", message: `ping: ${pong}` };
},
});A check function may:
- return/resolve
true→status: "up" - return/resolve
false→status: "down" - return/resolve
undefined(or throw nothing) →status: "up" - return/resolve
{ status: "up" | "down" | "warning", message?, error? }for full control - throw / reject → treated as
status: "down"(error message sanitized outside development)
Timeouts & Retries
const health = new HealthCheck({
timeout: 3000, // default for all checks (ms)
retries: 1, // default retry count for all checks
retryDelay: 100,
});
// Per-check overrides:
health.addCheck("slow-report-service", checkFn, { timeout: 8000, retries: 3, retryDelay: 250 });A check that exceeds its timeout, or throws on every attempt (including retries), is marked "down" — it never hangs the endpoint.
Status Levels
| Status | Meaning |
| ----------- | ---------------------------------------------------------------- |
| healthy | All critical checks are up. |
| warning | A non-critical check is down/warning, but nothing critical failed. |
| unhealthy | At least one critical check is down. |
API Reference
new HealthCheck(options?)
| Option | Type | Default | Description |
| -------------- | ------------------------ | ---------------------------------------- | --------------------------------------------- |
| service | string | "application" | Reported service name |
| version | string | npm_package_version env var or "0.0.0" | Reported app version |
| environment | string | NODE_ENV or "development" | Reported environment |
| timeout | number | 3000 | Default per-check timeout (ms) |
| retries | number | 0 | Default per-check retry count |
| retryDelay | number | 100 | Delay between retries (ms) |
| exposeErrors | boolean | true outside production | Whether raw error messages are included |
| logger | boolean \| Logger | false | true for console logging, or a custom logger |
| envKeys | string[] | [] | Allowlisted env vars to include in /health |
Methods
health.register(definition)— register a check via{ name, check, critical?, timeout?, retries?, retryDelay? }health.addCheck(name, checkFn, options?)— shorthand forregisterhealth.removeCheck(name)— unregister a checkhealth.listChecks()— list registered check nameshealth.configure(options)— merge new options after constructionhealth.getHealth()— returns the/healthpayload (Promise<HealthResponse>)health.getReadiness()— returns the/readypayload (Promise<ReadinessResponse>)health.getLiveness()— returns the/livepayload (LivenessResponse, synchronous)
Adapters
expressHealthCheck(health, options?)— combined Express middleware for all three routesexpressHandlers(health)—{ health, ready, live }individual Express handlersfastifyHealthCheck(health, options?)— Fastify pluginhttpHealthCheck(health, options?)— plainhttp/httpsrequest handler, returnstrueif it handled the request
All adapters accept { healthPath, readyPath, livePath } to override the default /health, /ready, /live routes.
TypeScript Usage
import HealthCheck, {
createHealthCheck,
expressHealthCheck,
type HealthCheckDefinition,
type HealthResponse,
type CheckResult,
} from "health-chk";
const health: HealthCheck = createHealthCheck({ service: "orders-api" });
const dbCheck: HealthCheckDefinition = {
name: "postgres",
critical: true,
check: async (): Promise<boolean> => {
await pool.query("SELECT 1");
return true;
},
};
health.register(dbCheck);
const result: HealthResponse = await health.getHealth();
const dbResult: CheckResult | undefined = result.checks?.postgres;Security Notes
- Raw error messages/stacks from failing checks are hidden by default in production (
NODE_ENV=production) — only a generic"Health check failed"message is returned unlessexposeErrors: trueis set explicitly. - Environment variables are never included in responses unless you explicitly allowlist keys via
envKeys. register()/addCheck()validate thatnameandcheckare present, failing fast with a clear error at registration time rather than at request time.
Testing
npm test # run the Jest suite
npm run test:coverage # run with coverage
npm run typecheck # tsc --noEmit
npm run build # produce dist/ (CJS + ESM + .d.ts)License
MIT © 2025 Gulam Ashraf. See LICENSE.
