In short
Judge a dependency on maintenance, not popularity: recent commits, how issues are handled, how many maintainers there are, and whether releases are predictable. Weigh the cost of adopting it against the cost of writing the part you actually need — most projects need a fraction of what a large library provides. Every dependency also brings its own dependencies, which is where supply-chain risk actually lives.
What you are actually taking on
Adding a dependency is not free, and the costs are deferred, which is why they get underweighted.
You take on its bugs, which you cannot fix directly. Its security vulnerabilities, which become yours to patch on someone else's schedule. Its breaking changes, which arrive when the maintainer decides. Its own dependencies, which you did not evaluate. And the risk of abandonment, which is the expensive one — an unmaintained package eventually blocks a runtime upgrade, and by then it is load-bearing.
None of that argues against dependencies. It argues for choosing deliberately rather than by search-result order.
Maintenance signals that predict
Popularity is a weak signal — plenty of widely used packages are unmaintained. Look instead at:
Recent commits. Not activity for its own sake, but evidence someone is still there.
How issues are handled. Open issues are normal. Hundreds with no maintainer replies is the signal. Read a few recent threads and see whether questions are answered.
Number of maintainers. A single-maintainer package is a single point of failure, however good that person is. People change jobs and lose interest.
Release cadence. Predictable releases and a real changelog indicate someone treating it as a project rather than a gist.
Response to security reports. A documented policy and a history of prompt patches is worth more than any download count.
Whether it handles its own upgrades. A package still supporting a runtime version from five years ago, with no plan for the current one, tells you where it is heading.
Size and surface
Check what it actually pulls in. A package with sixty transitive dependencies is sixty projects you did not evaluate. This is where supply-chain risk lives — compromises usually arrive through a dependency of a dependency, not through the thing you chose.
Check the bundle cost for anything shipped to browsers. A convenience helper adding 40KB to every page load is an expensive convenience.
Prefer narrow over broad. A library doing one thing has less surface to break and is easier to replace. Large frameworks that want to own your architecture are a much bigger commitment than their install command suggests.
The question people skip
How much of this do I actually need?
Most projects use a fraction of a large library. If you need to format a date, you may not need a full internationalisation framework. If you need to validate an email, you do not need a validation suite.
The honest comparison is not library versus writing the whole library. It is library versus writing the twenty lines you actually use. That reframing changes the answer surprisingly often.
But apply it carefully. Some things look small and are not: date and time handling, currency arithmetic, cryptography, character encoding, and anything security-related. Write your own crypto is the canonical mistake, and time zones are close behind — both look like a small function and are years of accumulated edge cases.
Licensing
Check it before adopting, not before shipping.
Permissive licences — MIT, Apache 2.0, BSD — are straightforward for commercial work. Copyleft licences impose obligations that may conflict with what you are building or with a client's requirements. Some packages change licence between versions, which is a reason to pin.
For client work, keep a record of what you used and under what terms. It is the kind of question that arrives during an acquisition, and reconstructing it afterwards is unpleasant.
Reducing the risk you have accepted
Pin versions and commit the lockfile, so builds are reproducible and an upstream change cannot alter your build silently.
Wrap third-party code behind your own interface where it touches a lot of your codebase. If you call a library directly from two hundred files, replacing it is a rewrite. Behind one adapter, it is an afternoon.
Automate vulnerability scanning in CI, so you learn about advisories from your pipeline rather than from a headline.
Update regularly in small steps. A dependency two years behind is a migration; one two months behind is a patch. The pain compounds, which is the argument for a routine.
Know your critical dependencies. If a handful of packages would be genuinely painful to lose, that is worth writing down — and worth checking their maintenance status occasionally.
When to write it yourself
When you need a small part of what the library offers. When the domain is genuinely simple and stable. When the library is unmaintained and the alternatives are worse. When the dependency would sit at the centre of your architecture and you would rather own that decision.
When not to: anything security-sensitive, anything involving time zones or currency, anything with a mature well-maintained standard, and anything where your version would need the same edge-case handling and would get less scrutiny.
The decision that ages best is usually the boring one: a well-maintained, narrow, widely used library for hard problems, and your own code for the easy ones.
If you are inheriting a project and want a read on which of its dependencies are risks, book a call.
Common questions
How do you evaluate an npm package before using it?
Look at maintenance rather than popularity: recent commits, whether issues get maintainer replies, how many maintainers there are, release cadence, and how security reports have been handled. Then check what it pulls in transitively and, for browser code, what it costs in bundle size.
Is it better to write code yourself or use a library?
Compare the library against writing only the part you actually need, not against reimplementing the whole library — most projects use a fraction of what a large package offers. But never apply that to cryptography, time zones, currency arithmetic, or character encoding, which look small and are years of accumulated edge cases.
Where does supply-chain risk actually come from?
Usually from transitive dependencies rather than the package you chose. A library with sixty dependencies of its own is sixty projects you never evaluated, and compromises typically arrive through one of those. Check the dependency tree, prefer narrow packages, and run vulnerability scanning in CI.
How do I reduce the risk of depending on a library?
Pin versions and commit the lockfile so builds are reproducible, wrap the library behind your own interface so replacing it is an afternoon rather than a rewrite, automate vulnerability scanning, and update in small regular steps — a dependency two months behind is a patch, two years behind is a migration.
What are the signs a package is abandoned?
No recent commits, open issues with no maintainer replies, a single maintainer who has gone quiet, no changelog or predictable releases, and continued support for old runtime versions with no plan for the current one. Abandonment is the expensive failure because it eventually blocks a runtime upgrade.
