@firebreak/vitals
v3.0.1
Published
Deep healthcheck endpoints following the HEALTHCHECK_SPEC format
Downloads
1,524
Readme
Vitals (Node.js)
Structured healthcheck endpoints for Node.js services. Register health checks, run them concurrently with timeouts, and expose them via Express or Next.js. Health checks always run — a configurable detail policy controls whether callers see a summary status or full diagnostic results.
Install
npm install @firebreak/vitalsPeer dependencies are optional — install only what you need:
npm install pg # for PostgreSQL checks
npm install ioredis # for Redis checks
npm install express # for Express integrationQuick Start (Express)
import { HealthcheckRegistry, extractToken, verifyToken } from '@firebreak/vitals';
import { createHealthcheckMiddleware } from '@firebreak/vitals/express';
import { pgPoolCheck } from '@firebreak/vitals/checks/postgres';
import { ioredisClientCheck } from '@firebreak/vitals/checks/redis';
import { httpCheck } from '@firebreak/vitals/checks/http';
import express from 'express';
const registry = new HealthcheckRegistry({ defaultTimeout: 5000 });
registry.add('postgres', pgPoolCheck(pool));
registry.add('redis', ioredisClientCheck(redisClient));
registry.add('api', httpCheck('https://api.example.com/health'));
const app = express();
app.get('/vitals', createHealthcheckMiddleware({
registry,
resolveDetail: (req) => {
const token = extractToken({
queryParams: req.queryParams as Record<string, string>,
authorizationHeader: req.authorizationHeader as string | null,
});
if (token && verifyToken(token, process.env.VITALS_TOKEN!)) return 'full';
return 'summary';
},
metadata: {
build: process.env.BUILD_SHA,
buildTimestamp: process.env.BUILD_TIMESTAMP,
},
}));Hit /vitals without a valid token to get a summary response with real health status:
{ "status": "healthy", "timestamp": "...", "build": "stg-45d76e5", "buildTimestamp": "2025-02-25T19:41:56Z" }When resolveDetail returns 'full', the full response with check results is returned.
Quick Start (Next.js)
// app/vitals/route.ts
import { HealthcheckRegistry, extractToken, verifyToken } from '@firebreak/vitals';
import { createNextHandler } from '@firebreak/vitals/next';
import { pgClientCheck } from '@firebreak/vitals/checks/postgres';
import { httpCheck } from '@firebreak/vitals/checks/http';
const registry = new HealthcheckRegistry({ defaultTimeout: 5000 });
if (process.env.DATABASE_URL) {
registry.add('postgres', pgClientCheck(process.env.DATABASE_URL));
}
registry.add('api', httpCheck(`${process.env.API_BASE_URL}/health`));
export const GET = createNextHandler({
registry,
resolveDetail: (req) => {
const token = extractToken({
queryParams: req.queryParams as Record<string, string>,
authorizationHeader: req.authorizationHeader as string | null,
});
if (token && verifyToken(token, process.env.VITALS_TOKEN!)) return 'full';
return 'summary';
},
metadata: {
build: process.env.BUILD_SHA,
},
});API
HealthcheckRegistry
const registry = new HealthcheckRegistry({ defaultTimeout: 5000 });registry.add(name, checkFn, options?)
Register a named async check function. Options: { timeout?: number }.
registry.add('service', async () => ({
status: Status.HEALTHY,
message: '',
}), { timeout: 3000 });Check functions return a CheckInput with status and message. The latencyMs field is optional — the registry measures wall-clock latency automatically using performance.now().
registry.check(name, checkFn?, options?)
Decorator-style registration. Can be used as a method decorator or called directly.
registry.run()
Execute all registered checks concurrently. Returns a HealthcheckResponse with the worst overall status and registry-measured latency for each check.
const response = await registry.run();
// { status: 2, timestamp: '...', checks: { service: { status: 2, latencyMs: 12, message: '' } } }
const json = toJson(response);
// { status: 'healthy', timestamp: '...', checks: { ... } }Status
import { Status, statusToLabel, statusFromString, httpStatusCode } from '@firebreak/vitals';
Status.HEALTHY // 2
Status.DEGRADED // 1
Status.OUTAGE // 0
statusToLabel(Status.HEALTHY) // 'healthy'
statusFromString('degraded') // Status.DEGRADED
httpStatusCode(Status.HEALTHY) // 200
httpStatusCode(Status.OUTAGE) // 503Built-in Checks
HTTP
import { httpCheck } from '@firebreak/vitals/checks/http';
// Basic URL check — returns HEALTHY if response is 2xx
registry.add('api', httpCheck('https://api.example.com/health'));
// With options
registry.add('api', httpCheck('https://api.example.com/health', {
method: 'HEAD',
headers: { 'X-Api-Key': 'secret' },
timeout: 3000, // default: 5000ms
}));PostgreSQL
import { pgClientCheck, pgPoolCheck } from '@firebreak/vitals/checks/postgres';
// Fresh connection each time (good for validating connectivity)
registry.add('pg', pgClientCheck('postgresql://localhost:5432/mydb'));
// Existing pg.Pool (good for production — reuses connections)
registry.add('pg', pgPoolCheck(pool));Redis
import { ioredisCheck, ioredisClientCheck } from '@firebreak/vitals/checks/redis';
// Fresh connection each time
registry.add('redis', ioredisCheck('redis://localhost:6379'));
// Existing ioredis client
registry.add('redis', ioredisClientCheck(client));Custom Checks
Check functions only need to return status and message. The registry measures latency automatically:
registry.add('databricks', async () => {
try {
await databricksService.query('SELECT 1');
return { status: Status.HEALTHY, message: '' };
} catch (error) {
return { status: Status.OUTAGE, message: error.message };
}
});Sync Checks
Wrap synchronous functions to use as health checks:
import { syncCheck, Status } from '@firebreak/vitals';
registry.add('disk', syncCheck(() => ({
status: checkDiskSpace('/') > 1_000_000_000 ? Status.HEALTHY : Status.DEGRADED,
message: '',
})));Framework Integrations
Express
import { createHealthcheckMiddleware } from '@firebreak/vitals/express';
app.get('/vitals', createHealthcheckMiddleware({
registry,
resolveDetail: (req) => { // optional — omit for always-summary
const token = extractToken({
queryParams: req.queryParams as Record<string, string>,
authorizationHeader: req.authorizationHeader as string | null,
});
if (token && verifyToken(token, 'my-secret-token')) return 'full';
return 'summary';
},
metadata: { // optional — included in summary & full responses
build: 'stg-45d76e5',
},
}));Next.js (App Router)
import { createNextHandler } from '@firebreak/vitals/next';
export const GET = createNextHandler({
registry,
resolveDetail: (req) => {
const token = extractToken({
queryParams: req.queryParams as Record<string, string>,
authorizationHeader: req.authorizationHeader as string | null,
});
if (token && verifyToken(token, process.env.VITALS_TOKEN!)) return 'full';
return 'summary';
},
metadata: { build: process.env.BUILD_SHA },
});Framework-Agnostic Handler
For other frameworks, use the generic handler directly:
import { createHealthcheckHandler, extractToken, verifyToken } from '@firebreak/vitals';
const handle = createHealthcheckHandler({
registry,
resolveDetail: (req) => {
const token = extractToken({
queryParams: req.queryParams as Record<string, string>,
authorizationHeader: req.authorizationHeader as string | null,
});
if (token && verifyToken(token, process.env.VITALS_TOKEN!)) return 'full';
return 'summary';
},
metadata: { build: 'stg-45d76e5' },
});
// resolveDetail returns 'summary' → summary response (real status, no checks)
const summary = await handle({});
// { status: 200, body: { status: 'healthy', timestamp: '...', build: 'stg-45d76e5' } }
// resolveDetail returns 'full' → full response with check details
const full = await handle({ queryParams: { token: '...' } });
// { status: 200, body: { status: 'healthy', timestamp: '...', build: 'stg-45d76e5', checks: { ... } } }Summary / Full Responses
Health checks always run. The resolveDetail option controls how much detail is exposed in the response:
| resolveDetail returns | Response |
|-------------------------|----------|
| 'summary' (or omitted) | Summary — real HTTP status with { status: "healthy"/"degraded"/"outage", timestamp, ...metadata } |
| 'full' | Full — real HTTP status with check results + metadata |
When resolveDetail is not provided, the handler returns a summary response.
The resolveDetail function receives the request object ({ ip, headers, queryParams, authorizationHeader }) and returns 'full' or 'summary' (or a promise of either). This lets you implement any detail policy — token-based, IP-based, header-based, or always-full for internal services:
// Always full (internal service behind a private network)
createHealthcheckHandler({
registry,
resolveDetail: () => 'full',
});Both summary and full responses reflect real health status and return the correct HTTP status code (200 for healthy, 503 for degraded/outage). The summary response lets load balancers and uptime monitors get real health status without exposing internal check details. The full response adds the checks object with per-check diagnostics.
Metadata
Attach static key-value pairs that appear in both summary and full responses:
createHealthcheckHandler({
registry,
metadata: {
build: 'stg-45d76e5',
buildTimestamp: '2025-02-25T19:41:56Z',
region: 'us-east-1',
},
});Metadata values can be string, number, or boolean. Keys status, timestamp, checks, and cachedAt are reserved -- passing them throws at handler creation time.
summaryMetadata
By default, summary responses include all metadata fields. Use summaryMetadata to override what appears in summary responses while keeping metadata for full responses:
createHealthcheckHandler({
registry,
metadata: {
build: 'stg-45d76e5',
buildTimestamp: '2025-02-25T19:41:56Z',
region: 'us-east-1',
},
// Summary responses only include status + timestamp (no build info)
summaryMetadata: {},
});You can also include a subset of fields:
createHealthcheckHandler({
registry,
metadata: { build: 'stg-45d76e5', region: 'us-east-1', version: '1.0.0' },
// Summary responses include only version
summaryMetadata: { version: '1.0.0' },
});When summaryMetadata is omitted, summary responses fall back to using metadata.
Utilities
extractToken and verifyToken are exported helpers for use inside your resolveDetail function.
import { verifyToken, extractToken } from '@firebreak/vitals';
// Extract a token from query params (?token=...) or Authorization header
const token = extractToken({
queryParams: { token: 'abc' },
authorizationHeader: 'Bearer abc',
});
// Timing-safe SHA-256 comparison
verifyToken('provided-token', 'expected-token'); // booleanResponse Format
Summary response (resolveDetail returns 'summary' or is omitted):
{
"status": "healthy",
"timestamp": "2025-02-26T12:00:00.000Z",
"build": "stg-45d76e5",
"buildTimestamp": "2025-02-25T19:41:56Z"
}Full response (resolveDetail returns 'full'):
{
"status": "healthy",
"timestamp": "2025-02-26T12:00:00.000Z",
"build": "stg-45d76e5",
"buildTimestamp": "2025-02-25T19:41:56Z",
"checks": {
"postgres": {
"status": "healthy",
"latencyMs": 4,
"message": ""
},
"redis": {
"status": "healthy",
"latencyMs": 1,
"message": ""
}
}
}Both summary and full responses return real HTTP status codes:
| Status | HTTP Code |
|--------|-----------|
| "healthy" | 200 |
| "degraded" | 503 |
| "outage" | 503 |
Requirements
- Node.js >= 20.0.0
