PermissiveMITTested by Geekflare

Koa review

Small async middleware framework for Node.js from the team behind Express, with no bundled middleware

Data refreshedMethodologyHow we tested
Director of Geekflare

Rity Anand is a director of Geekflare. She has extensive experience managing business technology and a special interest in emerging technologies.

Verdict

Koa is a good fit for developers who want a very small core and async middleware that can act before and after the rest of the stack, with Koa 3 adding Web response bodies and access to the current context. The catch is that almost everything else, from routing and body parsing to validation and types, comes from separate packages, and some of the official koajs ones, such as the joi router and JWT middleware, have not had a release in years.

Best for

Developers who want a minimal async core and pick every other piece

Not for

Teams that want validation, OpenAPI docs or project structure in the framework

Pros

  • Middleware can run code before and after downstream handlers
  • Small core that is quick to read in full
  • Koa 3 accepts Web Response bodies and exposes the current context
  • Official router, body parser, CORS, session and static file middleware

Cons

  • Routing and body parsing are separate packages
  • TypeScript types come from DefinitelyTyped, not the project
  • Official validation and JWT middleware have not been released in years
Ask Sparkian

Is Koa right for your project?

Describe what you need and AI will check the fit against this review.

Or try:

Koa features and capabilities

Async middleware stack

Each middleware awaits the next one and can change the response on the way back, which makes timing, error handling and response rewrites simple to write.

One context object

Request and response helpers sit on a single ctx object, so handlers read and set everything in one place.

Current context in Koa 3

Code deep in a call stack can read the active request context through app.currentContext without passing ctx around.

Web standard response bodies

Handlers can set a Web Response or a custom stream as the body, which helps when sharing code with fetch-based libraries.

TypeScript firstWritten in TypeScript or ships its own types, with typed routes or handlers
PartlyTypes from @types/koa
Request validationValidates bodies, params or queries against a schema, in the core or an official package
PartlyOfficial koa-joi-router, last released 2021
OpenAPI docsGenerates an OpenAPI spec or Swagger UI from routes
PartlyCommunity packages only
Beyond Node.jsOfficially runs on Bun, Deno or edge runtimes such as Cloudflare Workers
NoNode.js only in the docs
WebSocketsBuilt in or through an official plugin
PartlyCommunity packages only
Project structureModules, dependency injection or a fixed layout, not only routing
No
Database layerAn official ORM, query builder or data layer
No
AuthenticationAn official auth module, plugin or strategy system
Yeskoa-jwt and koa-session in the koajs org

At a glance

Type
Minimal middleware framework
Stack
JavaScript / Node.js 18+

Free vs paid editions

What you get when you self-host, and what needs a paid plan.

Koa and its official middleware are free and have no paid edition or official hosting.

Running Koa

Koa 3 needs Node.js 18 or later, and the official @koa/router needs Node.js 20 or later.

New cloud server to ready

5 s

Memory in use at idle

82 MB of 4 GB

Reads per second

13,497

Writes per second

8,260

Startup

0.44 s

Version tested

3.2.1 · Oct 31, 2026

  • Koa's core has no router or body parser, so the app adds @koa/router and @koa/bodyparser from the Koa organisation, plus Zod: 78 packages and 17 MB, the most of the light frameworks.
  • Reads had a median latency of 3.5 ms and 6.0 ms at the 99th percentile.
  • Malformed JSON returned the plain text Bad Request rather than JSON.
app.js, after npm i koa @koa/router @koa/bodyparser zod
import Koa from "koa";
import Router from "@koa/router";
import { bodyParser } from "@koa/bodyparser";
import { z } from "zod";

// 100 items kept in memory, so the test measures the framework, not a database.
const items = new Map();
for (let i = 1; i <= 100; i++) items.set(i, { id: i, name: `Item ${i}`, price: i * 1.5, tags: ["sample"] });
let nextId = 101;
const Item = z.object({ name: z.string().min(1).max(100), price: z.number().min(0), tags: z.array(z.string()).max(10).optional() });

const app = new Koa();
const router = new Router();

router.get("/health", (ctx) => { ctx.body = { status: "ok" }; });

router.get("/items/:id", (ctx) => {
  const item = items.get(Number(ctx.params.id));
  if (!item) { ctx.status = 404; ctx.body = { error: "Not found" }; return; }
  ctx.body = item;
});

router.post("/items", (ctx) => {
  const parsed = Item.safeParse(ctx.request.body);
  if (!parsed.success) { ctx.status = 400; ctx.body = { error: "Invalid item", issues: parsed.error.issues }; return; }
  const item = { id: nextId++, ...parsed.data };
  items.set(item.id, item);
  ctx.status = 201;
  ctx.body = item;
});

app.use(bodyParser());
app.use(router.routes()).use(router.allowedMethods());
app.listen(Number(process.env.PORT ?? 3000), "127.0.0.1");
OutputPassed3.2.1 · 400, error body written by my handler from Zod's issues
Request: POST /items with {"name": "", "price": -1}
{
  "error": "Invalid item",
  "issues": [
    {
      "origin": "string",
      "code": "too_small",
      "minimum": 1,
      "inclusive": true,
      "path": [
        "name"
      ],
      "message": "Too small: expected string to have >=1 characters"
    },
    {
      "origin": "number",
      "code": "too_small",
      "minimum": 0,
      "inclusive": true,
      "path": [
        "price"
      ],
      "message": "Too small: expected number to be >=0"
    }
  ]
}

What you can do with Koa

Lightweight API services

Build a small REST service from Koa, @koa/router and @koa/bodyparser with only the parts it needs.

Proxies and response rewriting

Use middleware that runs after downstream handlers to add headers, cache or reshape responses.

Custom in-house frameworks

Use Koa as the base layer for a company framework that adds its own conventions and plugins.