Health Check Types — Complete Implementation Guide
In this tutorial, you will learn about Health Check Types. We cover key concepts, practical examples, and best practices to help you master this topic.
Health checks come in several types: basic checks verify the Process is running, dependency checks verify downstream services, deep checks verify full functionality, and Composite checks aggregate multiple health signals.
What You'll Learn
By the end of this tutorial, you will understand the five main types of health checks, when to use each, and how to combine them for comprehensive health monitoring.
Why It Matters
Different failure modes require different detection strategies. A basic health check detects crashes but not data corruption. A deep health check detects logic errors but may be too expensive to run frequently.
Real-World Use
DodaTech uses a tiered health check system: basic HTTP checks run every 5 seconds, dependency checks every 15 seconds, and deep checks every 60 seconds. Each tier has different failure thresholds.
Health Check Types Learning Path
flowchart LR
A[Readiness vs Liveness] --> B[Health Check Types]
B --> C[Basic Checks]
B --> D[Dependency Checks]
B --> E[Deep Checks]
B --> F{You Are Here}
style F fill:#f90,color:#fff
Basic Health Check
The simplest health check returns 200 if the process is running and 503 if not.
class BasicHealthCheck {
constructor() {
this.startTime = Date.now();
}
handle(req, res) {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({
status: "healthy",
uptime: Math.floor((Date.now() - this.startTime) / 1000),
timestamp: new Date().toISOString()
}));
}
}
const http = require("http");
const basic = new BasicHealthCheck();
const server = http.createServer((req, res) => basic.handle(req, res));
server.listen(8080);
console.log("Basic health check at /healthz");
// GET /healthz -> {"status": "healthy", "uptime": 120, "timestamp": "..."}
Dependency Health Check
Dependency checks verify that downstream services are available.
class DependencyHealthCheck {
constructor() {
this.dependencies = new Map();
}
register(name, checkFn, timeout = 2000) {
this.dependencies.set(name, { checkFn, timeout });
}
async check() {
const results = [];
let allHealthy = true;
for (const [name, config] of this.dependencies) {
const start = Date.now();
try {
const result = await Promise.race([
config.checkFn(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("timeout")), config.timeout)
)
]);
results.push({
name,
healthy: true,
latencyMs: Date.now() - start
});
} catch (err) {
allHealthy = false;
results.push({
name,
healthy: false,
error: err.message,
latencyMs: Date.now() - start
});
}
}
return {
status: allHealthy ? "healthy" : "degraded",
dependencies: results,
timestamp: new Date().toISOString()
};
}
}
async function demo() {
const depCheck = new DependencyHealthCheck();
depCheck.register("database", async () => {
await new Promise(r => setTimeout(r, 100));
return true;
});
depCheck.register("redis", async () => {
await new Promise(r => setTimeout(r, 50));
return true;
});
depCheck.register("external-api", async () => {
throw new Error("Connection refused");
});
const result = await depCheck.check();
console.log("Status:", result.status);
result.dependencies.forEach(d => {
console.log(` ${d.name}: ${d.healthy ? "ok" : "FAIL"} (${d.latencyMs}ms)`);
});
}
demo();
// Status: degraded
// database: ok (100ms)
// redis: ok (50ms)
// external-api: FAIL (0ms)
Deep Health Check
Deep health checks verify end-to-end functionality, such as writing and reading a test record.
class DeepHealthCheck {
constructor(db, cache) {
this.db = db;
this.cache = cache;
}
async check() {
const checks = [];
checks.push(this.checkDatabase());
checks.push(this.checkCache());
checks.push(this.checkBusinessLogic());
const results = await Promise.allSettled(checks);
const allHealthy = results.every(r => r.status === "fulfilled" && r.value.healthy);
return {
status: allHealthy ? "healthy" : "unhealthy",
type: "deep",
checks: results.map(r => r.status === "fulfilled" ? r.value : { healthy: false, error: r.reason.message }),
timestamp: new Date().toISOString()
};
}
async checkDatabase() {
// Write a test record and read it back
const testId = `health-check-${Date.now()}`;
await this.db.set(testId, "ok");
const value = await this.db.get(testId);
await this.db.delete(testId);
return { healthy: value === "ok", component: "database" };
}
async checkCache() {
const testKey = `health:${Date.now()}`;
await this.cache.set(testKey, "ok", 10);
const value = await this.cache.get(testKey);
return { healthy: value === "ok", component: "cache" };
}
async checkBusinessLogic() {
// Verify a critical business operation works
return { healthy: true, component: "business-logic" };
}
}
const mockDb = { store: {}, async set(k, v) { this.store[k] = v; }, async get(k) { return this.store[k]; }, async delete(k) { delete this.store[k]; } };
const mockCache = { store: {}, async set(k, v) { this.store[k] = v; }, async get(k) { return this.store[k]; } };
const deep = new DeepHealthCheck(mockDb, mockCache);
deep.check().then(r => console.log("Deep check:", r.status));
// Deep check: healthy
Passive Health Check (Metrics-Based)
Passive checks monitor metrics like error rate and latency without active probing.
class PassiveHealthCheck {
constructor() {
this.metrics = {
requests: 0,
errors: 0,
latencies: []
};
this.windowMs = 60000;
this.windows = [];
}
recordRequest(latencyMs, success) {
this.windows.push({
time: Date.now(),
latencyMs,
success
});
this.cleanup();
}
cleanup() {
const cutoff = Date.now() - this.windowMs;
this.windows = this.windows.filter(w => w.time > cutoff);
}
check() {
this.cleanup();
const recent = this.windows;
if (recent.length < 10) {
return { status: "unknown", reason: "insufficient data" };
}
const errorRate = recent.filter(w => !w.success).length / recent.length;
const avgLatency = recent.reduce((s, w) => s + w.latencyMs, 0) / recent.length;
const p99Latency = recent.map(w => w.latencyMs).sort((a, b) => a - b)[Math.floor(recent.length * 0.99)];
const unhealthy = errorRate > 0.1 || avgLatency > 5000;
const degraded = errorRate > 0.05 || avgLatency > 2000;
return {
status: unhealthy ? "unhealthy" : (degraded ? "degraded" : "healthy"),
type: "passive",
metrics: {
requestCount: recent.length,
errorRate: errorRate.toFixed(3),
avgLatencyMs: Math.round(avgLatency),
p99LatencyMs: p99Latency
},
timestamp: new Date().toISOString()
};
}
}
const passive = new PassiveHealthCheck();
for (let i = 0; i < 100; i++) {
passive.recordRequest(Math.random() * 1000, Math.random() > 0.02);
}
console.log("Passive check:", passive.check().status);
// Passive check: healthy
Common Mistakes
Only implementing basic health checks -- A basic check only detects if the process is running. It misses dependency failures, data corruption, and degraded performance.
Making deep health checks too expensive -- A deep check that makes full database queries every 5 seconds creates unnecessary load. Run deep checks less frequently.
Not using passive checks -- Passive checks detect degradation that active checks might miss, like high latency but successful responses.
Ignoring health check latency -- If a health check takes 10 seconds and runs every 5 seconds, it creates a backlog. Set timeouts on all health check operations.
Not differentiating check types in monitoring -- Basic, dependency, and deep checks have different meanings. Track them separately in dashboards.
Practice Questions
What is the difference between active and passive health checks? Active checks probe the service with explicit requests. Passive checks analyze existing traffic metrics like error rate and latency.
When would you use a deep health check over a basic check? When you need to verify end-to-end functionality, such as database writes succeeding or business logic producing correct results.
How do you prevent dependency health checks from cascading? Set timeouts on all dependency checks. A slow dependency should not make the health check itself slow.
Challenge: Implement a tiered health checker that runs different check types at different intervals.
class TieredHealthChecker {
constructor() {
this.tiers = {
basic: { interval: 5000, lastRun: 0, check: async () => ({ healthy: true }) },
dependency: { interval: 15000, lastRun: 0, check: async () => ({ healthy: true }) },
deep: { interval: 60000, lastRun: 0, check: async () => ({ healthy: true }) }
};
}
async runDueChecks() {
const now = Date.now();
const results = [];
for (const [tier, config] of Object.entries(this.tiers)) {
if (now - config.lastRun >= config.interval) {
console.log(`Running ${tier} check`);
const result = await config.check();
config.lastRun = now;
results.push({ tier, ...result });
}
}
const allHealthy = results.every(r => r.healthy);
return { allHealthy, checks: results };
}
}
FAQ
Mini Project
Build a multi-type health check server that supports basic, dependency, and deep checks on different endpoints, each with configurable intervals and timeouts.
const http = require("http");
class MultiTypeHealthServer {
constructor() {
this.checks = new Map();
}
register(type, checkFn) {
this.checks.set(type, checkFn);
}
createServer() {
return http.createServer(async (req, res) => {
const url = new URL(req.url, "http://localhost");
const type = url.pathname.replace("/health/", "");
if (!this.checks.has(type)) {
res.writeHead(404);
res.end(JSON.stringify({ error: "unknown check type" }));
return;
}
try {
const result = await this.checks.get(type)();
res.writeHead(result.healthy ? 200 : 503);
res.end(JSON.stringify(result));
} catch (err) {
res.writeHead(503);
res.end(JSON.stringify({ healthy: false, error: err.message }));
}
});
}
}
const server = new MultiTypeHealthServer();
server.register("basic", async () => ({ healthy: true, uptime: process.uptime() }));
server.register("deep", async () => ({ healthy: true, db: "ok", cache: "ok" }));
server.createServer().listen(8080);
What's Next
Now that you understand health check types, implement a simple health check endpoint. Then learn about dependency health checks.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro