// Express app with health checks and graceful shutdown
const express = require('express');
const { createServer } = require('http');
const app = express();
const server = createServer(app);
// Application state
let isReady = false;
let isShuttingDown = false;
const connections = new Set();
// Track connections for graceful shutdown
server.on('connection', (conn) => {
connections.add(conn);
conn.on('close', () => connections.delete(conn));
});
// Health check endpoints
app.get('/health/live', (req, res) => {
// Liveness: is the process alive and not deadlocked?
if (isShuttingDown) {
return res.status(503).json({
status: 'shutting_down',
timestamp: new Date().toISOString(),
});
}
res.json({
status: 'alive',
uptime: process.uptime(),
memoryUsage: process.memoryUsage(),
timestamp: new Date().toISOString(),
});
});
app.get('/health/ready', (req, res) => {
// Readiness: can this instance handle requests?
if (!isReady || isShuttingDown) {
return res.status(503).json({
status: 'not_ready',
isReady,
isShuttingDown,
});
}
res.json({
status: 'ready',
timestamp: new Date().toISOString(),
});
});
app.get('/health/startup', (req, res) => {
// Startup: has the app finished initializing?
if (!isReady) {
return res.status(503).json({ status: 'starting' });
}
res.json({ status: 'started' });
});
// Middleware: reject requests during shutdown
app.use((req, res, next) => {
if (isShuttingDown) {
res.setHeader('Connection', 'close');
return res.status(503).json({
error: 'Server is shutting down',
});
}
next();
});
// Application routes
app.get('/', (req, res) => {
res.json({ message: 'Hello, World!' });
});
// Startup sequence
async function startup() {
console.log('Starting application...');
try {
// Initialize database connection
// await db.connect();
console.log('Database connected');
// Initialize cache
// await redis.connect();
console.log('Cache connected');
// Warm up caches if needed
// await warmupCache();
isReady = true;
console.log('Application is ready');
} catch (error) {
console.error('Startup failed:', error);
process.exit(1);
}
}
// Graceful shutdown
async function shutdown(signal) {
console.log(`Received ${signal}, starting graceful shutdown...`);
isShuttingDown = true;
isReady = false;
// 1. Stop accepting new connections
server.close(() => {
console.log('HTTP server closed');
});
// 2. Wait for in-flight requests (with timeout)
const shutdownTimeout = setTimeout(() => {
console.error('Shutdown timeout, forcing exit');
process.exit(1);
}, 30000); // 30 second timeout
// 3. Close existing keep-alive connections
for (const conn of connections) {
conn.end();
}
// Give connections time to drain
await new Promise((resolve) => setTimeout(resolve, 5000));
// 4. Destroy remaining connections
for (const conn of connections) {
conn.destroy();
}
try {
// 5. Close external connections
// await db.disconnect();
console.log('Database disconnected');
// await redis.disconnect();
console.log('Cache disconnected');
clearTimeout(shutdownTimeout);
console.log('Graceful shutdown complete');
process.exit(0);
} catch (error) {
console.error('Error during shutdown:', error);
clearTimeout(shutdownTimeout);
process.exit(1);
}
}
// Signal handlers
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
// Unhandled errors
process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error);
shutdown('uncaughtException');
});
process.on('unhandledRejection', (reason) => {
console.error('Unhandled rejection:', reason);
});
// Start server
const PORT = process.env.PORT || 3000;
server.listen(PORT, async () => {
console.log(`Server listening on port ${PORT}`);
await startup();
});