Register Error Handlers First, Routes Last — Order Is Architecture
Middleware Order
The position of each app.use() call determines which middleware wraps which. Learn the canonical ordering for error handlers, loggers, body parsers, security layers, and route handlers.
What you'll learn
- Explain why registration order equals wrapping order in the onion
- Place error-handling middleware at the top of the stack
- Sequence bodyparser, security, logger, and routes correctly
app.use() calls are executed in the order they are registered. Because
Koa uses the onion model, the first registered middleware is the
outermost layer — it wraps everything that follows.
The Canonical Stack
import Koa from 'koa';
import bodyParser from 'koa-bodyparser';
import helmet from 'koa-helmet';
import logger from 'koa-logger';
import router from './router.js';
const app = new Koa();
// 1. Error handler — outermost so it catches errors from every layer
app.use(errorHandler);
// 2. Logger — sees every request and response, including error responses
app.use(logger());
// 3. Security headers — applied before any route logic runs
app.use(helmet());
// 4. Body parser — must come before routes that read ctx.request.body
app.use(bodyParser());
// 5. Routes — innermost handlers
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000); Why Error Handler Goes First
The error-handling middleware uses a try/catch around await next().
If it were registered after a route, it would only wrap middleware
registered after it — missing any errors from earlier layers.
async function errorHandler(ctx, next) {
try {
await next(); // wraps ALL middleware registered after this one
} catch (err) {
ctx.status = err.status || 500;
ctx.body = { error: err.message };
}
} Body Parser Before Routes
Route handlers read ctx.request.body. If you register the body parser
after a route, that route will see an empty body.
// WRONG: body parser registered too late
app.use(router.routes());
app.use(bodyParser()); // too late — POST body already missed
// RIGHT
app.use(bodyParser());
app.use(router.routes()); Mental Model for Placement
| Layer | Register | Reason |
|---|---|---|
| Error handler | First | Must wrap everything |
| Logger | Second | Needs full req/res lifecycle |
| Security (helmet, CORS) | Third | Before any content is processed |
| Body parser | Fourth | Before route handlers |
| Auth / session | Fifth | After body, before routes |
| Router / routes | Last | Innermost handlers |
Static Assets Are an Exception
Static file middleware can come early (before routes) so it short-circuits immediately for asset requests without passing through auth or body parsing.
Up Next
With order understood, the next lesson shows you how to write a top-level error handler that covers every failure mode.
Error-Handling Middleware →