When to use an npm package?

Use date-fns. Use better-auth. Write your own text utils. The real skill isn't avoiding dependencies or collecting them - it's knowing which problems are worth depending on someone else for, and what a bloated package list is actually telling you.

Sefa
Sefa Monday, July 14th, 2025

There are two ways developers get the npm question wrong. The first is reaching for a package every time they hit a problem, until the project has four hundred dependencies and nobody knows what half of them do. The second is the opposite kind of pride, where someone reimplements date handling or authentication from scratch because "it's just a few functions," and then maintains that mistake for years.

The skill is knowing which problems are worth depending on someone else for, and which ones you should just write. After enough projects I have a fairly settled set of rules, so here they are with the actual packages I reach for.

Install a package when the problem is genuinely hard

Dates are the clearest example. Date formatting, time zones, and locale handling are deceptively deep, and getting them right yourself is a waste of your time and a source of bugs. Use date-fns with the locale you need and move on. There is no version of "I'll write my own date utilities" that ends well on a real project.

Authentication is the same, only with higher stakes. You should almost never write your own auth. The failure modes are security failures, and the surface area is enormous: sessions, tokens, password resets, social providers, expiry. I reach for better-auth here. The cost of getting auth subtly wrong is far higher than the cost of a dependency.

Maps are another. Rendering, tiles, markers, interaction, performance: this is not something you hand-roll. A package is always the right call.

The pattern across all three is the same. When the problem is hard, well-understood, and dangerous to get wrong, the package is not laziness. It is the responsible choice.

Write it yourself when the problem is small

Text formatting is where I go the other way. Need to truncate a string, capitalise a label, format a name? Write a small util. Pulling in a dependency to do something you can express in five lines is how projects accumulate weight that nobody can justify later. Small, specific, easy-to-test logic belongs in your own codebase where you can read it.

The test I apply is whether I could write and fully test the thing in an afternoon, and whether getting it slightly wrong would be harmless. If yes to both, I write it. A bad truncation function is a five-minute fix. A bad auth implementation is an incident.

Some packages are simply mandatory

Type definitions for the third-party tools you already use are not optional. If you are working with Next.js and Sanity, their types are a must. This is not really "adding a dependency" in the risky sense; it is making the tools you have chosen actually usable in a typed codebase. Skipping them to keep the dependency count down is false economy.

I also make exceptions for things that are technically doable yourself but where a good library is clearly better. Animation is one. You can write animations with Tailwind, but framer-motion gives you control and a developer experience that Tailwind's utility classes cannot match once the motion gets beyond a fade. For anything beyond trivial transitions, I would rather have the library.

When the package list itself is the problem

Here is the part most "should you use this package" articles miss. The real warning sign is not any single dependency. It is the total count.

If a project is accumulating a lot of packages, that is usually a signal about the project's structure, not just its dependencies. It often means you are building several things inside one codebase that should be separated. At that point the better move is not to keep pruning packages one by one. It is to move to a monorepo, define your own internal packages, and implement real separation of concerns. The dependencies that felt like clutter in a single app become clean, owned boundaries once each part of the system has its own package.

So my full answer to "when should I use an npm package" is layered. Use one when the problem is hard, well-solved, and dangerous to get wrong. Write your own when the problem is small and safe. Always take the type definitions for the tools you have committed to. And when the package list starts to feel out of control, treat that as architecture feedback, not a dependency problem, and restructure into a monorepo where you own the boundaries.

The goal is never the smallest possible dependency count. The goal is a codebase where every dependency earns its place and you can explain why each one is there. If you cannot explain why a package is in your package.json, that is the one to look at first.

When to Use an npm Package vs Write Your Own | Article | Sefa's Portfolio