@betternest/healthcheck
v1.0.0
Published
Auto-discovery health checks with decorators for NestJS
Downloads
12
Maintainers
Readme
@betternest/healthcheck
Auto-discovery health checks with decorators for NestJS
@betternest/healthcheck provides zero-configuration health checks for your NestJS application. Simply decorate your service methods with @HealthCheck() and they're automatically exposed at /healthz.
Features
- ✅ Zero Configuration - Works out of the box
- ✅ Auto-Discovery - Automatically finds all health checks
- ✅ Type-Safe - Full TypeScript support
- ✅ REST API - Exposes
/healthzendpoints - ✅ Service Filtering - Check individual services
- ✅ Version Info - Includes app version in response
Installation
npm install @betternest/healthcheck
# or
yarn add @betternest/healthcheck
# or
pnpm add @betternest/healthcheckQuick Start
1. Import Module
import { Module } from '@nestjs/common';
import { HealthCheckModule } from '@betternest/healthcheck';
@Module({
imports: [HealthCheckModule],
})
export class AppModule {}2. Add Health Checks to Your Services
import { Injectable } from '@nestjs/common';
import { HealthCheck } from '@betternest/healthcheck';
@Injectable()
export class DatabaseService {
@HealthCheck()
protected async checkConnection() {
const isConnected = await this.database.ping();
if (!isConnected) {
throw new Error('Database not connected');
}
return {
connected: true,
latency: await this.database.getLatency(),
};
}
}
@Injectable()
export class CacheService {
@HealthCheck()
protected async checkCache() {
return {
connected: await this.redis.ping(),
memory: await this.redis.info('memory'),
};
}
}3. That's it!
Health checks are automatically exposed:
# Check all services
curl http://localhost:3000/healthz
# Check specific service
curl http://localhost:3000/healthz/DatabaseServiceAPI Endpoints
GET /healthz
Check all services.
Response (200 OK):
{
"hasError": false,
"states": {
"DatabaseService": "OK",
"CacheService": "OK"
},
"services": {
"DatabaseService": {
"connected": true,
"latency": 5
},
"CacheService": {
"connected": true,
"memory": "used_memory:1024"
}
},
"version": "1.0.0",
"timestamp": "2025-01-05T12:00:00.000Z"
}Response (500 Internal Server Error) when any check fails:
{
"hasError": true,
"states": {
"DatabaseService": "ERRORED",
"CacheService": "OK"
},
"services": {
"DatabaseService": "Database not connected",
"CacheService": {
"connected": true
}
},
"version": "1.0.0",
"timestamp": "2025-01-05T12:00:00.000Z"
}GET /healthz/:service
Check a specific service (e.g., /healthz/DatabaseService).
Returns the same format but filtered to that service only.
Usage
Basic Health Check
@Injectable()
export class MyService {
@HealthCheck()
protected async check() {
return {
status: 'OK',
uptime: process.uptime(),
};
}
}Health Check with Error
@Injectable()
export class ApiService {
@HealthCheck()
protected async checkApi() {
const response = await fetch('https://api.example.com/health');
if (!response.ok) {
throw new Error(`API returned ${response.status}`);
}
return {
status: 'OK',
latency: response.headers.get('x-response-time'),
};
}
}Health Check with Structured Error Data
Use HealthCheckError to return structured data even on failure:
import { HealthCheck, HealthCheckError } from '@betternest/healthcheck';
@Injectable()
export class DatabaseService {
@HealthCheck()
protected async checkDatabase() {
try {
await this.database.ping();
return {
connected: true,
pool: await this.database.getPoolStatus(),
};
} catch (error) {
// Return structured error data
throw new HealthCheckError({
connected: false,
error: error.message,
lastSuccessfulPing: this.lastPingTime,
});
}
}
}Response when using HealthCheckError:
{
"hasError": true,
"states": {
"DatabaseService": "ERRORED"
},
"services": {
"DatabaseService": {
"connected": false,
"error": "Connection timeout",
"lastSuccessfulPing": "2025-01-05T11:00:00.000Z"
}
}
}Best Practices
1. Use Protected Methods
Health checks should be protected (not public) to keep them internal:
@HealthCheck()
protected async check() { // ✅ Good
return { status: 'OK' };
}
@HealthCheck()
async check() { // ⚠️ Works but less encapsulated
return { status: 'OK' };
}2. One Health Check Per Service
Each service should have exactly ONE @HealthCheck() method:
// ✅ Good
@Injectable()
export class DatabaseService {
@HealthCheck()
protected async check() {
return {
connected: await this.checkConnection(),
migrations: await this.checkMigrations(),
};
}
}
// ❌ Bad - Multiple health checks in one service
@Injectable()
export class DatabaseService {
@HealthCheck()
protected async checkConnection() { }
@HealthCheck() // ERROR: Duplicate health check!
protected async checkMigrations() { }
}3. Keep Checks Fast
Health checks should be fast (<1 second):
// ✅ Good - Fast ping
@HealthCheck()
protected async check() {
return {
connected: await this.redis.ping(), // ~1-5ms
};
}
// ❌ Bad - Slow query
@HealthCheck()
protected async check() {
return {
totalUsers: await this.db.users.count(), // Could be slow!
};
}4. Return Useful Information
Include diagnostic information in your health checks:
@HealthCheck()
protected async check() {
return {
connected: true,
latency: 5, // ✅ Useful
connections: { // ✅ Useful
active: 10,
idle: 5,
total: 15,
},
lastError: null, // ✅ Useful
version: this.getVersion(), // ✅ Useful
};
}Advanced Usage
Kubernetes Liveness/Readiness Probes
# kubernetes/deployment.yaml
spec:
containers:
- name: app
livenessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 5
periodSeconds: 10Custom Health Check Service
Inject HealthCheckService to programmatically check health:
import { Injectable } from '@nestjs/common';
import { HealthCheckService } from '@betternest/healthcheck';
@Injectable()
export class MonitoringService {
constructor(
private readonly healthCheck: HealthCheckService,
) {}
async getApplicationHealth() {
const health = await this.healthCheck.getHealth();
if (health.hasError) {
await this.sendAlert('Application health degraded', health);
}
return health;
}
async checkSpecificService(serviceName: string) {
return this.healthCheck.getHealth(serviceName);
}
}Comparison with @nestjs/terminus
| Feature | @betternest/healthcheck | @nestjs/terminus | |---------|------------------------|------------------| | Setup | ✅ Zero config | ⚠️ Manual setup required | | Auto-discovery | ✅ Yes | ❌ No | | API | ✅ Simple decorator | ⚠️ Verbose HealthCheckService | | Type-safety | ✅ Full | ✅ Full | | Built-in indicators | ❌ DIY | ✅ Many built-in | | Custom indicators | ✅ Easy | ✅ Possible |
When to use @betternest/healthcheck:
- ✅ You want zero configuration
- ✅ You want auto-discovery
- ✅ You prefer decorators over imperative code
- ✅ You have custom health check logic
When to use @nestjs/terminus:
- ✅ You need built-in indicators (disk, memory, etc.)
- ✅ You want battle-tested library
- ✅ You need complex health check composition
Examples
See the examples directory for complete working examples.
Troubleshooting
Health check not discovered
- Ensure
HealthCheckModuleis imported - Verify method is decorated with
@HealthCheck() - Check that the service is properly registered as a provider
- Check logs for "Health check registered: ServiceName"
Multiple health checks error
Each service can only have ONE @HealthCheck() method. Combine multiple checks into one method:
@HealthCheck()
protected async check() {
return {
database: await this.checkDatabase(),
cache: await this.checkCache(),
api: await this.checkApi(),
};
}Contributing
Contributions are welcome! Please see CONTRIBUTING.md.
License
MIT © Mathieu
Related Packages
- @betternest/config - Type-safe configuration
- @betternest/workflows - MongoDB-based workflow orchestration
Part of the BetterNest ecosystem - Production-proven patterns for NestJS applications.
