Every new project starts the same way. A day or two in, I’m writing a deep-get helper because some API returned undefined three levels deep and crashed the build. Then a retry wrapper because a network call flakes in prod. Then an env loader because .env.production needs to merge on top of .env and none of the popular ones do that cleanly. None of this is hard code. It’s just code I’ve written, in some form, in nearly every codebase I’ve touched since 2019: Node backends, browser apps, CLI tools, Firebase functions.
At some point I stopped rewriting it and started copying it. Then I stopped copying it and put it in one repo: libx.js. It isn’t a framework. It doesn’t want to own your architecture, and it has no opinion about React vs. whatever. Think of it as a toolbelt: the drawer of load-bearing helpers that behave the same in Node and the browser.
The left-pad argument
The obvious counter is “npm has a package for every one of these.” It does. And on March 22, 2016, that fact broke the internet’s build. Azer Koçulu unpublished 273 of his modules after a trademark dispute with Kik, and one of them was left-pad, 11 lines of code that prepend characters to a string. It was downloaded over 15 million times and sat, transitively, under Babel, Webpack, React, and React Native. When it vanished, builds at Facebook, Netflix, Spotify and everyone else started throwing 404s. npm restored it within hours and changed its unpublish policy so it couldn’t happen again.
The lesson had nothing to do with npm being dangerous. I had handed control of eleven lines I could write in my sleep to a stranger and a registry policy. Multiply that by the 40-odd micro-dependencies a typical app drags in for one-liners, and you’re trusting a lot of people you’ll never meet to keep trivial code alive and un-sabotaged.
What’s actually in there
Two examples that get used constantly.
The env loader. Every “just use dotenv” answer breaks the moment you need .env.production to override .env without clobbering values a process manager already injected. Mine merges multiple files in order, expands ${VAR} references, and never overwrites something already sitting in process.env:
import { loadEnv } from 'libx.js/src/node/env';
loadEnv({ path: ['.env', '.env.local'] });
const env = loadEnv({ env: 'production' }); // merges .env + .env.production
Small, but I’ve hand-rolled worse versions of this in at least six repos before I got tired of it.
The other one I reach for constantly is the deep object/array helpers (safe nested access, deep clone, deep extend) without pulling in lodash for three functions out of the two hundred it ships. Same with the Callbacks module (pub/sub in ~50 lines), the Deferred wrappers for turning old callback APIs into promises, and QueueWorker for fanning work across a bounded number of concurrent handlers, the thing you need the moment you’re batch-processing anything and don’t want to blow through a rate limit.
None of these are novel ideas. That’s the point. They’re boring, and boring is exactly what you want from infrastructure code you didn’t write this week and don’t want to re-audit next week.
Why one repo beats 40 packages
Consistency. When the deep-get helper behaves identically in every project, I stop relearning edge cases: does it throw on a missing key or return undefined, does it treat 0 as falsy. I know, because I wrote the answer once and it hasn’t drifted.
One place to fix bugs. Find an edge case in the retry backoff, fix it in libx, and every project on a newer version gets it, instead of hunting six repos for six slightly different copy-pasted copies.
Code I actually understand. A dependency graph 40 micro-packages deep means that when something breaks at 2am, you’re reading source you’ve never seen, with someone else’s conventions and someone else’s bugs. My toolbelt I can read in my sleep, because I wrote it and I’ve read it a hundred times fixing exactly this kind of 2am problem.
The tradeoff, honestly
None of that is free. A personal standard library is a bus-factor of one. If I disappear, whoever inherits a libx-based project inherits a dependency only I really understand, with docs that lag the code and tests that cover what I remembered, not what a community has hardened over years. It’s a maintenance tax I pay alone: every breaking Node version, every TS strictness bump, is on me, across every helper.
I’ve made peace with it because the helpers are deliberately small and deliberately boring: little surface area to go wrong, and reading the source is usually faster than reading the docs for whatever package would replace it. If a helper ever needed real complexity (crypto, timezone-aware date math, anything with a wide attack surface), I’d pull a maintained package rather than own that risk. The toolbelt is for the stuff that’s tedious to write correctly but trivial to read once written. left-pad’s mistake wasn’t being small; it was being small and someone else’s.
It’s on GitHub at Livshitz/libx.js and on npm as libx.js. Take what’s useful, skip the rest; that’s exactly how I use it.
That’s the line I hold: for the boring, load-bearing stuff, I’d rather own eleven lines I understand than rent them from a stranger. Small and mine beats small and someone else’s, every time.
References
- The npm left-pad incident (Mar 22, 2016), Wikipedia: en.wikipedia.org/wiki/Npm_left-pad_incident
- libx.js source, GitHub: github.com/Livshitz/libx.js
- libx.js on npm: npmjs.com/package/libx.js </content> </invoke>