stoppable review
Stoppable 1.1.0 decorates a Node HTTP or HTTPS server with `stop(callback)`. It counts active requests on each socket, ends idle keep-alive sockets when shutdown begins, lets active responses finish, and destroys what remains after an optional grace period. Version 1.1.0 added the callback's `gracefully` boolean and removed extra files from the published tarball. The original keep-alive problem is now smaller: Node's own `server.close()` has reaped idle HTTP connections since Node 19, and Node 18.2 added explicit idle and all-connection methods.
Stoppable 1.1.0 installed as one 1 MB package with zero audit findings in our sandbox, but Node 19 already folds its main idle-connection fix into `server.close()`. Retain it for tested old-Node support or its forced-deadline callback; new current-Node services should begin with core shutdown methods and explicit resource cleanup.
We installed it
| Install | ✓ · 0.4s | 1 package on disk · 1 MB |
| Import | ✓ | ESM import works · require() works · CommonJS package |
| Browser | n/a | could not be bundled for the browser (Node-only code, most likely) |
| Types | — | no TypeScript types found |
| Known vulns | 0 | 0 critical · 0 high · 0 moderate · 0 low (npm audit) |
Answers from our run
Does stoppable install cleanly?
Yes. In a fresh container with an empty cache, npm install stoppable finished in 0.4s, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.
Can stoppable run in a browser?
Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.
Does stoppable work with both ESM and CommonJS?
Yes. Both import 'stoppable' and require('stoppable') worked in Node 22 in our run. The package is published as CommonJS.
Does stoppable include TypeScript types?
No type declarations were found in our install, so TypeScript users need their own declarations.
stoppable or http-terminator: which should you use?
http-terminator: Choose it for a promise-based HTTP termination API with a newer package design. Stoppable 1.1.0 installed as one 1 MB package with zero audit findings in our sandbox, but Node 19 already folds its main idle-connection fix into server.close().
When should you not use stoppable?
The service runs only on Node 19 or newer and merely needs server.close() to reap idle keep-alives. Node core already does that.
Use it if
- A service supports Node older than 19 and needs idle keep-alive sockets closed while active HTTP responses drain.
- Existing callback-based shutdown code depends on the second `gracefully` result to distinguish a clean drain from a deadline.
- One HTTP or HTTPS server needs a finite force-close timer, and all other resources already have their own cleanup path.
- The team can test the decorator's socket bookkeeping against the exact Node version and proxy behavior in production.
- The service runs only on Node 19 or newer and merely needs `server.close()` to reap idle keep-alives. Node core already does that.
- WebSockets, HTTP/2 streams, or upgraded protocols must drain through the same helper. Node documents that even `closeAllConnections()` excludes upgraded sockets, and Stoppable tracks HTTP request events.
- A promise API and TypeScript declarations are required. Our 1.1.0 package inspection found no types and the library exposes a callback.
- Monkey-patching production server objects is unacceptable. The decorator adds `stop` and `_pendingSockets` to the supplied instance.
- You expect signal handling, health-check changes, database cleanup, and job shutdown in one package. Stoppable only owns sockets for one server.
- Active requests can hang without a deadline. The default grace is `Infinity`, so forgetting the second argument can prevent shutdown forever.
Setup reality
We installed stoppable 1.1.0 in 0.4 seconds. The result was one package using 1 MB on disk, with 24 KB unpacked and no direct or peer dependencies. npm audit found zero known vulnerabilities. It is CommonJS without an exports map; require() and ESM import both worked in Node 22. No TypeScript declarations were present. The package declares Node >=4 and npm >=6, while its README calls Node 6 the supported baseline.
Pass an existing http.Server or https.Server plus a grace value in milliseconds. The same instance comes back with stop. A missing grace means Infinity; 0 schedules immediate ending and destruction of tracked sockets. The callback signature is (error, gracefully), with the second value added in 1.1.0. Wrap it yourself if the rest of the shutdown path uses promises. Guard repeated signals because the README does not promise that repeated stop() calls are idempotent.
On shutdown, Stoppable calls server.close(), ends sockets with zero active requests, and waits for response finish before ending busy sockets. When the timer expires it marks the result forced, calls end(), then destroys sockets on the next turn. It watches connection or secureConnection plus HTTP request events. WebSocket upgrades, HTTP/2, raw TCP work, timers, database pools, queue consumers, and child processes remain open unless your signal handler closes them separately.
Our browser bundling attempt failed, which is expected for a Node server decorator. More importantly, modern Node changes the buy decision. Since Node 19, server.close() itself closes idle HTTP connections. Node 18.2 also provides closeIdleConnections() and closeAllConnections(); the latter is forceful and still excludes upgraded protocols. A small local function using those core calls is easier to type on current runtimes. Keep Stoppable where old-Node compatibility or its tested graceful flag still earns the dependency.
Patterns
Add a finite shutdown deadline decorate-server
const http = require('node:http')
const stoppable = require('stoppable')
const server = stoppable(http.createServer(handler), 10_000)
server.listen(3000)The second argument is milliseconds; omitting it leaves the 1.1.0 default at `Infinity`.
Wrap a server after construction decorate-existing-server
const server = http.createServer(app)
stoppable(server, 15_000)
server.listen(3000)The decorator mutates and returns the same server by adding `stop` and `_pendingSockets`.
Check whether requests drained observe-stop-result
server.stop((error, gracefully) => {
if (error) {
process.exitCode = 1
console.error(error)
return
}
console.log({ gracefully })
})Version 1.1.0 added the second boolean; it is false when the grace timer takes the forced path.
Preserve the graceful flag in a promise await-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)Stoppable 1.1.0 is callback-only, so promise-based lifecycle code needs this local wrapper.
Start shutdown once on SIGTERM handle-sigterm
let closing = false
process.on('SIGTERM', () => {
if (closing) return
closing = true
server.stop((error, gracefully) => {
if (error || !gracefully) process.exitCode = 1
})
})Guard duplicate signals because repeated `stop()` behavior is not documented; close pools and consumers in the same lifecycle separately.
Decorate the server returned by Express stop-express
const express = require('express')
const stoppable = require('stoppable')
const app = express()
app.get('/ready', (_, response) => response.send('ok'))
const server = stoppable(app.listen(3000), 10_000)Express returns a Node HTTP server, but this wrapper does not change readiness or close application dependencies.
Track an HTTPS server stop-https
const https = require('node:https')
const stoppable = require('stoppable')
const server = stoppable(https.createServer(tlsOptions, app), 10_000)
server.listen(443)For HTTPS, 1.1.0 tracks `secureConnection`; upgraded WebSocket and HTTP/2 lifecycles remain separate.
Use a zero-millisecond grace force-sockets
const server = stoppable(http.createServer(handler), 0)
server.stop((error, gracefully) => {
console.log({ error, gracefully })
})A 0 grace ends and then destroys all tracked sockets without waiting for in-flight HTTP work.
Fail a test when draining times out close-test-server
afterEach(async () => {
const { gracefully } = await stopServer(server)
if (!gracefully) throw new Error('server exceeded shutdown deadline')
})Use a finite grace in tests; the default `Infinity` can leave the test process waiting on one stuck response.
Replace it on current Node use-core-node-shutdown
await new Promise((resolve, reject) => {
server.close((error) => error ? reject(error) : resolve())
})
// Force only after your deadline:
server.closeAllConnections()Since Node 19, `server.close()` reaps idle HTTP connections. `closeAllConnections()` is forceful and does not close upgraded protocols.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| http-terminator | npm | Choose it for a promise-based HTTP termination API with a newer package design. |
| http-graceful-shutdown | npm | Choose it when signal handling and application cleanup callbacks should surround server shutdown. |
| close-with-grace | npm | Choose it to coordinate process signals and cleanup across several resources instead of decorating one server. |
More web backend guides
urllib3 · requests · ws · anyio · httpx · undici · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

