--- title: Implement Graceful Shutdown impact: MEDIUM-HIGH impactDescription: Proper shutdown handling ensures zero-downtime deployments tags: devops, graceful-shutdown, lifecycle, kubernetes --- ## Implement Graceful Shutdown Handle SIGTERM and SIGINT signals to gracefully shutdown your NestJS application. Stop accepting new requests, wait for in-flight requests to complete, close database connections, and clean up resources. This prevents data loss and connection errors during deployments. **Incorrect (ignoring shutdown signals):** ```typescript // Ignore shutdown signals async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); // App crashes immediately on SIGTERM // In-flight requests fail // Database connections are abruptly closed } // Long-running tasks without cancellation @Injectable() export class ProcessingService { async processLargeFile(file: File): Promise { // No way to interrupt this during shutdown for (let i = 0; i < file.chunks.length; i++) { await this.processChunk(file.chunks[i]); // May run for minutes, blocking shutdown } } } ``` **Correct (enable shutdown hooks and handle cleanup):** ```typescript // Enable shutdown hooks in main.ts async function bootstrap() { const app = await NestFactory.create(AppModule); // Enable shutdown hooks app.enableShutdownHooks(); // Optional: Add timeout for forced shutdown const server = await app.listen(3000); server.setTimeout(30000); // 30 second timeout // Handle graceful shutdown const signals = ['SIGTERM', 'SIGINT']; signals.forEach((signal) => { process.on(signal, async () => { console.log(`Received ${signal}, starting graceful shutdown...`); // Stop accepting new connections server.close(async () => { console.log('HTTP server closed'); await app.close(); process.exit(0); }); // Force exit after timeout setTimeout(() => { console.error('Forced shutdown after timeout'); process.exit(1); }, 30000); }); }); } // Lifecycle hooks for cleanup @Injectable() export class DatabaseService implements OnApplicationShutdown { private readonly connections: Connection[] = []; async onApplicationShutdown(signal?: string): Promise { console.log(`Database service shutting down on ${signal}`); // Close all connections gracefully await Promise.all( this.connections.map((conn) => conn.close()), ); console.log('All database connections closed'); } } // Queue processor with graceful shutdown @Injectable() export class QueueService implements OnApplicationShutdown, OnModuleDestroy { private isShuttingDown = false; onModuleDestroy(): void { this.isShuttingDown = true; } async onApplicationShutdown(): Promise { // Wait for current jobs to complete await this.queue.close(); } async processJob(job: Job): Promise { if (this.isShuttingDown) { throw new Error('Service is shutting down'); } await this.doWork(job); } } // WebSocket gateway cleanup @WebSocketGateway() export class EventsGateway implements OnApplicationShutdown { @WebSocketServer() server: Server; async onApplicationShutdown(): Promise { // Notify all connected clients this.server.emit('shutdown', { message: 'Server is shutting down' }); // Close all connections this.server.disconnectSockets(); } } // Health check integration @Injectable() export class ShutdownService { private isShuttingDown = false; startShutdown(): void { this.isShuttingDown = true; } isShutdown(): boolean { return this.isShuttingDown; } } @Controller('health') export class HealthController { constructor(private shutdownService: ShutdownService) {} @Get('ready') @HealthCheck() readiness(): Promise { // Return 503 during shutdown - k8s stops sending traffic if (this.shutdownService.isShutdown()) { throw new ServiceUnavailableException('Shutting down'); } return this.health.check([ () => this.db.pingCheck('database'), ]); } } // Integrate with shutdown @Injectable() export class AppShutdownService implements OnApplicationShutdown { constructor(private shutdownService: ShutdownService) {} async onApplicationShutdown(): Promise { // Mark as unhealthy first this.shutdownService.startShutdown(); // Wait for k8s to update endpoints await this.sleep(5000); // Then proceed with cleanup } } // Request tracking for in-flight requests @Injectable() export class RequestTracker implements NestMiddleware, OnApplicationShutdown { private activeRequests = 0; private isShuttingDown = false; private shutdownPromise: Promise | null = null; private resolveShutdown: (() => void) | null = null; use(req: Request, res: Response, next: NextFunction): void { if (this.isShuttingDown) { res.status(503).send('Service Unavailable'); return; } this.activeRequests++; res.on('finish', () => { this.activeRequests--; if (this.isShuttingDown && this.activeRequests === 0 && this.resolveShutdown) { this.resolveShutdown(); } }); next(); } async onApplicationShutdown(): Promise { this.isShuttingDown = true; if (this.activeRequests > 0) { console.log(`Waiting for ${this.activeRequests} requests to complete`); this.shutdownPromise = new Promise((resolve) => { this.resolveShutdown = resolve; }); // Wait with timeout await Promise.race([ this.shutdownPromise, new Promise((resolve) => setTimeout(resolve, 30000)), ]); } console.log('All requests completed'); } } ``` Reference: [NestJS Lifecycle Events](https://docs.nestjs.com/fundamentals/lifecycle-events)