Proactive Dependency Checks for Node.js Projects
proactive-deps
is a lightweight Node.js library that makes it easy to monitor the health of your app’s runtime dependencies. It lets you define custom async checks for critical external services—like databases, APIs, queues, etc.—and provides real-time status tracking, latency metrics, and Prometheus-style exports.
Long-running services often depend on external systems, and when those go down, it can cause confusing or delayed failures. proactive-deps helps you proactively detect issues before they become full outages—without adding brittle health check logic to your app’s core business logic.
- ✅ Custom async health checks per dependency
- 🧠 Smart result caching (set TTL per dependency)
- 📈 Built-in latency tracking
- 📊 Prometheus-style metrics export
- 🧪 Live status summaries for dashboards or alerts
npm install proactive-deps
Once you have registered your dependencies, you must call monitor.startDependencyCheckInterval()
to start the automated interval that periodically checks the status of all registered dependencies. This ensures that the health of your dependencies is monitored continuously at the configured interval.
If you need to stop the automated checks (e.g., during application shutdown or maintenance), you can call monitor.stopDependencyCheckInterval()
to stop the interval.
import {
DependencyMonitor,
SUCCESS_STATUS_CODE,
ERROR_STATUS_CODE,
} from 'proactive-deps';
const monitor = new DependencyMonitor();
monitor.register({
name: 'redis',
description: 'Redis cache layer',
impact: 'Responses may be slower due to missing cache.',
check: async () => {
try {
// Simulate a health check (e.g., ping Redis)
await redis.ping();
return SUCCESS_STATUS_CODE; // Healthy status
} catch (error) {
return {
code: ERROR_STATUS_CODE,
error,
errorMessage: 'Redis connection failed',
}; // Unhealthy status with error details
}
},
cacheDurationMs: 10000, // Cache results for 10 seconds
refreshThresholdMs: 5000, // Refresh results if older than 5 seconds
checkDetails: {
type: 'database',
server: 'localhost',
database: 'cache',
dbType: 'redis',
}, // Optional details about the dependency
});
monitor.startDependencyCheckInterval();
The skip
boolean allows you to mark a dependency as skipped, meaning its health check will not be executed. Skipped dependencies are considered healthy by default, with a latency of 0
and the skipped
flag set to true
.
There are several scenarios where skipping a dependency might be useful:
- Temporarily Disabled Services: If a service is temporarily offline or not in use, you can skip its health check to avoid unnecessary errors or alerts.
- Development or Testing: During development or testing, you might want to skip certain dependencies that are not yet implemented or are mocked.
monitor.register({
name: 'external-service',
description: 'An external service that is temporarily disabled',
impact: 'No impact since this service is currently unused.',
check: async () => {
// This check will not run because the dependency is skipped
return { code: SUCCESS_STATUS_CODE };
},
skip: true, // Mark this dependency as skipped
});
When a dependency is skipped, its status will look like this:
{
name: 'external-service',
description: 'An external service that is temporarily disabled',
impact: 'No impact since this service is currently unused.',
healthy: true,
health: {
state: 'OK',
code: 0,
latency: 0,
skipped: true,
},
lastChecked: '2025-04-13T12:00:00Z',
}
The checkDetails
property allows you to provide additional metadata about the dependency being monitored. This can be useful for:
- Debugging: Including details like the server, database name, or API endpoint can help quickly identify the source of an issue.
-
Monitoring Dashboards: Exposing
checkDetails
in status summaries or metrics can provide more context for operations teams. -
Custom Alerts: Use
checkDetails
to include specific information in alerts, such as the type of dependency or its criticality.
monitor.register({
name: 'user-service',
description: 'User management REST API',
impact: 'User-related operations may fail.',
check: async () => {
try {
const response = await fetch('https://api.example.com/users/health');
if (response.ok) {
return SUCCESS_STATUS_CODE;
} else {
return {
code: ERROR_STATUS_CODE,
errorMessage: `Unexpected status: ${response.status}`,
};
}
} catch (error) {
return {
code: ERROR_STATUS_CODE,
error,
errorMessage: 'Failed to reach user-service API',
};
}
},
checkDetails: {
type: 'rest',
url: 'https://api.example.com/users/health',
method: 'GET',
}, // Optional details about the dependency
});
A registered dependency check can return either a status code or an object with additional details.
You can return just the status code:
SUCCESS_STATUS_CODE;
Or an object with the status code:
{
code: SUCCESS_STATUS_CODE,
}
-
code
: A status code indicating success (e.g.,SUCCESS_STATUS_CODE
). -
error
: Should beundefined
when the dependency is healthy. -
errorMessage
: Should beundefined
when the dependency is healthy.
You can return an object with the status code and optional error details:
{
code: ERROR_STATUS_CODE,
error: new Error('Connection failed'),
errorMessage: 'Redis connection failed',
}
-
code
: A status code indicating an error (e.g.,ERROR_STATUS_CODE
). -
error
: AnError
object describing the issue. -
errorMessage
: A string describing the error in detail.
This flexibility allows you to return a simple status code for healthy dependencies or provide detailed error information when issues are encountered. The structure ensures consistency across all dependency checks and allows the monitor to handle and report errors effectively.
const statuses = await monitor.getAllStatuses();
console.log(statuses);
// Example output:
// [
// {
// name: 'redis',
// description: 'Redis cache layer',
// impact: 'Responses may be slower due to missing cache.',
// healthy: true,
// health: {
// state: 'OK',
// code: 0,
// latency: 5,
// skipped: false,
// },
// lastChecked: '2025-04-13T12:00:00Z',
// },
// ];
const status = await monitor.getStatus('redis');
console.log(status);
// Example output:
// {
// name: 'redis',
// description: 'Redis cache layer',
// impact: 'Responses may be slower due to missing cache.',
// healthy: true,
// health: {
// state: 'OK',
// code: 0,
// latency: 5,
// skipped: false,
// },
// lastChecked: '2025-04-13T12:00:00Z',
// }
const metrics = await monitor.getPrometheusMetrics();
console.log(metrics);
/*
# HELP dependency_latency_ms Latency of dependency checks in milliseconds
# TYPE dependency_latency_ms gauge
dependency_latency_ms{dependency="redis"} 5
# HELP dependency_health Whether the dependency is currently healthy (0 = healthy, 1 = unhealthy)
# TYPE dependency_health gauge
dependency_health{dependency="redis", impact="Responses may be slower due to missing cache."} 0
*/
For detailed API documentation, refer to the docs.
Other tools might let you know that a dependency was broken when you find out the hard way. proactive-deps
helps you know in advance, by making it dead simple to wrap, register, and expose active health checks for the services your app relies on.
- Embedding in HTTP services to power
/health
or/metrics
endpoints - Scheduled checks that alert on failure via cron or background workers
- Internal monitoring dashboards for systems that depend on flaky external services
- [ ] Built-in Prometheus metrics endpoint handler
- [ ] Retry logic with exponential backoff
- [ ] Custom alert hooks (email, Slack, etc.)
- [ ] Custom cache stores.
MIT © 2025 Daniel Essig