import express, { type Express, type Request, type Response } from 'express';
export function buildApp(isShuttingDown: () => boolean): Express {
const app = express();
app.disable('x-powered-by');
app.get('/healthz', (_req: Request, res: Response) => {
res.status(200).json({ status: 'alive' });
});
app.get('/readyz', (_req: Request, res: Response) => {
if (isShuttingDown()) {
res.status(503).json({ status: 'draining' });
return;
}
res.status(200).json({ status: 'ready' });
});
app.get('/work', async (_req: Request, res: Response) => {
await new Promise((resolve) => setTimeout(resolve, 2000));
res.json({ done: true });
});
return app;
}
import type { Server } from 'http';
import type { Socket } from 'net';
export interface ShutdownManager {
isShuttingDown: () => boolean;
registerSocket: (socket: Socket) => void;
shutdown: (signal: string) => Promise<void>;
}
export function createShutdownManager(server: Server, graceMs = 15000): ShutdownManager {
const sockets = new Set<Socket>();
let shuttingDown = false;
const registerSocket = (socket: Socket): void => {
sockets.add(socket);
socket.once('close', () => sockets.delete(socket));
};
const shutdown = (signal: string): Promise<void> => {
if (shuttingDown) return Promise.resolve();
shuttingDown = true;
console.info(`received ${signal}, draining connections`);
return new Promise<void>((resolve) => {
const forceTimer = setTimeout(() => {
console.error('drain timed out, forcing exit');
for (const socket of sockets) socket.destroy();
process.exit(1);
}, graceMs);
forceTimer.unref();
server.close(() => {
clearTimeout(forceTimer);
console.info('all connections drained');
resolve();
});
// idle keep-alive sockets never trigger server.close's callback
for (const socket of sockets) {
if ((socket as Socket & { _httpMessage?: unknown })._httpMessage == null) {
socket.end();
}
}
});
};
return { isShuttingDown: () => shuttingDown, registerSocket, shutdown };
}
import { createServer } from 'http';
import { buildApp } from './app';
import { createShutdownManager } from './gracefulShutdown';
const PORT = Number(process.env.PORT ?? 3000);
let shutdownRef: ReturnType<typeof createShutdownManager>;
const app = buildApp(() => shutdownRef.isShuttingDown());
const server = createServer(app);
shutdownRef = createShutdownManager(server, Number(process.env.GRACE_MS ?? 15000));
server.on('connection', (socket) => shutdownRef.registerSocket(socket));
server.listen(PORT, () => {
console.info(`listening on :${PORT}`);
for (const signal of ['SIGTERM', 'SIGINT'] as const) {
process.on(signal, () => {
shutdownRef
.shutdown(signal)
.then(() => process.exit(0))
.catch((err) => {
console.error('shutdown failed', err);
process.exit(1);
});
});
}
});
A process that handles SIGTERM by calling process.exit() immediately will sever connections mid-response, drop in-flight work, and can corrupt clients that expected a clean reply. Graceful shutdown instead stops accepting new work, lets outstanding requests finish, and only then exits. This snippet shows the pattern as three collaborating files: a shutdown coordinator, its wiring into an HTTP server, and the Express app that exposes health probes.
In gracefulShutdown.ts, createShutdownManager closes over the http.Server and returns a shutdown function bound to signal handlers. Calling server.close() tells Node to stop accepting new connections and to invoke the callback once every open connection is idle. The catch is that a keep-alive HTTP connection sitting idle will never close on its own, so server.close() can hang forever. To avoid that, the manager tracks live sockets in a Set and, once draining begins, sets Connection: close on the next response of each socket via the beforeExit hook and destroys sockets that are still idle after a timeout. A shuttingDown flag makes the handler idempotent so a second SIGTERM does not start a second teardown.
The forceTimer is the safety valve: if requests refuse to finish within graceMs, the process exits anyway with a non-zero code rather than blocking a container restart indefinitely. unref() on the timer keeps it from holding the event loop open once real shutdown wins the race.
In server.ts, the raw Server is created explicitly so the manager can hook connection events and register SIGTERM/SIGINT handlers. Ordering matters here: signal handlers are attached only after listen succeeds.
In app.ts, two probes cooperate with the drain. Liveness (/healthz) always returns 200, but readiness (/readyz) flips to 503 the instant isShuttingDown() is true. In Kubernetes this is what actually makes the shutdown graceful: the failing readiness probe pulls the pod out of the Service endpoints so no new traffic is routed to it, giving the in-flight requests room to drain before the socket closes. The trade-off is added complexity and the need to tune graceMs against both the load balancer's deregistration delay and the longest legitimate request. Setting the grace window too low reintroduces the abrupt cutoff the pattern was meant to prevent.
Related snips
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
package dbutil
import (
"context"
"github.com/jackc/pgconn"
Retry Postgres serialization failures with bounded attempts
import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
timeout: 15000,
Axios API client with interceptors
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface FilterState {
search: string
category: string | null
Zustand for lightweight state management
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";
const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";
JWT access + refresh token rotation (conceptual)
package deps
import (
"net"
"net/http"
"time"
HTTP client tuned for production: timeouts, transport, and connection reuse
Share this code
Here's the card — post it anywhere.