Saltar al contenido
← Back to blog

~/blog · 15 May 2026 · 5 min read

Why my npm packages have zero dependencies

When I published lightq-node I started realising something: every dependency you add isn't really yours. It belongs to someone you don't know, who might disappear tomorrow.

Node.jsnpmTypeScript

When I published lightq-node, a Node.js job queue, the first question I got was: why not use bull or bee-queue? The short answer is I didn't want my users installing Redis without asking them. But while thinking about it I realised something deeper: I'm genuinely scared of dependency trees I don't control.

It's not paranoia. In 2018, event-stream, a package with millions of weekly downloads, was compromised because its author handed it off to a stranger. The new maintainer injected malicious code that stole cryptocurrency wallets. The package itself was harmless — the problem was one of its dependencies. That's a supply chain attack, and it can happen to anyone.

The cost nobody calculates

Imagine you publish a library with three small dependencies. Each one has its own. Before you know it, your users have 40 packages in node_modules they don't know exist. When one of those 40 ships a broken release, you get an open issue even though your code hasn't changed. This happens constantly.

The other cost is quieter: audit time. If you use a dependency with a CVE, you have to patch even if the vulnerability doesn't directly affect you. With zero dependencies, that problem simply doesn't exist.

Compiled TypeScript does it all

The part that surprised me most when writing these packages: almost everything I needed was already in Node.js core, or simple enough to implement in a couple of hours. The cron parser in cron-scheduler-ts took me an afternoon. A queue with MinHeap and exponential backoff, another afternoon. There's no magic — just basic algorithms with good names.

json
// This is what cron-scheduler-ts's package.json looks like
// Nothing in "dependencies" — only development tools
{
  "name": "cron-scheduler-ts",
  "dependencies": {},
  "devDependencies": {
    "typescript": "^5.8.3",
    "tsup": "^8.4.0",
    "vitest": "^3.1.1"
  }
}

When is it actually worth adding a dep?

I'm not fanatical about it. If I need a database driver or an HTTP client with well-tested retry and circuit breaker support, I use one. The question I ask myself is: how long would it take me to build this properly, versus the risk of carrying this dep forever? If the answer is 'more than two days', I take the dependency. If it's 'an afternoon', I implement it myself.

The practical result: packages that install in under a second, that will never break because of a change in a transitive dep, and whose complete source you can read in half an hour. To me that's worth the investment.