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 flowThe 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