Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
27 changes: 1 addition & 26 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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 {
Expand Down
107 changes: 107 additions & 0 deletions src/modules/health/health.controllers.ts
Original file line number Diff line number Diff line change
@@ -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<void> => {
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(),
});
};
12 changes: 12 additions & 0 deletions src/modules/health/health.routes.ts
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 2 additions & 0 deletions src/modules/index.ts
Original file line number Diff line number Diff line change
@@ -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;
Loading