By Rahul — Google Frontend Engineer
Quick Context
Both are Node.js web frameworks created by TJ Holowaychuk. Express came first (2010), Koa came later (2013) as a more modern take using async/await instead of callbacks.
The Core Difference: Middleware
Express — Linear Middleware
const express = require("express");
const app = express();
app.use((req, res, next) => {
console.log("Start");
next(); // Pass to next middleware
// Code here runs AFTER next(), but this pattern
// is unreliable with async operations
console.log("End"); // May run before async next() finishes
});
app.get("/", (req, res) => {
res.send("Hello");
});Koa — Onion Middleware (async/await)
const Koa = require("koa");
const app = new Koa();
app.use(async (ctx, next) => {
console.log("Start");
await next(); // Properly waits for downstream
console.log("End"); // Guaranteed to run AFTER all downstream
});
app.use(async (ctx) => {
ctx.body = "Hello";
});Koa's onion model means each middleware wraps around the next. The await next() properly waits for all downstream middleware to finish before continuing. This makes timing, logging, and error handling much more predictable.
Error Handling
Express
// Express uses a special 4-argument error handler
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: err.message });
});
// You must manually pass errors with next(err)
app.get("/", (req, res, next) => {
someAsyncOp().catch(next); // Easy to forget
});Koa
// Koa — just use try/catch
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = { error: err.message };
}
});
// Errors naturally propagate up through await
app.use(async (ctx) => {
const data = await riskyOperation(); // Throws naturally
ctx.body = data;
});Feature Comparison
Feature | Express | Koa
-----------------+----------------+------------------
Middleware | Callback-based | async/await
Router | Built-in | Separate package
Body parsing | Built-in (4.x) | Separate package
Template engine | Built-in | Separate package
Community | Massive | Smaller
Bundled features | Many | Minimal (by design)
Error handling | next(err) | try/catch
Context | req + res | ctx (wraps both)When to Use Which
- Express: Quick prototypes, large ecosystem, tons of middleware available, team familiarity
- Koa: Clean async code, precise middleware control, when you want to pick your own libraries
Modern Alternatives
Consider Fastify (fastest, schema validation built-in) or Hono (works everywhere — Node, Deno, Bun, Cloudflare Workers). Express is showing its age, but its ecosystem is unmatched.
Summary
Express is batteries-included with callback middleware. Koa is minimal with async/await onion middleware. Koa gives you cleaner error handling and more predictable middleware flow. Choose based on your team's needs and existing ecosystem.