I opened an old package.json recently — from a practice project, not anything in production — and counted 340 lines in the node_modules listed by pnpm. One single direct dependency. Everything else, transitive. I didn't pick any of it. Somebody, at some point, needed to solve one specific problem and reached for a library, and that single decision dragged in 340 more packages sitting in that tree. I can't tell you how many of those 340 get audited or read line by line by anyone — probably close to none — but I can tell you that whoever maintains them eventually has to patch a CVE under pressure, because that's just how the ecosystem behaves.
That's the real problem: npm install takes three seconds and the decision it represents lasts years.
My thesis is simple and not original, but almost nobody applies it with discipline: adding a dependency also means taking on maintenance. It's not "using someone else's code for free." It's signing a tacit contract where you become responsible for that code continuing to work, staying secure, and staying compatible with whatever the project needs two years from now — even if the original author abandoned the repo eighteen months ago.
Evaluating npm Dependencies: Security and Maintenance Are Not the Same Thing
The official npm documentation on packages and modules explains well what a package is, how package.json gets resolved, how the dependency tree works, and semver. It's the right reference for understanding the mechanics.
What that documentation doesn't say — because it's not its job to say it — is how to evaluate whether a library is a good idea for your project. There's no "maintenance" section in the npm docs, no trust score. The mechanics of installing a package are trivial. The criteria for deciding whether it's worth installing is a completely different problem, and that's where most people improvise.
Security and maintenance aren't synonyms either, even though they get treated as if they were. A library can have zero known vulnerabilities today and be completely abandoned — nobody reviewing issues, no releases in two years, a maintainer who stopped responding. That doesn't show up in npm audit. It shows up when you need someone to fix something and there's nobody on the other side.
Where People Get It Wrong: The Common Recipe and Its Hidden Cost
The typical recipe when a problem shows up is: search npm, filter by weekly downloads, pick the one with the most GitHub stars, install, move on. It's reasonable as a first filter — downloads and stars correlate with adoption — but as the only criteria it leaves out everything that matters after day one.
Typical counterexample: a utility library with 2 million weekly downloads, but whose last commit is 3 years old and whose maintainer answered the last issue 14 months ago. High download counts often reflect past adoption, not present health — legacy projects keep pulling it because it's already in the lockfile of thousands of repos, not because anyone's choosing it again today.
The hidden cost shows up late: Node bumps a major version, something in the runtime changes, the library doesn't get updated because there's nobody to update it, and the team ends up with two ugly options — fork it and maintain it themselves, or migrate all the code depending on it under pressure, with no planning. Neither is free, and both were avoidable if someone had looked at the last commit date before installing.
Decision Matrix: What to Check Before Installing
This is what I go through, in order, before adding a new dependency to a TypeScript project:
1. Active maintenance
- Last commit: less than 6 months is a good sign, more than 18 months is a red flag.
- Open vs. closed issues: a ratio heavily skewed toward open suggests nobody's triaging.
- Number of maintainers: just one is a single point of failure — if that person burns out, the project dies.
2. Library surface area
- Does it solve a specific problem or is it a full framework? The smaller the surface, the less it can break and the less there is to audit.
- How many things does it export that you're not actually going to use? Unused surface area is risk without benefit.
3. TypeScript types
- Does it have its own types or does it depend on a separate
@types/package? Separate types drift out of sync with the actual implementation more often than we'd like to admit. - If you're working with
strict: trueand strict null checks enabled, as I discussed in the post on strict null checks in production, a library with badly done types is going to generate implicitanys that the compiler won't be able to catch.
4. Transitive dependencies
- Run
pnpm why <package>to see what it drags in. A "lightweight" library can bring in fifteen transitive dependencies you never chose. - The more transitives, the bigger the attack surface, and the more likely a CVE in a third-level package hits you without you knowing.
5. Exit strategy
- If this library gets abandoned in two years, how much does it cost to pull it out? If it's scattered across the entire codebase with no intermediate abstraction layer, the exit cost is high.
- If it's replaceable with your own code in a day, the dependency risk drops a lot.
A quick, reproducible check for point 4:
# See the transitive dependency tree of a package
pnpm why nombre-del-paquete
# Audit for known vulnerabilities
pnpm audit
# See the last publish date on the npm registry
npm view nombre-del-paquete time.modified
None of these commands give you a binary answer. They give you data to decide with judgment, which is different from having an automatic rule. That same principle — evaluating with concrete data instead of trusting a tool's general reputation — is the one I apply when reviewing integrations with external models, like I mentioned when evaluating the DeepSeek API in TypeScript: the question is never "is this tool good in general?", it's "is it good for this specific thing, with this level of maintenance?".
flowchart TD
A[Necesito resolver X] --> B{¿Lo resuelvo en menos de un dia con codigo propio?}
B -->|si| C[Escribo codigo propio]
B -->|no| D{¿Mantenimiento activo y tipos propios?}
D -->|no| E[Buscar alternativa o reconsiderar]
D -->|si| F{¿Transitivas razonables y salida clara?}
F -->|no| E
F -->|si| G[Instalar con abstraccion intermedia]
The Limits of This Evaluation
This matrix doesn't replace a real experiment. Everything above is upfront reading criteria — looking at metadata, history, types — it's not the same as running the library under load, measuring its behavior in the project's specific runtime, or seeing how the maintainer reacts to a real issue reported by your own team.
You also can't conclude, from "actively maintained today," that the library is going to keep being actively maintained next year. Commit history is evidence of past behavior, not a guarantee of future behavior. Projects with lone maintainers can change status overnight — new job, burnout, personal decision — and no checklist predicts that with certainty.
And watch out with npm audit specifically: it tells you which vulnerabilities are reported and cataloged today in npm's database. It doesn't tell you which vulnerabilities exist but haven't been reported yet, and it doesn't tell you whether that reported vulnerability actually applies to the usage pattern you have in your project. It's a signal, not a verdict.
If the project is critical — handles sensitive data, runs in a regulated environment, has an uptime SLA — this matrix is the starting point, not the end. That's where a deeper review is warranted: reading the entire library's source code, not just its README, and considering your own experiment with controlled load before deciding.
FAQ
How do I know if an npm library is well maintained?
Look at the date of the last commit and the last release, the number of active maintainers, and the ratio of closed versus open issues. None of these data points alone is decisive, but together they paint a fairly clear picture.
Is npm audit enough to evaluate a dependency's security?
No. npm audit checks for already-reported, cataloged vulnerabilities. It doesn't detect unknown vulnerabilities and doesn't evaluate whether your specific usage pattern actually exposes you to a listed CVE.
Is it worth using a library without its own TypeScript types?
Depends on the surface area. If it's something small with well-maintained @types, it's acceptable. If it's something central to the architecture, well-maintained proper types should be a requirement, not a luxury.
How do I check a package's transitive dependencies before installing it?
With pnpm why <package> after installing, or by checking the package's package.json on the npm registry before deciding. Looking at the tree size with tools like npm ls in dry-run mode also helps.
What do I do if the library I need is basically abandoned?
Weigh the cost of forking it yourself versus writing your own alternative scoped to the actual problem. If the surface you're using is small, a ten-line piece of your own code is often better than an external dependency with no owner.
Does pnpm change anything about this evaluation compared to npm?
The installation mechanics change — pnpm uses a shared content store and is stricter about access to undeclared dependencies — but the criteria for evaluating maintenance, types, and surface area is the same regardless of which package manager you use.
Where I Stand
I'm not going to tell you to stop using external libraries. That'd be a ridiculous stance coming from someone who works with Next.js, Docker, and half a dozen packages in every project. But every pnpm add deserves the same question you'd ask about a hire: who's on the other side, and what happens if they disappear?
Next time you're about to install something to solve a small problem, try first whether you can solve it with a ten-line function of your own. If you can't, run pnpm why after installing and look at what you brought in the door without actually deciding to. That habit, more than any checklist, is what separates a project that's maintainable three years from now from one that turns into archaeology.
Original source: npm package documentation
This article was originally published on juanchi.dev
Top comments (0)