From 806d5b0824546ff9473d7c3a59306014f3866a63 Mon Sep 17 00:00:00 2001 From: kamaldeen Aliyu Date: Tue, 24 Mar 2026 10:39:44 +0100 Subject: [PATCH] Created and Implemented health check endpoints --- README.md | 94 ++++++++++++++++++++ src/app.ts | 27 +----- src/modules/health/health.controllers.ts | 107 +++++++++++++++++++++++ src/modules/health/health.routes.ts | 12 +++ src/modules/index.ts | 2 + 5 files changed, 216 insertions(+), 26 deletions(-) create mode 100644 src/modules/health/health.controllers.ts create mode 100644 src/modules/health/health.routes.ts diff --git a/README.md b/README.md index d60e72b..1db3bd7 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,100 @@ pnpm lint pnpm build ``` +## Health Check + +The server provides health check endpoints for local development and production monitoring: + +### Simple Health Check + +**Endpoint:** `GET /api/v1/health` + +Returns a minimal response suitable for load balancers and uptime monitors: + +```json +{ + "success": true, + "message": "OK", + "timestamp": "2025-01-15T10:30:00.000Z" +} +``` + +### Detailed Health Check + +**Endpoint:** `GET /api/v1/health/detailed` + +Returns comprehensive service status including database connectivity: + +```json +{ + "success": true, + "message": "Access Layer server is running", + "timestamp": "2025-01-15T10:30:00.000Z", + "version": "1.0.0", + "environment": "development", + "uptime": 12345.67, + "memory": { + "used": 45.23, + "total": 128.5 + }, + "system": { + "platform": "win32", + "nodeVersion": "v20.10.0" + }, + "database": { + "status": "connected", + "responseTime": 12 + }, + "services": [ + { + "name": "API Server", + "status": "healthy" + }, + { + "name": "Database", + "status": "healthy" + } + ] +} +``` + +**Response Codes:** + +- `200 OK` - All services healthy (or development mode) +- `503 Service Unavailable` - Database disconnected in production + +### Usage Examples + +**Local Development:** + +```bash +curl http://localhost:3000/api/v1/health/detailed +``` + +**Production Monitoring:** + +```bash +curl https://your-domain.com/api/v1/health +``` + +**Docker/Kubernetes Health Probes:** + +```yaml +livenessProbe: + httpGet: + path: /api/v1/health + port: 3000 + initialDelaySeconds: 10 + periodSeconds: 30 + +readinessProbe: + httpGet: + path: /api/v1/health/detailed + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 10 +``` + ## Open source workflow - Read [CONTRIBUTING.md](./CONTRIBUTING.md) before starting work. diff --git a/src/app.ts b/src/app.ts index 47000e1..edbf3e5 100644 --- a/src/app.ts +++ b/src/app.ts @@ -7,7 +7,6 @@ import { corsMiddleware } from './middlewares/cors.middleware'; import helmet from 'helmet'; import morgan from 'morgan'; import tspecOptions from './tspec.config'; -import { envConfig } from './config'; import { SendMail } from './utils/mail.utils'; import { appRateLimit } from './middlewares/rate.middleware'; @@ -22,31 +21,7 @@ app.use(morgan('combined')); app.use(express.urlencoded({ extended: true })); app.use(appRateLimit); -// Health check -app.get('/health', (_, res: Response) => { - const healthData = { - success: true, - message: 'Access Layer server is running', - timestamp: new Date().toISOString(), - version: '1.0.0', - environment: envConfig.MODE || 'development', - uptime: process.uptime(), - memory: { - used: - Math.round((process.memoryUsage().heapUsed / 1024 / 1024) * 100) / - 100, - total: - Math.round((process.memoryUsage().heapTotal / 1024 / 1024) * 100) / - 100, - }, - system: { - platform: process.platform, - nodeVersion: process.version, - }, - }; - - res.status(200).json(healthData); -}); +// Health check endpoints are now in /api/v1/health async function setupTspecDocs() { try { diff --git a/src/modules/health/health.controllers.ts b/src/modules/health/health.controllers.ts new file mode 100644 index 0000000..4c78ab5 --- /dev/null +++ b/src/modules/health/health.controllers.ts @@ -0,0 +1,107 @@ +import { Request, Response } from 'express'; +import { prisma } from '../../utils/prisma.utils'; +import { envConfig } from '../../config'; + +interface HealthStatus { + success: boolean; + message: string; + timestamp: string; + version: string; + environment: string; + uptime: number; + memory: { + used: number; + total: number; + }; + system: { + platform: string; + nodeVersion: string; + }; + database?: { + status: 'connected' | 'disconnected'; + responseTime?: number; + }; + services?: { + name: string; + status: 'healthy' | 'unhealthy'; + }[]; +} + +export const healthCheck = async (_: Request, res: Response): Promise => { + const startTime = Date.now(); + + try { + // Check database connectivity + let dbStatus: HealthStatus['database'] = { + status: 'disconnected', + }; + + try { + await prisma.$queryRaw`SELECT 1`; + const dbResponseTime = Date.now() - startTime; + dbStatus = { + status: 'connected', + responseTime: dbResponseTime, + }; + } catch (dbError) { + console.error('Database health check failed:', dbError); + dbStatus = { + status: 'disconnected', + }; + } + + const healthData: HealthStatus = { + success: true, + message: 'Access Layer server is running', + timestamp: new Date().toISOString(), + version: '1.0.0', + environment: envConfig.MODE || 'development', + uptime: process.uptime(), + memory: { + used: + Math.round((process.memoryUsage().heapUsed / 1024 / 1024) * 100) / + 100, + total: + Math.round((process.memoryUsage().heapTotal / 1024 / 1024) * 100) / + 100, + }, + system: { + platform: process.platform, + nodeVersion: process.version, + }, + database: dbStatus, + services: [ + { + name: 'API Server', + status: 'healthy', + }, + { + name: 'Database', + status: dbStatus.status === 'connected' ? 'healthy' : 'unhealthy', + }, + ], + }; + + // Return 503 if database is disconnected in production + const overallHealthy = + dbStatus.status === 'connected' || + envConfig.MODE !== 'production'; + + res.status(overallHealthy ? 200 : 503).json(healthData); + } catch (error) { + console.error('Health check failed:', error); + res.status(500).json({ + success: false, + message: 'Health check failed', + error: error instanceof Error ? error.message : 'Unknown error', + }); + } +}; + +export const simpleHealthCheck = (_: Request, res: Response): void => { + res.status(200).json({ + success: true, + message: 'OK', + timestamp: new Date().toISOString(), + }); +}; diff --git a/src/modules/health/health.routes.ts b/src/modules/health/health.routes.ts new file mode 100644 index 0000000..05b6b0d --- /dev/null +++ b/src/modules/health/health.routes.ts @@ -0,0 +1,12 @@ +import { Router } from 'express'; +import { healthCheck, simpleHealthCheck } from './health.controllers'; + +const router = Router(); + +// Detailed health check with database connectivity +router.get('/detailed', healthCheck); + +// Simple health check for load balancers +router.get('/', simpleHealthCheck); + +export default router; diff --git a/src/modules/index.ts b/src/modules/index.ts index 9c2e73d..3e85adc 100644 --- a/src/modules/index.ts +++ b/src/modules/index.ts @@ -1,8 +1,10 @@ import { Router } from 'express'; import authRouter from './auth/auth.routes'; +import healthRouter from './health/health.routes'; const router = Router(); +router.use('/health', healthRouter); router.use('/auth', authRouter); export default router;