@unidev-hub/health
v1.1.24
Published
Standardized health checks for TypeScript services with readiness/liveness probes, dependency monitoring, and dashboard visualization
Readme
@unidev-hub/health
A standardized health check system for TypeScript/Node.js services with support for readiness/liveness probes, dependency health reporting, customizable health indicators, and metrics reporting.
Features
- TypeScript-first design: Full type safety with comprehensive TypeScript definitions
- Standardized health check endpoints: Easily implement Kubernetes-compatible liveness and readiness probes
- Dependency health monitoring: Track the health of HTTP endpoints, databases, and other service dependencies
- Customizable health indicators: Create custom health checks to monitor any aspect of your service
- Health status aggregation: Automatically determine overall service health based on individual components
- Performance metrics: Track response times and resource usage
- Cached responses: Configurable caching to reduce the performance impact of frequent health checks
- Express integration: Simple middleware for adding health endpoints to Express applications
Installation
npm install @unidev-hub/healthQuick Start
import express from 'express';
import {
HealthService,
MemoryHealthIndicator,
HttpHealthIndicator,
createHealthEndpoint
} from '@unidev-hub/health';
// Create health service
const healthService = new HealthService();
// Add memory indicator to monitor heap usage
healthService.addIndicator(new MemoryHealthIndicator({
thresholdPercent: 85 // Alert when heap usage exceeds 85%
}));
// Add HTTP dependency indicator to check external API
healthService.addIndicator(new HttpHealthIndicator({
name: 'payment-api',
url: 'https://api.payment-provider.com/status',
timeout: 3000,
expectedStatus: 200
}));
// Create Express app and add health endpoints
const app = express();
app.use(createHealthEndpoint(healthService));
// Start server
app.listen(3000, () => {
console.log('Server running with health checks at:');
console.log('- /health (Basic health check)');
console.log('- /health/liveness (Liveness probe)');
console.log('- /health/readiness (Readiness probe)');
console.log('- /health/details (Detailed health report)');
});Health Endpoints
The module provides four standard health check endpoints:
/health: Basic health status with minimal details/health/liveness: Kubernetes liveness probe (is the service running?)/health/readiness: Kubernetes readiness probe (is the service ready to receive traffic?)/health/details: Detailed health report with all component statuses
Built-in Health Indicators
Memory Health Indicator
Monitors memory usage of the Node.js process.
import { MemoryHealthIndicator } from '@unidev-hub/health';
const memoryIndicator = new MemoryHealthIndicator({
thresholdPercent: 90 // Optional: threshold for DEGRADED status (default: 90%)
});
healthService.addIndicator(memoryIndicator);Disk Health Indicator
Monitors available disk space.
import { DiskHealthIndicator } from '@unidev-hub/health';
const diskIndicator = new DiskHealthIndicator({
path: '/', // Optional: path to monitor (default: '/')
thresholdPercent: 90 // Optional: threshold for DEGRADED status (default: 90%)
});
healthService.addIndicator(diskIndicator);HTTP Health Indicator
Monitors the health of HTTP dependencies.
import { HttpHealthIndicator } from '@unidev-hub/health';
const apiIndicator = new HttpHealthIndicator({
name: 'auth-service', // Optional: name for the indicator
url: 'https://auth.example.com/health',
method: 'GET', // Optional: HTTP method (default: 'GET')
timeout: 5000, // Optional: timeout in ms (default: 5000)
expectedStatus: 200, // Optional: expected HTTP status (default: 200)
headers: { 'Authorization': 'Bearer token' } // Optional: custom headers
});
healthService.addIndicator(apiIndicator);Database Health Indicator
Monitors database connectivity.
import { DatabaseHealthIndicator, DatabaseConnection } from '@unidev-hub/health';
// Your database connection object implementing the DatabaseConnection interface
const db: DatabaseConnection = {
async query(sql: string) {
// Your database query implementation
return someDbClient.query(sql);
},
config: {
database: 'users',
host: 'localhost'
}
};
const dbIndicator = new DatabaseHealthIndicator({
name: 'primary-db', // Optional: name for the indicator
connection: db, // Required: database connection object
validationQuery: 'SELECT 1', // Optional: query to validate connection (default: 'SELECT 1')
timeout: 3000 // Optional: timeout in ms (default: 5000)
});
healthService.addIndicator(dbIndicator);Custom Health Indicators
You can create custom health indicators to monitor any aspect of your application:
Using the Factory Function
import { createHealthIndicator, Status, HealthResult } from '@unidev-hub/health';
// Example Redis client
interface RedisClient {
ping(): Promise<string>;
options: {
host: string;
port: number;
};
}
const redisClient: RedisClient = {
/* your Redis client */
};
const RedisHealthIndicator = createHealthIndicator('redis', async (): Promise<HealthResult> => {
try {
const start = Date.now();
await redisClient.ping();
const responseTime = Date.now() - start;
return {
status: Status.UP,
details: {
responseTime: `${responseTime}ms`,
host: redisClient.options.host
}
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
status: Status.DOWN,
details: { error: errorMessage }
};
}
});
// Create an instance of the indicator
healthService.addIndicator(new RedisHealthIndicator());Extending the Base Class
import { HealthIndicator, Status, HealthResult } from '@unidev-hub/health';
// Example message queue client
interface QueueClient {
getStats(): Promise<{
messageCount: number;
consumerCount: number;
oldestMessage: string;
}>;
}
class QueueHealthIndicator extends HealthIndicator {
private queueClient: QueueClient;
constructor(queueClient: QueueClient) {
super('message-queue');
this.queueClient = queueClient;
}
async check(): Promise<HealthResult> {
try {
const stats = await this.queueClient.getStats();
const status = stats.consumerCount > 0 ? Status.UP : Status.DEGRADED;
return {
status,
details: {
messageCount: stats.messageCount,
consumerCount: stats.consumerCount,
oldestMessage: stats.oldestMessage
}
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
status: Status.DOWN,
details: { error: errorMessage }
};
}
}
}
const mqClient: QueueClient = {
/* your message queue client */
};
healthService.addIndicator(new QueueHealthIndicator(mqClient));Health Service Configuration
Caching Results
For performance reasons, health check results are cached by default for 10 seconds:
// Change cache time to 30 seconds
healthService.setCacheTime(30000);
// Force a fresh health check
const healthStatus = await healthService.check(false);Checking Health Programmatically
You can check the health of your service programmatically:
// Get the current health status
const healthStatus = await healthService.check();
// Log overall status
console.log(`Service status: ${healthStatus.status}`);
// Check a specific component
if (healthStatus.components.database.status === Status.DOWN) {
console.error('Database is down:', healthStatus.components.database.details);
}Express Middleware Configuration
You can customize the health endpoint paths:
import { createHealthEndpoint } from '@unidev-hub/health';
const healthMiddleware = createHealthEndpoint(healthService, {
path: '/status', // Basic health endpoint
livenessPath: '/status/alive', // Liveness probe
readinessPath: '/status/ready', // Readiness probe
detailedPath: '/status/details' // Detailed health report
});
app.use(healthMiddleware);Health Status Response Format
Basic Health Endpoint Response
{
"status": "UP",
"timestamp": "2025-03-14T15:30:45.123Z",
"metrics": {
"responseTime": "12ms",
"componentCount": 3
}
}Detailed Health Endpoint Response
{
"status": "DEGRADED",
"timestamp": "2025-03-14T15:30:45.123Z",
"components": {
"memory": {
"status": "UP",
"details": {
"total": "512 MB",
"used": "128 MB",
"free": "384 MB",
"percentage": "25.00%"
}
},
"payment-api": {
"status": "DEGRADED",
"details": {
"url": "https://api.payment-provider.com/status",
"statusCode": 200,
"expectedStatus": 200,
"responseTime": "2500ms"
}
},
"database": {
"status": "UP",
"details": {
"database": "users",
"validationQuery": "SELECT 1",
"responseTime": "5ms"
}
}
},
"metrics": {
"responseTime": "2510ms",
"componentCount": 3
}
}Status Codes
Health checks use four possible status values:
- UP: Component is fully operational
- DOWN: Component is not functioning and requires attention
- DEGRADED: Component is functioning but with reduced performance or capabilities
- UNKNOWN: Component status could not be determined
Integration with Kubernetes
This health check system is designed to work seamlessly with Kubernetes:
# Example Kubernetes deployment with probes
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-service
spec:
template:
spec:
containers:
- name: my-service
image: my-service:1.0.0
ports:
- containerPort: 3000
livenessProbe:
httpGet:
path: /health/liveness
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/readiness
port: 3000
initialDelaySeconds: 5
periodSeconds: 5Best Practices
Use TypeScript: Take advantage of the TypeScript definitions for type safety and better developer experience
Meaningful Component Names: Use descriptive names for health indicators to easily identify issues
Correct Status Usage:
UP: Component is fully operationalDOWN: Component is not functioningDEGRADED: Component is working but with reduced performanceUNKNOWN: Status could not be determined
Timeouts: Always set reasonable timeouts for dependency checks to avoid hanging health checks
Error Handling: Always use try/catch blocks in custom health indicators to avoid crashing the health check system
Security: Consider protecting your detailed health endpoint (
/health/details) with authentication in production
Advanced Patterns
Critical vs. Non-Critical Components
You can implement a system to differentiate critical components:
import { HealthService, Status } from '@unidev-hub/health';
// Mark components as critical by extending HealthIndicator
class CriticalHealthIndicator extends HealthIndicator {
readonly critical: boolean = true;
// ...rest of implementation
}
// Or add a property to existing indicators
interface CriticalIndicator {
critical?: boolean;
}
// Mark components as critical
function markAsCritical(healthService: HealthService, componentName: string): void {
const indicators = healthService.getIndicators();
const indicator = indicators.get(componentName) as HealthIndicator & CriticalIndicator;
if (indicator) {
indicator.critical = true;
}
}
// Use this information in your health check processing
// ...Custom Express Middleware with Critical Components
function createCustomHealthEndpoint(healthService: HealthService, options = {}) {
const standardMiddleware = createHealthEndpoint(healthService, options);
return function(req: Request, res: Response, next: NextFunction) {
if (req.path === options.readinessPath || req.path === '/health/readiness') {
// Check if any critical component is down
healthService.check().then(result => {
const indicators = healthService.getIndicators();
let criticalDown = false;
for (const [name, indicator] of indicators.entries()) {
if ((indicator as any).critical && result.components[name].status === Status.DOWN) {
criticalDown = true;
break;
}
}
if (criticalDown) {
res.status(503).json({
status: Status.DOWN,
message: 'Critical component is down'
});
} else {
// Continue with standard processing
standardMiddleware(req, res, next);
}
}).catch(next);
} else {
standardMiddleware(req, res, next);
}
};
}Running Tests
# Install dependencies
npm install
# Run tests
npm test
# Run linting
npm run lintExample Applications
The package includes example applications that demonstrate how to use the health check system:
# Run the simple Express example
npm run example:simple
# Run the custom indicator example
npm run example:customFAQ
How do I test my custom health indicator?
You can test your custom health indicator using Jest:
import { Status } from '@unidev-hub/health';
import MyCustomHealthIndicator from './my-custom-health-indicator';
// Mock your dependencies
jest.mock('./my-dependency', () => ({
checkStatus: jest.fn()
}));
describe('MyCustomHealthIndicator', () => {
let indicator: MyCustomHealthIndicator;
beforeEach(() => {
indicator = new MyCustomHealthIndicator();
});
test('should return UP status when service is healthy', async () => {
// Arrange
const mockDependency = require('./my-dependency');
mockDependency.checkStatus.mockResolvedValue({ healthy: true });
// Act
const result = await indicator.check();
// Assert
expect(result.status).toBe(Status.UP);
expect(result.details).toHaveProperty('responseTime');
});
test('should return DOWN status when service is unhealthy', async () => {
// Arrange
const mockDependency = require('./my-dependency');
mockDependency.checkStatus.mockRejectedValue(new Error('Connection failed'));
// Act
const result = await indicator.check();
// Assert
expect(result.status).toBe(Status.DOWN);
expect(result.details).toHaveProperty('error', 'Connection failed');
});
});How do I register health checks with other frameworks?
While Express middleware is provided out of the box, you can use the HealthService directly with any web framework:
// Example with Fastify
import fastify from 'fastify';
import { HealthService } from '@unidev-hub/health';
const app = fastify();
const healthService = new HealthService();
// Add your indicators
// ...
// Register routes
app.get('/health', async (request, reply) => {
const health = await healthService.check();
const statusCode = health.status === 'UP' ? 200 :
health.status === 'DEGRADED' ? 200 : 503;
return reply.status(statusCode).send({
status: health.status,
timestamp: health.timestamp,
metrics: health.metrics
});
});
app.get('/health/details', async (request, reply) => {
const health = await healthService.check();
const statusCode = health.status === 'UP' ? 200 :
health.status === 'DEGRADED' ? 200 : 503;
return reply.status(statusCode).send(health);
});
// Start server
app.listen({ port: 3000 });Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
