stoppable
stoppable is a dependency-free CommonJS decorator for Node HTTP and HTTPS servers. It adds server.stop(callback), tracks how many requests are active on each socket, closes idle keep-alive sockets during shutdown, lets active responses finish, and can destroy whatever remains after a configured grace period. The callback reports both an error and whether shutdown stayed graceful. It was created to fill an old server.close behavior gap; on Node 19 and newer, core server.close already closes idle HTTP connections.
Do not add stoppable merely to make server.close reap keep-alives on current Node; core fixed that problem. Keep it only for an older service or a callback-based grace deadline you have tested, and plan an explicit path for WebSockets and every non-HTTP resource.
Use it if
- You maintain an older Node HTTP or HTTPS service where server.close leaves idle keep-alive sockets open
- You want one tiny CommonJS helper that waits for active requests and then forces remaining sockets after a fixed grace period
- Your shutdown code uses callbacks and needs a boolean telling it whether the grace deadline forced termination
- You run Node 19 or newer and only need idle HTTP connections reaped: current Node documentation says server.close already closes idle connections before returning
- You prefer current core primitives: Node added server.closeIdleConnections and server.closeAllConnections in 18.2, so a timer around those methods can replace this dependency
- You serve WebSockets, upgraded connections, or HTTP/2: the README promises HTTP and HTTPS servers, and the implementation only tracks connection or secureConnection plus request and response events
- You need an actively maintained, typed shutdown library: version 1.1.0 was published in November 2018, has no TypeScript declarations, and the repository last pushed in October 2023
- You dislike monkey-patching or per-request bookkeeping: the README calls out both design choices, while the source adds stop and _pendingSockets properties and updates a Map on connection, request, response, and close events
Setup reality
Install stoppable and require it from CommonJS; the package has no ESM export map and ships no TypeScript declarations. It accepts an existing http.Server or https.Server and returns that same object after adding stop, so you can decorate inline or after construction. The second constructor argument is the grace period in milliseconds. Its default is Infinity, which means a stuck request can keep shutdown waiting forever; production code should almost always choose a finite deadline. A value of 0 ends and then destroys all tracked sockets immediately, including active work. stop() schedules its work with setImmediate, calls server.close to stop new connections, sends FIN to idle sockets, and lets each active response finish before ending that socket. When the deadline fires it ends every tracked socket and destroys them on the next turn. The callback receives (error, gracefully), not the usual one-argument Node callback. Signal handling is still your job, including ignoring repeated SIGTERM or SIGINT while shutdown is already underway, stopping background consumers, closing database pools, and choosing the final process exit code. The package tracks only HTTP and HTTPS request lifecycles. WebSocket upgrades, HTTP/2 streams, raw TCP servers, open database handles, timers, and jobs need separate shutdown logic. Modern Node changes the decision: server.close has reaped idle HTTP connections since Node 19, and Node 18.2 added closeIdleConnections and closeAllConnections. If your supported runtime is current, a small local shutdown function using those core methods is easier to type, audit, and adapt. If you retain stoppable, test slow responses, keep-alive clients, aborted requests, forced deadlines, and repeat signals under the exact Node version you deploy. The project README's performance numbers are an old local benchmark, not a guarantee for your workload.
Patterns
Create a stoppable HTTP serverdecorate-http-server
const http = require('node:http')
const stoppable = require('stoppable')
const server = stoppable(http.createServer((req, res) => {
res.end('ok')
}), 10_000)
server.listen(3000)The second argument is the grace deadline in milliseconds. Without it, the default is Infinity and a stuck request can block forever.
Decorate an existing serverdecorate-existing-server
const server = http.createServer(app)
stoppable(server, 15_000)
server.listen(process.env.PORT || 3000)stoppable mutates and returns the same server instance by adding stop and _pendingSockets properties.
Observe graceful versus forced shutdownstop-with-callback
server.stop((error, gracefully) => {
if (error) {
console.error('shutdown error', error)
process.exitCode = 1
return
}
console.log(gracefully ? 'drained requests' : 'forced remaining sockets')
})The second callback argument is specific to stoppable. It becomes false when the grace timer runs its force-close path.
Wrap stop in a promiseawait-server-stop
function stopServer(server) {
return new Promise((resolve, reject) => {
server.stop((error, gracefully) => {
if (error) reject(error)
else resolve({gracefully})
})
})
}
const result = await stopServer(server)The package itself is callback-only. This wrapper preserves the graceful flag instead of dropping it.
Stop once on SIGTERMhandle-termination-signal
let shuttingDown = false
process.on('SIGTERM', () => {
if (shuttingDown) return
shuttingDown = true
server.stop((error, gracefully) => {
if (error || !gracefully) process.exitCode = 1
})
})Guard repeat signals because stoppable does not document stop() as idempotent. Also close database pools and background consumers separately.
Use stoppable under Expressstop-express-server
const express = require('express')
const stoppable = require('stoppable')
const app = express()
app.get('/health', (req, res) => res.send('ok'))
const server = stoppable(app.listen(3000), 10_000)Express returns the underlying Node HTTP server. This does not stop queues, scheduled work, or Express dependencies with their own handles.
Decorate an HTTPS serverstop-https-server
const https = require('node:https')
const stoppable = require('stoppable')
const server = stoppable(https.createServer(tlsOptions, app), 10_000)
server.listen(443)The implementation listens to secureConnection for https.Server. This does not add HTTP/2 stream or WebSocket upgrade tracking.
Force sockets closed immediatelyforce-immediate-stop
const server = stoppable(http.createServer(handler), 0)
server.stop((error, gracefully) => {
console.log({error, gracefully})
})A grace value of 0 ends and destroys tracked sockets without waiting for active requests. Use it only when abrupt termination is intended.
Close a test server after each casetest-server-cleanup
afterEach(async () => {
const {gracefully} = await stopServer(server)
if (!gracefully) throw new Error('test server exceeded shutdown grace')
})Use a finite grace in tests so one hung handler fails instead of leaving the test process open indefinitely.
Decorate and listen in one expressionchain-server-creation
const server = stoppable(
http.createServer(handler),
5_000
)
server.listen(3000, () => console.log('ready'))The decorator returns the original server, so normal server methods remain available after wrapping.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| http-terminator | npm | You want a purpose-built HTTP termination API with promise-based shutdown and more recent package design |
| http-graceful-shutdown | npm | You want signal hooks plus ordered cleanup callbacks around an HTTP server |
| @godaddy/terminus | npm | You need Kubernetes-oriented health checks, lifecycle signals, and server termination in one package |
| close-with-grace | npm | You need process-signal orchestration for several resources rather than socket tracking attached to one server |