The maintained, zero-dependency, TypeScript-first health check for Node.js, with a browser dashboard built in.
One endpoint returns JSON to your probes and monitors, and a human-friendly dashboard to your browser. The dashboard is self-contained (inline CSS, no JS, no CDN), so it renders offline, on an air-gapped box, and under a default-src 'self' CSP. Failure reasons show inline on each check row, with the full unmasked output in a hover bubble. It also tells degraded (healthy, but with a failing optional check) apart from all-green, while the JSON output and status code stay the same (healthy/200).
npm install node-healthzimport express from 'express'
import * as healthz from 'node-healthz'
const app = express()
// Returns an Express-style handler; serves /healthz by default.
app.use(
healthz.express({
checks: [
{
id: 'PostgreSQL',
required: true,
fn: async () => {
await pool.query('SELECT 1')
},
},
{
id: 'Redis', // not required: failure won't flip the report to NOT_OK
fn: async () => {
await redis.ping()
},
},
],
}),
)
app.listen(3000)# JSON for machines (probes, monitors): the default response
$ curl -si http://localhost:3000/healthz
HTTP/1.1 200 OK
{
"status": "OK",
"version": "2.1.0",
"appName": "app",
"checks": [
{ "id": "PostgreSQL", "status": "OK", "required": true, "t": "1,003ms", "output": "<masked>" },
{ "id": "Redis", "status": "OK", "required": false, "t": "11ms", "output": "<masked>" }
]
}
# Same URL with `Accept: text/html` → the HTML dashboard
$ open http://localhost:3000/healthzversion is node-healthz's own library version (stamped automatically); appName defaults to "app". See Service identity to name the service, environment, and build.
A failing required check turns the report NOT_OK with HTTP 500; failing optional checks keep it OK / 200. Check output is masked by default so secrets in connection errors never leak through the health endpoint.
When the same /healthz shape is served by many services, environments, and deploys, tell them apart with three optional flat fields:
app.use(
healthz.express({
appName: 'checkout-api', // optional, defaults to "app"
envName: process.env.NODE_ENV, // optional, omitted when unset
buildVer: process.env.GIT_SHA, // optional: a version, tag, or SHA; omitted when unset
checks: [{ id: 'PostgreSQL', required: true, fn: () => pool.query('SELECT 1') }],
}),
)They surface in both outputs. In JSON, appName is always present and envName/buildVer appear only when set:
In the dashboard, the identity becomes an eyebrow above the status hero (CHECKOUT-API · PRODUCTION) and a footer that reads node-healthz · v2.1.0 on the left with build 8f3c1a2 on the right. There is no application-version field — the only version shown is node-healthz's own, as a provenance stamp. All identity values are HTML-escaped before rendering.
| node-healthz | @nestjs/terminus | @godaddy/terminus | lightship | |
|---|---|---|---|---|
| Runtime dependencies | 0 | 2 (+ 17 peer) | 1 | 4 |
| Last release | 2026 | 2026 | 2023 | 2026 |
| TypeScript-first | ✅ | ✅ | typings only | ✅ |
| HTML dashboard | ✅ | ❌ | ❌ | ❌ |
| Latency tiers | ✅ | ❌ | ❌ | ❌ |
| Masking by default | ✅ | ❌ | ❌ | ❌ |
| Framework integration | any (or none) | NestJS only | bare http.Server |
own HTTP server |
Verified against the npm registry, June 2026.
app.use(
healthz.express({
path: '/readyz',
checks: [{ id: 'db', required: true, fn: pingDb }],
}),
)app.use(
healthz.express({
checks: [
{
id: 'Stripe',
fn: checkStripe,
maskOutput: false, // default is true (masking-by-default)
latencyLevents: [50, 250], // default [100, 500] (note: key is misspelled in the public API)
},
],
}),
)import { createServer } from 'node:http'
import * as healthz from 'node-healthz'
createServer(async (req, res) => {
const result = await healthz.check({ checks })
res.statusCode = healthz.status(result) // 200 healthy / 500 not-ok
res.end(JSON.stringify(healthz.json(result))) // or healthz.html(result)
}).listen(3000)// Global timeout for every check (default 5000 ms) …
app.use(healthz.express({ timeout: 2000, checks }))
// … or per check, overriding the global
const result = await healthz.check({
timeout: 1000,
checks: [
{ id: 'db', required: true, fn: pingDb }, // 1s (global)
{ id: 'flakyVendor', fn: checkVendor, timeout: 8000 }, // 8s (own)
],
})Precedence per check: check.timeout → top-level timeout → 5000. A check that loses the race reports TIMEOUT.


{ "status": "OK", "version": "2.1.0", // node-healthz's own version, automatic "appName": "checkout-api", "envName": "production", "buildVer": "8f3c1a2", "checks": [ /* ... */ ] }