Middleware Order

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.

3 min read Level 2/5 #koa#middleware#order
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

LayerRegisterReason
Error handlerFirstMust wrap everything
LoggerSecondNeeds full req/res lifecycle
Security (helmet, CORS)ThirdBefore any content is processed
Body parserFourthBefore route handlers
Auth / sessionFifthAfter body, before routes
Router / routesLastInnermost 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 →