The Hill I'm Willing to Die On
Look, I've been building APIs with Node.js since before async/await was a thing. I've watched frameworks come and go, seen best practices evolve, and dealt with more breaking changes than I care to remember. And after all that time, I've reached a conclusion that some of you might find controversial: the Node.js ecosystem's approach to APIs is fundamentally broken.
This isn't about Express vs. Fastify vs. Hapi. It's deeper than that. It's about how we think about API development, how we manage dependencies, and how we've created a culture where "just npm install it" has become both our greatest strength and our most crippling weakness.
If you've ever spent hours debugging why your API suddenly stopped working after a minor dependency update, or if you've looked at your node_modules folder and wondered how 10MB of code became 200MB, you know exactly what I'm talking about. This isn't theoretical—it's affecting real projects, real teams, and real businesses every single day.
The Dependency Hell We've Built Ourselves
Let's start with the obvious: our dependency tree is out of control. I recently worked on a relatively simple REST API—nothing fancy, just CRUD operations with authentication. The package.json? 87 dependencies. Eighty-seven. For what should be a straightforward service.
Now, I'm not against using packages. The whole point of Node.js is its rich ecosystem. But here's the problem: we've stopped thinking critically about what we're pulling in. That "tiny" validation library you added? It might depend on three other packages, each with their own dependencies, and suddenly you're pulling in half of npm for what could be 20 lines of custom code.
And the security implications? Don't get me started. Every additional dependency is another potential vulnerability, another maintenance burden, another thing that can break in production. We've traded simplicity for convenience, and I'm not convinced it's a good trade.
Framework Fatigue is Real (And It's Getting Worse)
Remember when Express was the obvious choice? Those were simpler times. Now we've got Express, Fastify, NestJS, Koa, Hapi, Adonis, and about a dozen others I'm probably forgetting. Each comes with its own philosophy, its own middleware ecosystem, its own way of doing things.
Here's what happens in practice: a team picks Framework A, builds an API, then hires a new developer who only knows Framework B. Suddenly you're dealing with knowledge transfer issues, retraining, and the inevitable "why didn't we use Framework B instead?" discussions.
Worse, these frameworks often encourage specific patterns that lock you in. Try moving from Express to Fastify without significant rewrites. Or from NestJS to anything else. The abstraction layers we build on top of these frameworks make our code less portable, not more.
The Middleware Madness
Middleware should make our lives easier. And sometimes it does. But have you looked at a typical Express app's middleware stack lately? It's not uncommon to see 15-20 middleware functions, many of them doing tiny, specific things.
CORS middleware. Body parsing middleware. Compression middleware. Logging middleware. Authentication middleware. Rate limiting middleware. Validation middleware. Error handling middleware. And that's before we even get to our actual route handlers.
The execution order matters. The configuration matters. The compatibility between different middleware packages matters. And when something goes wrong? Good luck tracing through that chain to figure out which middleware is causing the issue.
We've created these Rube Goldberg machines of request processing, and we call it "best practices."
TypeScript: Savior or Another Layer of Complexity?
Don't get me wrong—I love TypeScript. I use it on every project. But in the API world, it's created its own set of problems.
First, there's the definition file problem. That popular validation library you're using? Hope the maintainer updated the TypeScript definitions. Or that DefinitelyTyped has current versions. Or that the community-provided types actually match what the library does.
Then there's the "everything must be typed" mentality. I've seen teams spend days creating elaborate type definitions for API responses, only to have them break when the backend changes. I've seen complex generic types that make simple endpoints look like PhD-level TypeScript exercises.
TypeScript should help us, not hinder us. But in the Node.js API world, it often feels like we're adding complexity for complexity's sake.
The Documentation Disaster
Here's a test: pick five random npm packages you use for API development. Go to their GitHub repos. How many have up-to-date documentation? How many have examples that actually work with the latest version? How many document their TypeScript usage properly?
If you're like most developers, you'll find maybe two out of five are well-documented. The rest? You're reading source code, digging through GitHub issues, or experimenting in the console to figure out how they work.
And this isn't just about small packages. Major frameworks have this problem too. Version differences aren't documented. Breaking changes slip into minor releases. Examples show deprecated APIs. It's a mess.
We're building critical business infrastructure on top of poorly documented tools, and we wonder why onboarding new developers takes months instead of weeks.
What Actually Works: A Pragmatic Approach
Okay, enough complaining. What should we actually do about this? After building dozens of APIs and watching what works (and what doesn't), here's my approach:
First, minimize dependencies aggressively. Before adding a package, ask: "Can I write this in 50 lines or less?" If the answer is yes, consider writing it yourself. Yes, you'll miss out on community updates. But you'll also miss out on breaking changes, security vulnerabilities in dependencies-of-dependencies, and the overhead of learning yet another API.
Second, pick boring technology. Express might not be the fastest or the shiniest, but it works, everyone knows it, and it's not going anywhere. The same goes for other foundational pieces. Boring is stable. Boring is maintainable. Boring keeps your business running.
Third, create clear boundaries. Your API framework should be a detail, not the core of your application. Structure your code so that business logic lives separately from HTTP concerns. This makes testing easier and framework migration possible (even if you never actually do it).
The Tools That Actually Help (And One That Might Save You)
Despite all the problems, some tools genuinely make API development better. Here are the ones I keep coming back to:
For validation, I've started using simple, focused libraries or even custom validation. Zod has been a game-changer for many teams—it's TypeScript-first, has zero dependencies, and does one thing well.
For testing, skip the complex setup. Use the built-in Node.js test runner or something minimal like Tap. You don't need a testing framework with more dependencies than your actual application.
And here's where I'm going to make a slightly controversial suggestion: sometimes, the best tool isn't another npm package. For data extraction or automation tasks that would normally require complex scraping setups, consider using a specialized platform. I've had good experiences with Apify for web scraping tasks that would otherwise require maintaining puppeteer/playwright setups, proxy rotation, and error handling. It's one less thing to worry about breaking.
But—and this is crucial—only reach for external services when the maintenance burden of doing it yourself is genuinely too high. Most API tasks don't need external services. They need clean, simple code.
Common Mistakes (And How to Avoid Them)
Let's talk about what goes wrong most often. These are patterns I've seen in code reviews, inherited projects, and yes, my own work:
Mistake #1: Over-abstracting too early. You don't need a repository pattern, a service layer, and a controller for your simple CRUD API. Start simple. Add complexity only when you need it.
Mistake #2: Chasing performance you don't need. Yes, Fastify is faster than Express. But is your API bottleneck really framework overhead? Probably not. Optimize your database queries first. Cache responses. Then maybe think about framework performance.
Mistake #3: Ignoring error handling until it's too late. Your API will fail. Plan for it. Have consistent error responses. Log appropriately. Handle unexpected errors gracefully. This matters more than which validation library you use.
Mistake #4: Not thinking about the long-term maintenance. That cool new framework with 200 GitHub stars? Who's going to maintain it in two years? What happens if the maintainer loses interest? Choose tools with staying power.
The Future: Where Do We Go From Here?
So where does this leave us in 2026? Honestly, I'm cautiously optimistic. I'm seeing more developers push back against dependency bloat. I'm seeing more emphasis on minimalism. I'm seeing tools like Node.js's built-in test runner gain traction.
The pendulum might finally be swinging back toward simplicity. And not a moment too soon.
We're also seeing better tooling for dependency management. npm audit actually works now. Tools can analyze your dependency tree and flag potential problems. We're getting better at managing the complexity we've created.
But tools can only do so much. The real change has to come from us—the developers making daily decisions about what to add to our projects.
Your Next API: A Better Way Forward
Here's my challenge to you: on your next API project, try something different. Start with the absolute minimum. Use Express (or Fastify if you must). Add dependencies only when absolutely necessary. Write code instead of configuring packages.
Keep your node_modules small. Your tests fast. Your documentation current. Your mental load manageable.
And when someone suggests adding yet another package to solve a problem that could be solved with a few lines of code? Push back. Ask why. Be the voice of simplicity on your team.
Because here's the truth: the best API isn't the one with the most dependencies or the fanciest framework. The best API is the one that works reliably, is easy to maintain, and doesn't give the next developer a headache.
That's the hill I'm willing to die on. And honestly? I think more of us should be standing here together.