Dependency Health Check — Complete Implementation Guide
In this tutorial, you will learn about Dependency Health Check. We cover key concepts, practical examples, and best practices to help you master this topic.
Dependency health checks verify that downstream services and resources the application depends on are available, providing a more accurate picture of service health than a simple Process check.
What You'll Learn
By the end of this tutorial, you will know how to check database connectivity, cache availability, downstream API health, and message queue status, then aggregate results into a comprehensive health response.
Why It Matters
A service can be running but unable to serve requests because its database is down. Dependency checks detect this scenario and prevent traffic from being routed to a service that can't function.
Real-World Use
DodaTech's API Gateway checks all downstream service health before routing requests. If the user service database is down, the gateway returns 503 immediately instead of timing out.
Dependency Health Learning Path
flowchart LR
A[Simple Health Check] --> B[Dependency Health]
B --> C[Database]
B --> D[Cache]
B --> E[Downstream APIs]
B --> F{You Are Here}
style F fill:#f90,color:#fff
Database Connectivity Check
Verify database connectivity with a lightweight query.
class DatabaseHealthCheck {
constructor(pool) {
this.pool = pool;
}
async check() {
const start = Date.now();
try {
const result = await this.pool.query("SELECT 1 AS health");
const latencyMs = Date.now() - start;
return {
name: "database",
healthy: true,
latencyMs,
detail: {
type: this.pool.constructor.name,
activeConnections: this.pool.totalCount,
idleConnections: this.pool.idleCount,
waitingQueries: this.pool.waitingCount
}
};
} catch (err) {
return {
name: "database",
healthy: false,
latencyMs: Date.now() - start,
error: err.message
};
}
}
}
const { Pool } = require("pg");
const pool = new Pool({ connectionString: "postgresql://localhost/test" });
const dbCheck = new DatabaseHealthCheck(pool);
dbCheck.check().then(r => console.log("DB:", r.healthy ? "ok" : "FAIL"));
Cache Health Check
Check cache availability with SET and GET operations.
class CacheHealthCheck {
constructor(redisClient) {
this.redis = redisClient;
}
async check() {
const start = Date.now();
const testKey = `health:${Date.now()}`;
try {
await this.redis.set(testKey, "ok", "EX", 5);
const value = await this.redis.get(testKey);
await this.redis.del(testKey);
return {
name: "cache",
healthy: value === "ok",
latencyMs: Date.now() - start,
detail: {
type: "redis",
pingMs: Date.now() - start
}
};
} catch (err) {
return {
name: "cache",
healthy: false,
latencyMs: Date.now() - start,
error: err.message
};
}
}
}
const Redis = require("ioredis");
const redis = new Redis();
const cacheCheck = new CacheHealthCheck(redis);
cacheCheck.check().then(r => console.log("Cache:", r.healthy ? "ok" : "FAIL"));
Message Queue Health Check
Verify message queue connectivity by checking queue status.
class QueueHealthCheck {
constructor(channel) {
this.channel = channel;
}
async check() {
const start = Date.now();
try {
const queueInfo = await this.channel.checkQueue("health-check");
return {
name: "message-queue",
healthy: true,
latencyMs: Date.now() - start,
detail: {
type: "rabbitmq",
messageCount: queueInfo.messageCount,
consumerCount: queueInfo.consumerCount
}
};
} catch (err) {
return {
name: "message-queue",
healthy: false,
latencyMs: Date.now() - start,
error: err.message
};
}
}
}
// Assuming amqplib channel
// const queueCheck = new QueueHealthCheck(channel);
// queueCheck.check().then(r => console.log("Queue:", r.healthy ? "ok" : "FAIL"));
console.log("Queue health check module initialized");
Downstream API Health Check
Check upstream services by calling their health endpoints.
class DownstreamAPIHealthCheck {
constructor() {
this.services = new Map();
}
register(name, url, timeout = 3000) {
this.services.set(name, { url, timeout });
}
async check() {
const results = [];
for (const [name, config] of this.services) {
const start = Date.now();
try {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), config.timeout);
const response = await fetch(config.url, {
signal: controller.signal
});
clearTimeout(timer);
const healthy = response.ok;
results.push({
name,
healthy,
latencyMs: Date.now() - start,
statusCode: response.status
});
} catch (err) {
results.push({
name,
healthy: false,
latencyMs: Date.now() - start,
error: err.code || err.message
});
}
}
return {
allHealthy: results.every(r => r.healthy),
services: results,
timestamp: new Date().toISOString()
};
}
}
const downstream = new DownstreamAPIHealthCheck();
downstream.register("user-service", "http://users:8080/healthz", 2000);
downstream.register("payment-service", "http://payments:8080/healthz", 2000);
downstream.check().then(r => {
console.log("Downstream all healthy:", r.allHealthy);
r.services.forEach(s => console.log(` ${s.name}: ${s.healthy ? "ok" : "FAIL"}`));
});
Aggregated Dependency Health
Combine all dependency checks into a single health response.
class AggregatedDependencyChecker {
constructor() {
this.checks = [];
}
add(name, checkFn) {
this.checks.push({ name, check: checkFn });
}
async checkAll(timeoutMs = 5000) {
const start = Date.now();
const results = await Promise.allSettled(
this.checks.map(({ name, check }) =>
Promise.race([
check(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error(`${name} timeout`)), timeoutMs)
)
])
)
);
const dependencies = results.map((r, i) => {
if (r.status === "fulfilled") {
return r.value;
}
return {
name: this.checks[i].name,
healthy: false,
error: r.reason.message
};
});
const allHealthy = dependencies.every(d => d.healthy);
const response = {
status: allHealthy ? "healthy" : "degraded",
dependencies,
totalTimeMs: Date.now() - start,
timestamp: new Date().toISOString()
};
return response;
}
}
const checker = new AggregatedDependencyChecker();
checker.add("database", async () => ({ healthy: true, latencyMs: 5 }));
checker.add("cache", async () => ({ healthy: true, latencyMs: 2 }));
checker.add("payment-api", async () => ({ healthy: true, latencyMs: 150 }));
checker.checkAll().then(r => {
console.log("Overall:", r.status);
console.log("Checks:", r.dependencies.length);
});
// Overall: healthy
// Checks: 3
Common Mistakes
Not setting timeouts on dependency checks -- A slow dependency blocks the health check response. All dependency checks must have timeouts.
Making dependency checks too expensive -- SELECT 1 is fine for databases. Don't run full queries or complex operations in health checks.
Failing open vs failing closed -- Decide: if the health check itself fails (timeout), do you assume the dependency is healthy or unhealthy? Failing closed (assume unhealthy) is safer.
Checking every dependency on every health check -- Tier your checks. Check critical dependencies every time, check secondary dependencies less frequently.
Not logging dependency failures -- When a dependency check fails, log the failure with details. This helps operators diagnose issues without additional debugging.
Practice Questions
What is the best query to check database health? SELECT 1, SELECT 1 AS health, or a simple SELECT NOW(). These are lightweight and don't depend on application data.
How do you handle a dependency that has degraded performance instead of being completely down? Add a latency threshold. If the dependency responds but takes > 2 seconds, mark it as degraded rather than healthy.
Should you check dependencies synchronously or asynchronously? Asynchronously. Run all dependency checks in parallel with Promise.all() and individual timeouts.
Challenge: Implement a dependency checker with circuit breaker protection to avoid hammering a failing dependency.
class CircuitProtectedDependencyCheck {
constructor(name, checkFn, options = {}) {
this.name = name;
this.checkFn = checkFn;
this.failureCount = 0;
this.threshold = options.threshold || 3;
this.cooldownPeriod = options.cooldownPeriod || 30000;
this.lastFailureTime = 0;
}
async check() {
if (this.failureCount >= this.threshold) {
if (Date.now() - this.lastFailureTime < this.cooldownPeriod) {
return { name: this.name, healthy: false, skipped: true, reason: "circuit-open" };
}
this.failureCount = 0;
}
try {
const result = await this.checkFn();
this.failureCount = 0;
return { ...result, name: this.name };
} catch (err) {
this.failureCount++;
this.lastFailureTime = Date.now();
return { name: this.name, healthy: false, error: err.message };
}
}
}
FAQ
Mini Project
Build a dependency health checker that validates database, cache, message queue, and downstream API health with configurable timeouts, circuit breaker protection, and aggregated JSON response.
class CompleteDependencyChecker {
constructor() {
this.dependencies = [];
}
addDependency(name, checkFn, options = {}) {
this.dependencies.push({
name,
check: checkFn,
timeout: options.timeout || 3000,
critical: options.critical !== false
});
}
async run() {
const results = await Promise.allSettled(
this.dependencies.map(dep =>
Promise.race([
dep.check(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error(`${dep.name} timeout`)), dep.timeout)
)
]).then(result => ({ ...result, name: dep.name, critical: dep.critical }))
)
);
const checks = results.map(r => {
if (r.status === "fulfilled") return r.value;
return { name: this.dependencies.find(d => d.name === r.reason.message.split(" ")[0]).name, healthy: false, critical: true, error: r.reason.message };
});
const criticalHealthy = checks.filter(c => c.critical && !c.healthy).length === 0;
return { healthy: criticalHealthy, checks };
}
}
const checker = new CompleteDependencyChecker();
checker.addDependency("postgres", async () => ({ healthy: true }), { critical: true });
checker.addDependency("redis", async () => ({ healthy: true }), { critical: true });
checker.addDependency("analytics-queue", async () => ({ healthy: false }), { critical: false });
checker.run().then(r => console.log("Healthy:", r.healthy));
// Healthy: true (analytics-queue is non-critical)
What's Next
Now that you understand dependency health checks, implement deep health checks that verify end-to-end functionality. Then explore Express.js health check implementation.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro