Koa: a 570 SLOC middleware core and what you give up for it
Expressive middleware for node.js using ES2017 async functions
At a glance
- What is it?
- Koa is an HTTP middleware framework for Node.js built around async functions and a stack that flows downstream then back upstream. It ships no middleware, so the framework decision is really a decision about how much you want to assemble yourself.
- Who is it for?
- Adopt Koa if you are starting a Node.js HTTP service on v18 or higher and you want a small core plus explicit choices about every middleware dependency. Do not adopt it if you need batteries included on day one, or if you are still on Koa v1 or v2 signatures, because the old middleware signature is removed in v3 and the migration guide is required reading rather than optional.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem Koa solves is the middleware layer people keep rewriting
Most Node HTTP frameworks accumulate features until the core is hard to reason about. Koa takes the opposite position. The README states that only methods common to nearly all HTTP servers are integrated directly into what it calls a small ~570 SLOC codebase, and it names the categories: content negotiation, normalization of node inconsistencies, redirection, and a few others. Everything else is a package you choose.
The audience follows from that. Koa is for engineers who already know which pieces they want in an HTTP stack and would rather wire them together than remove defaults they did not ask for. The README is explicit that Koa is not bundled with any middleware. If you want body parsing, sessions, routing or static file serving, those are separate dependencies, and the README points to a middleware list on the project wiki rather than shipping them.
The second problem is control flow. Koa's README describes the stack as flowing in a stack-like manner, letting you perform actions downstream and then filter and manipulate the response upstream. That ordering is the actual product. Logging, timing, error wrapping and response transformation all depend on being able to run code after the downstream handler finishes, and Koa makes that the default shape of middleware rather than something you approximate with callbacks.
The onion model and how ctx, request and response fit together
A Koa middleware is a function that receives a Context object and a next function. Calling await next() runs the downstream middleware and resolves when it completes, so any code after that await runs on the way back up. The README's logger example is exactly this pattern: record a start time, await next(), compute the elapsed milliseconds, log. Timing middleware written this way does not need to patch the response object or listen for a finish event.
The Context object, conventionally named ctx, encapsulates an incoming HTTP message and the corresponding response. Koa exposes a Request object as ctx.request and a Response object as ctx.response. Both delegate to Node's own IncomingMessage and ServerResponse rather than extending them. The README gives the reason directly: delegation produces a cleaner interface, reduces conflicts between middleware and with Node itself, and gives better support for stream handling. The underlying objects remain reachable as ctx.req and ctx.res.
The Context also carries shortcuts. ctx.type is the same as ctx.response.type, and ctx.accepts is the same as ctx.request.accepts. The README shows a content negotiation check using ctx.assert(ctx.request.accepts('xml'), 406) and notes the equivalent form, if (!ctx.request.accepts('xml')) ctx.throw(406). Streaming is handled through the same surface: set ctx.response.type and assign a readable stream to ctx.response.body, and Koa pipes it. Because the response object delegates rather than replaces Node's, that path stays close to the underlying ServerResponse.
The application object returned by new Koa() is the interface with Node's http server. The README lists its responsibilities: registering middleware, dispatching to middleware from http, default error handling, and configuring the context, request and response objects. That last item is worth noting. If you want to add helpers to ctx, you do it by configuring the application, not by mutating a global prototype.
Getting a Koa server running, and the v3 signature change that will bite you
Installation is one command: npm install koa. The README states that Koa requires Node v18.0.0 or higher, for ES2015 and async function support. The minimal server is a require, a new Koa(), a single app.use call that sets ctx.body, and app.listen(3000).
Middleware can be written two ways. The async form is the documented default for Node v7.6 and above: an async function taking (ctx, next) that awaits next(). The common function form takes the same parameters but returns next().then(...) and runs its post-processing inside the then callback. Both are accepted, and the README presents them side by side as equivalent options rather than treating one as legacy.
The part that matters for upgrades is the signature history. The README states plainly that the middleware signature changed between v1.x and v2.x, that the older signature is deprecated, and that old signature middleware support has been removed in v3. The project publishes two migration guides, one from v2.x to v3.x and one from v1.x to v2.x. If you are running a v1 codebase, the v2 to v3 guide alone will not be enough, because the v1 to v2 change is a separate step. The releases listed for the repository run v3.1.2, v3.2.0 and v3.2.1, so v3 is the current line.
There is no configuration file and no required directory layout. The framework has no CLI. Everything is code: you create the application, register middleware in the order you want them to run, and listen. That is a short setup path, but it also means there is no scaffolding to fall back on when you are unsure what a correct stack looks like.
The missing middleware layer is the real cost
Koa's small core is a deliberate trade, and the cost lands on you. The README says Koa is not bundled with any middleware, and the middleware list lives on the project wiki rather than in the repository. That means the set of packages you depend on is not curated by the core team in the way a bundled framework's defaults are. You are choosing each one, and you own the compatibility question for each one.
The v3 breaking change makes that concrete. Removing the old middleware signature does not just affect your own code. It affects every middleware package in your dependency tree that still uses the v1 style signature. Before upgrading, the question to answer is not whether your application code compiles, it is whether each middleware you rely on has been updated. The README does not provide a compatibility table for third-party middleware, so this is work you do yourself by checking each dependency.
A second limitation is structural. Koa's delegation model means ctx.request and ctx.response are Koa objects wrapping Node's, with ctx.req and ctx.res as the escape hatches. When you need behaviour that Koa's wrappers do not expose, you drop to the Node objects, and at that point you are writing against Node's http API with Koa as a dispatcher. The README frames delegation as a benefit for stream handling and conflict avoidance, which it is, but it also means Koa is thin enough that some problems are simply Node problems.
The documentation set is broad but spread out. The README links a usage guide, an error handling document, a page titled Koa for Express Users, an FAQ, an API reference split across request, response, context and application documents, and a troubleshooting guide. That is a lot of surface for a framework this small, and it tells you something about where the complexity actually lives: not in the core, but in the conventions around it.
Koa against Express: same problem, different centre of gravity
The README itself links a document called Koa for Express Users, which is a signal that the comparison is expected. The difference in approach is not subtle. Express is built around a request and response object that the framework extends, and it ships routing and a set of conveniences as part of the core experience. Koa does not extend Node's objects, it delegates to them, and it does not include routing at all.
The control flow difference is the one that changes how you write code. In Koa, await next() gives you a genuine pause point, so a middleware can measure, wrap, or modify a response after the downstream handler has produced it. The README's logger example depends on this. In a callback-style framework, the equivalent usually means attaching a listener to the response or wrapping the downstream call, which is a different mental model and a different set of failure modes.
The trade runs in both directions. Express gives you a routing layer, a larger middleware ecosystem with more established conventions, and a shorter path from zero to a working API. Koa gives you a smaller core, a context object with content negotiation and assertion helpers already on it, and a middleware ordering model that makes cross-cutting concerns like timing and error wrapping natural. If your application is mostly routing plus a handful of handlers, the Koa advantage is smaller, because you will install a router anyway and the onion model has less to wrap.
One more difference worth naming: Koa's error handling is part of the application object, per the README's description of the application's responsibilities. There is a dedicated error handling document, and the README lists it alongside the usage guide. That suggests the intended pattern is to handle errors in the application and in middleware, not to install an error middleware package as a first step.
Maintenance, licence and the upgrade arithmetic
Koa is MIT licensed, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are preserved. That is the standard permissive arrangement and it is compatible with proprietary applications. This is a description of the licence text, not legal advice; if your organisation has specific obligations around attribution or dependency review, run the licence through your own process.
The maintenance picture visible in the repository is active. The repository is not archived, the last push is dated 2026-09-03, and the three most recent releases are v3.1.2, v3.2.0 and v3.2.1, spanning February to May 2026. Patch and minor releases at that cadence suggest ongoing maintenance rather than a frozen project.
Upgrade cost is where Koa asks for real work. The v3 line removed the old middleware signature, and the project maintains two separate migration guides because there are two distinct jumps. A codebase on v2 needs the v2 to v3 guide. A codebase on v1 needs both, in order. The cost is not in the framework's own code, which is small by design, but in auditing every middleware dependency for signature compatibility. Because Koa ships no middleware, that audit is the upgrade.
The README also states a security reporting process: vulnerabilities should be emailed to the named maintainers rather than filed as a public issue, on the grounds that a public issue notifies attackers. If you operate a service on Koa, that is the channel to use, and it is worth noting that it is an email process rather than a private advisory system.
Who should pick Koa, and what to check before you do
Koa fits teams that have already decided what their HTTP stack contains and want to assemble it explicitly. If you are comfortable choosing a router, a body parser and the rest, and you value being able to run code after the downstream handler without workarounds, the onion model is the reason to be here. The content negotiation and assertion helpers on ctx, shown in the README as ctx.assert(ctx.request.accepts('xml'), 406) and ctx.throw(406), cover a category of code you would otherwise write by hand.
It fits less well if you want a framework to make decisions for you. The absence of bundled middleware is not a gap that gets filled later; it is the design. Teams without a clear opinion on their dependency set will spend their first week making choices Koa deliberately declines to make.
Three things to verify before you commit. First, Node version: Koa requires v18.0.0 or higher, and that is a hard floor, not a recommendation. Second, middleware compatibility: because the old signature is removed in v3, check each middleware package you intend to use against the v3 signature, and read the v2 to v3 migration guide if you are coming from v2, or both guides if you are coming from v1. Third, error handling: the application object owns default error handling and the project publishes a dedicated error handling document, so confirm your error strategy matches that model rather than assuming a middleware package will cover it. The README's own documentation index, which lists the usage guide, error handling, the Express comparison, the FAQ, the API references and the troubleshooting guide, is the shortest path through those checks.
Editorial conclusion
Adopt Koa if you are starting a Node.js HTTP service on v18 or higher and you want a small core plus explicit choices about every middleware dependency. Do not adopt it if you need batteries included on day one, or if you are still on Koa v1 or v2 signatures, because the old middleware signature is removed in v3 and the migration guide is required reading rather than optional. Before committing, verify three things against your own codebase: that every middleware you depend on is published for the v3 signature, that your Node runtime is at least v18.0.0, and that your error handling accounts for Koa's default error handling rather than a framework-supplied error middleware.
Community notes