53
lil-http-terminator, a tiny JS module to gracefully shutdown your HTTP server
Increase your
node_modules
by 11 KB to get tranquility that your node.js HTTP server is shutting down without data loss risk.Or how I shrunk a 2.2 MB module to 11 KB.
TL;DR:
npm i lil-http-terminator
I've been coding node.js microservices for almost a decade now. Graceful HTTP server shutdown was always a problem I didn't wanna deal with because it's hard to get right.
Recently I discovered that there is a perfect implementation of graceful shutdown. It's called http-terminator. Here is why I decided to use it (quoting the author):
- it does not monkey-patch Node.js API
- it immediately destroys all sockets without an attached HTTP request
- it allows graceful timeout to sockets with ongoing HTTP requests
- it properly handles HTTPS connections
- it informs connections using keep-alive that server is shutting down by setting a
connection: close
header - it does not terminate the Node.js process
Usage:
import { createHttpTerminator } from 'http-terminator';
const httpTerminator = createHttpTerminator({ server });
await httpTerminator.terminate();
Works with any node.js HTTP server out there (Express.js, Nest.js, Polka.js, Koa.js, Meteor.js, Sails.js, Hapi.js, etc).
Wow! Brilliant engineering! Well done author(s)!
But there is a catch.
Being a mere 4 KB codebase it adds 22 dependencies (2.2 MB, 464 files) to your
node_modules
.See for yourself:
$ npx howfat -r tree http-terminator
npx: installed 18 in 1.695s
http-terminator@3.0.0 (22 deps, 2.16mb, 464 files)
├── delay@5.0.0 (10.91kb, 5 files)
├─┬ roarr@4.2.5 (19 deps, 2.02mb, 398 files)
│ ├── boolean@3.1.2 (7.9kb, 10 files)
│ ├── detect-node@2.1.0 (2.7kb, 6 files)
│ ├─┬ fast-json-stringify@2.7.7 (9 deps, 1.79mb, 268 files)
│ │ ├─┬ ajv@6.12.6 (5 deps, 1.41mb, 181 files)
│ │ │ ├── fast-deep-equal@3.1.3 (12.66kb, 11 files)
│ │ │ ├── fast-json-stable-stringify@2.1.0 (16.56kb, 18 files)
│ │ │ ├── json-schema-traverse@0.4.1 (19.11kb, 9 files)
│ │ │ ╰─┬ uri-js@4.4.1 (1 dep, 490.54kb, 51 files)
│ │ │ ╰── punycode@2.1.1 (31.67kb, 5 files)
│ │ ├── deepmerge@4.2.2 (29.39kb, 9 files)
│ │ ├── rfdc@1.3.0 (23.48kb, 9 files)
│ │ ╰── string-similarity@4.0.4 (10.73kb, 5 files)
│ ├─┬ fast-printf@1.6.6 (1 dep, 34.32kb, 26 files)
│ │ ╰── boolean@3.1.2 (🔗, 7.9kb, 10 files)
│ ├─┬ globalthis@1.0.2 (2 deps, 114.41kb, 41 files)
│ │ ╰─┬ define-properties@1.1.3 (1 dep, 48.41kb, 21 files)
│ │ ╰── object-keys@1.1.1 (25.92kb, 11 files)
│ ├── is-circular@1.0.2 (5.89kb, 8 files)
│ ├── json-stringify-safe@5.0.1 (12.42kb, 9 files)
│ ╰── semver-compare@1.0.0 (3.96kb, 8 files)
╰── type-fest@0.20.2 (108kb, 42 files)
I got curious. What's that
roarr
package and if it can be removed from the package? The answer got me by surprise.The three top level dependencies can be easily removed.
The
type-fest
can be removed by rewriting the package from TS to JS. Hold saying your "boo" yet.It's a single function module. You don't need the code completion for just one function. So, rewriting to JS shouldn't be a downside for TypeScript proponents.
The
delay
module can be rewritten as a single-line function. Here it is:const delay = time => new Promise(r => setTimeout(r, time));
roarr
module, the largest of the tree, takes 2 MB of your hard drive. But it is used literally in the single line!!!if (terminating) {
log.warn('already terminating HTTP server');
return terminating;
}
The module will print that warning in case you decide to terminate your HTTP server twice. That's all. There is no more usage of the
roarr
logger within the whole http-terminator
module.I find it nearly impossible to accidentally call
.termiate()
twice. It's hard to imagine this ever happens. So I decided to put the log
variable to options
and assign it to console
by default.We get rid of 20 dependencies and simultaneously allow you, my fellow developers, to customise the termination with the logger of your choice (
winston
, bunyan
, pino
, morgan
, etc; or even the roarr
itself).const HttpTerminator = require("lil-http-terminator");
const httpTerminator = HttpTerminator({ server });
await httpTerminator.terminate();
Being as awesome as the origin, the
lil-
version is:I'm writing code for money for about 20 years. I'm using node.js and npm for almost a decade. I learnt to develop good and robust node.js services, scripts, serverless functions, apps. I discovered (re-invented) the best practices we better follow. I know how to make code maintainable years after it was written. The hardest bit was always the third party dependencies. I learnt the hard way that each additional sub-dependency can cost a company some thousands of dollars.
I forked and wrote
lil-http-terminator
in two hours. I foresee saving myself from 8 to 80 hours this way. You can save the same.53