Koa
01 / 02

Context, Middleware & the Onion Model

Context, Middleware & the Onion Model

Minimal Core, async-First

Created by the Express team as a redesign — Koa ships with no built-in router, body parser, or static file serving, expecting middleware packages for those. Middleware is async-function-based, not Express's callback style.

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();                    // pass control inward
  const ms = Date.now() - start;   // runs on the way back out
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});

app.use(async (ctx) => {
  ctx.status = 200;
  ctx.body = { message: 'Hello, Koa!' };
});

app.listen(3000);

The Onion Model

Code before await next() runs going "in" through the middleware layers; code after it runs going back "out," in reverse order. This is why error-handling middleware is registered FIRST — its try/catch around await next() wraps every downstream middleware registered after it.

app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500;
    ctx.body = { error: err.message };
  }
});
// registered first so it catches errors from every later middleware

ctx.throw(404, 'Not Found');   // shorthand that triggers this flow

The Unified ctx Object

ctx combines request + response (vs. Express's separate req/res) — ctx.body, ctx.status, ctx.method, ctx.url. ctx.state is the conventional namespace for passing data between middleware (e.g. ctx.state.user set by an auth middleware) — avoiding collisions with ctx's own built-in properties.

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free