How TJ Holowaychuk’s Express and Koa shaped the Node.js ecosystem: minimalist middleware, composable APIs, and lessons for building maintainable backends.

TJ Holowaychuk is one of the most influential early builders in the Node.js community. He created Express, helped popularize patterns that shaped how Node web apps are written, and later introduced Koa as a rethink of what a web framework core should be.
Even if you’ve never used his code directly, you’ve almost certainly felt its impact: many Node.js frameworks, tutorials, and production backends inherited ideas that Express and Koa made mainstream.
Express and Koa are “minimal” in a very specific way: they don’t try to make every decision for you. Instead of shipping a full set of opinions—authentication, database rules, background jobs, admin panels—they focus on a small, reliable core for handling HTTP requests and responses.
Think of it like a well-built toolbox rather than a pre-furnished house. The framework gives you a clear place to plug in features (routing, validation, cookies, sessions), but you decide which pieces you need and how they fit together.
This post is a practical tour of what made Express and Koa enduring:
By the end, you should be able to look at a project’s needs (team size, complexity, long-term maintenance) and choose an approach with fewer surprises.
Node.js changed what “backend development” felt like for a lot of teams. Instead of switching between JavaScript in the browser and another language on the server, you could build end-to-end in one language, share mental models, and move quickly from idea to working endpoint.
That didn’t just make development faster—it made it more approachable. A frontend-leaning developer could read server code without learning an entirely new ecosystem first, and small teams could ship prototypes and internal tools with fewer handoffs.
Node’s event-driven model and package ecosystem (npm) encouraged fast iteration. You could start with a tiny server, add one dependency at a time, and grow features as real needs appeared.
But early Node also exposed a gap: the built-in HTTP module was powerful, yet very low-level. Handling routing, parsing request bodies, cookies, sessions, and error responses meant rewriting the same plumbing in every project.
Developers didn’t want a heavy “everything included” framework. They wanted a simple way to:
The ideal tool was small enough to learn quickly, but structured enough to stop every app from becoming a one-off tangle of handlers.
Express arrived at the right moment with a small core and clear conventions. It gave teams a straightforward place to put routes and middleware, without forcing a complex architecture up front.
Just as importantly, Express didn’t try to solve everything. By staying minimal, it left room for the community to build the “optional parts” as add-ons—authentication strategies, validation helpers, logging, templating, and later, API-focused tooling.
That design choice helped Express become a common starting point for countless Node backends, from weekend projects to production services.
Express is a lightweight web framework for Node.js. Think of it as a thin layer that helps you accept HTTP requests (like “GET /products”) and send back responses (like JSON, HTML, or a redirect) without forcing you into a big, opinionated structure.
It doesn’t try to define your whole application. Instead, it gives you a few core building blocks—an app object, routing, and middleware—so you can assemble exactly the server you need.
At the center of Express is routing: mapping an HTTP method and a URL path to a function.
A handler is just code that runs when a request matches. For example, you can say: when someone requests GET /health, run a function that returns “ok”. When they send POST /login, run a different function that checks credentials and sets a cookie.
This “map routes to functions” approach is easy to reason about because you can read your server like a table of contents: here are the endpoints, here is what each one does.
When a request arrives, Express hands you two main objects:
Your job is to look at the request, decide what should happen, and finish by sending a response. If you don’t send one, the client waits.
In between, Express can run a chain of helpers (middleware): logging, parsing JSON bodies, checking authentication, handling errors, and more. Each step can do some work and then pass control to the next.
Express became popular because the surface area is small: a handful of concepts gets you to a working API quickly. The conventions are clear (routes, middleware, req/res), and you can start simple—one file, a few routes—then split things into folders and modules when the project grows.
That “start small, grow as needed” feel is a big part of why Express became a default choice for so many Node backends.
Express and Koa are often described as “minimal,” but their real gift is a way of thinking: middleware. Middleware treats a web request as a series of small steps that transform, enrich, or reject it before a response is sent.
Instead of one giant request handler that does everything, you build a chain of focused functions. Each one has a single job—add context, validate something, handle an edge case—then passes control along. The app becomes a pipeline: request in, response out.
Most production backends rely on a familiar set of steps:
This is why “minimal” frameworks can still power serious APIs: you add only the behaviors you need, in the order you need them.
Middleware scales because it encourages mix-and-match composition. When requirements change—new auth strategy, stricter input validation, different logging—you can swap a step rather than rewrite the app.
It also makes it easier to share patterns across services: “every API has these five middlewares” becomes a team standard.
Just as importantly, middleware shapes code style and folder structure. Teams often organize by layers (e.g., /middleware, /routes, /controllers) or by features (each feature folder contains its route + middleware). Either way, the middleware boundary pushes you toward small, testable units and a consistent flow that new developers can learn quickly.
Koa is TJ Holowaychuk’s second take on a minimalist Node.js web framework. It was created after Express had proven the “small core + middleware” model could power serious production apps—but also after its early design constraints started to show.
Express grew up in a world where callback-heavy APIs were normal and the best ergonomics often came from convenience helpers inside the framework.
Koa’s goal was to step back and make the core even smaller, leaving more decisions to the application. The result is a framework that feels less like a bundled toolkit and more like a clean foundation.
Koa intentionally avoids shipping lots of “standard” features (routing, body parsing, templating). That’s not omission by accident—it’s a nudge toward choosing explicit building blocks for each project.
One of Koa’s most practical improvements is the way it models request flow. Conceptually, instead of nesting callbacks to “pass control,” Koa encourages middleware that can pause and resume work:
await the downstream workThis makes it easier to reason about “what happens before and after” a handler, without mental gymnastics.
Koa keeps the core philosophy that made Express successful:
So Koa isn’t “Express but newer.” It’s Express’s minimalist idea pushed further: a slimmer core and a clearer, more structured way to control the request lifecycle.
Express and Koa share the same minimalist DNA, but they feel very different once you build something non-trivial. The key difference isn’t “new vs old”—it’s how much structure each framework gives you out of the box.
Express is easy to pick up because it has a familiar, straightforward mental model: define routes, attach middleware, send a response. Most tutorials and examples look similar, so new team members become productive quickly.
Koa is simpler at the core, but that also means you assemble more yourself. The async/await-first approach can feel cleaner, yet you’ll make more early decisions (routing, request validation, error handling style) before your app looks “complete.”
Express has a larger community, more copy‑pasteable snippets, and more “standard” ways of doing common tasks. Many libraries assume Express conventions.
Koa’s ecosystem is healthy, but it expects you to choose your preferred modules. That’s great when you want control, but it can slow teams that want one obvious stack.
Express fits:
Koa fits:
Choose Express when pragmatism wins: you want the shortest path to a working service, predictable patterns, and minimal debate about tooling.
Choose Koa when you’re willing to “design your framework” a bit: you want a clean core, tighter control over your middleware stack, and fewer legacy conventions influencing your architecture.
Express and Koa stay small on purpose: they handle the HTTP request/response cycle, routing basics, and the middleware “pipeline.” By not bundling every feature, they leave space for the community to build the rest.
A minimal framework becomes a stable “attachment point.” Once lots of teams rely on the same simple primitives (request objects, middleware signatures, error handling conventions), it becomes easy to publish add-ons that plug in cleanly.
That’s why Express and Koa sit at the center of huge npm ecosystems—even if the frameworks themselves look tiny.
Common add-on categories include:
This “bring your own building blocks” model lets you tailor a backend to the product. A small internal admin API might need only logging and auth, while a public API might add validation, rate limiting, caching, and observability.
Minimal cores make it easier to adopt only what you need, and to swap components when requirements change.
The same freedom creates risk:
In practice, Express/Koa ecosystems reward teams that curate a “standard stack,” pin versions, and review dependencies—because the framework won’t do that governance for you.
Express and Koa are deliberately small: they route requests, help you structure handlers, and enable middleware. That’s a strength—but it also means they won’t automatically give you the “safe defaults” people sometimes assume a web framework includes.
A minimalist backend needs a conscious security checklist. At a minimum:
Errors are inevitable; what matters is how consistently they’re handled.
In Express, you typically centralize error handling with an error middleware (the one with four arguments). In Koa, you generally wrap the request in a try/catch near the top of the middleware stack.
Good patterns in either:
{ code, message, details }) so clients aren’t guessing.Minimal frameworks won’t set up operational essentials for you:
/health) that verify critical dependencies like databases.Most real security issues arrive through packages, not your router.
Prefer well-maintained modules with recent releases, clear ownership, and good documentation. Keep your dependency list small, avoid “one-line helper” packages, and regularly audit for known vulnerabilities.
When you do add middleware, treat it like production code: review defaults, configure it explicitly, and keep it updated.
Minimal frameworks like Express and Koa make it easy to start, but they don’t force good boundaries. “Maintainable” isn’t about how few lines you have—it’s about whether the next change is predictable.
A maintainable backend is:
If you can’t confidently answer “where would this code live?” the project is already drifting.
Middleware is powerful, but long chains can turn into “action at a distance,” where a header or error response is set far from the route that triggered it.
A few habits prevent confusion:
In Koa, be especially careful with await next() placement; in Express, be strict about when you call next(err) versus returning a response.
A simple structure that scales is:
/web for HTTP concerns (routes, controllers, request parsing)/domain for business logic (services/use-cases)/data for persistence (repositories, queries)Group code by feature (e.g., billing, users) inside those layers. That way, “add a billing rule” doesn’t mean hunting across a “controllers/ services/ utils/ misc” maze.
The key boundary: web code translates HTTP → domain inputs, and the domain returns results that the web layer translates back to HTTP.
This split keeps tests fast while still catching real wiring issues—exactly what minimal frameworks leave up to you.
Express and Koa still make sense in 2025 because they represent the “small core” end of the Node.js framework spectrum. They don’t try to define your whole application—just the HTTP request/response layer—so they’re often used directly for APIs or as a thin shell around your own modules.
If you want something that feels like Express but is tuned for speed and a bit more modern ergonomics, Fastify is a common step. It keeps the “minimal framework” spirit, but adds a stronger plugin system, schema-friendly validation, and a more opinionated approach to serialization.
If you want a framework that feels more like a full application platform, NestJS sits on the other end: it adds conventions for controllers/services, dependency injection, common modules, and a consistent project structure.
You’ll also see teams reach for “batteries-included” stacks (for example, Next.js API routes for web apps) when the backend is closely tied to a frontend and deployment workflow.
More structured frameworks typically give you:
This reduces decision fatigue and helps onboard new developers faster.
The tradeoff is less flexibility and a larger surface area to learn. You may inherit patterns you don’t need, and upgrades can involve more moving parts.
With Express or Koa, you choose exactly what to add—but you also own those choices.
Pick Express/Koa when you need a small API quickly, have a team comfortable making architectural calls, or you’re building a service with unusual requirements.
Pick a more opinionated framework when timelines demand consistency, you expect frequent handoffs, or you want “one standard way” across multiple teams.
Express and Koa endure because they bet on a few lasting ideas rather than a long feature list. TJ Holowaychuk’s core contribution wasn’t “another router”—it was a way to keep the server small, predictable, and easy to extend.
A minimal core forces clarity. When a framework does less by default, you make fewer accidental choices (templating, ORM style, validation approach) and can adapt to different products—from a tiny webhook receiver to a larger web API.
The middleware pattern is the real superpower. By composing small, single-purpose steps (logging, auth, parsing, rate limiting), you get an application that reads like a pipeline. Express popularized this composition; Koa refined it with a cleaner control flow that makes “what happens next” easier to reason about.
Finally, community extensions are a feature, not a workaround. Minimal frameworks invite ecosystems: routers, auth adapters, request validation, observability, background jobs. The best teams treat these as deliberate building blocks, not random add-ons.
Pick the framework that matches your team’s preferences and the project’s risk:
Either way, your real architecture decisions live above the framework: how you validate input, structure modules, handle errors, and monitor production.
If you like the minimalist philosophy but want to ship faster, a vibe-coding platform like Koder.ai can be a useful complement. You can describe an API in plain language, generate a working web + backend scaffold, and then apply Express/Koa principles—small middleware layers, clear boundaries, explicit dependencies—without starting from an empty folder. Koder.ai also supports source code export, snapshots/rollback, and deployment/hosting, which can reduce the operational overhead that minimal frameworks intentionally leave to you.
If you’re mapping out a Node service, browse more guides in /blog. If you’re evaluating tools or support options for shipping a backend, see /pricing.
Express and Koa focus on a small HTTP core: routing plus a middleware pipeline. They don’t bundle opinions for auth, database access, background jobs, or project structure, so you add only what your service needs.
That keeps the framework easy to learn and stable over time, but it also means you’re responsible for picking and integrating the rest of the stack.
Middleware breaks request handling into small, single-purpose steps that run in order (e.g., logging → body parsing → auth → validation → route handler → error handling).
This makes behavior composable: you can swap one step (like auth) without rewriting the whole app, and you can standardize a shared set of middleware across multiple services.
Pick Express when you want the fastest path to a working service with widely-known conventions.
Common reasons:
Pick Koa when you want a slimmer core and you’re comfortable assembling the pieces yourself.
It tends to fit well when:
async/await control flowExpress middleware typically looks like (req, res, next) and you centralize failures using an error middleware (the one with four arguments).
Koa middleware is usually async (ctx, next) and common practice is a top-level try/catch that wraps await next().
In both cases, aim for predictable status codes and a consistent error body (e.g., ).
Start with “edge first, domain inside” boundaries:
/web: routes/controllers, request parsing, response shaping/domain: business rules (services/use-cases)/data: persistence (repositories/queries)Organize by within those layers (e.g., , ) so changes stay localized and you can answer “where would this code live?” quickly.
A practical baseline for most APIs:
Keep the chain short and purpose-specific; document any ordering constraints.
Minimal frameworks won’t give you safe defaults, so add them deliberately:
Treat middleware configuration as security-critical, not optional.
Curate a small “standard stack” and treat third-party packages like production code:
npm audit) and remove unused packagesIn minimal ecosystems, most risk comes from dependencies, not the router.
Choose a more opinionated framework when consistency and scaffolding matter more than flexibility.
Typical signals:
If you’re primarily building HTTP endpoints and want full control over composition, Express/Koa are still a strong fit.
{ code, message, details }usersbilling