koa-compose

Bundle Several Middleware Into One Reusable, Pipeable Function

koa-compose

koa-compose turns an array of middleware functions into a single function with the same (ctx, next) signature. It is how Koa builds its own internal pipeline and how you create shareable middleware stacks.

3 min read Level 2/5 #koa#middleware#compose
What you'll learn
  • Use koa-compose to combine middleware into a single function
  • Understand that Koa uses compose internally for app.use()
  • Build a reusable middleware stack for API routes

koa-compose is the function at the heart of Koa. It takes an array of middleware and returns a single function with the (ctx, next) signature — exactly what app.use() expects.

Installation

npm install koa-compose

Basic Usage

import compose from 'koa-compose';

const mw1 = async (ctx, next) => {
  console.log('mw1 before');
  await next();
  console.log('mw1 after');
};

const mw2 = async (ctx, next) => {
  console.log('mw2 before');
  await next();
  console.log('mw2 after');
};

const combined = compose([mw1, mw2]);

// Use anywhere an (ctx, next) function is accepted
app.use(combined);

The composed function preserves the onion order: mw1 wraps mw2.

How Koa Uses It Internally

Every time you call app.use(fn), Koa pushes fn into an internal array. When a request arrives, Koa calls compose(this.middleware)(ctx) to execute the full pipeline. Understanding this makes the entire middleware system predictable.

Reusable API Middleware Stack

A common pattern is composing a shared set of middleware for a sub-section of your app:

import compose from 'koa-compose';
import bodyParser from 'koa-bodyparser';
import cors from '@koa/cors';
import { requireAuth } from './auth.js';
import { rateLimiter } from './rate-limit.js';

// One composed unit applied to every API route
export const apiStack = compose([
  cors({ origin: process.env.ALLOWED_ORIGIN }),
  bodyParser(),
  requireAuth,
  rateLimiter,
]);
// In your main app file
import { apiStack } from './middleware/api-stack.js';
import router from './router.js';

app.use(errorHandler);
app.use(apiStack);       // all four middleware in one line
app.use(router.routes());

Composing for Testing

compose makes unit testing individual middleware stacks trivial because the composed function is a plain async function:

import { createMockContext } from '@shopify/jest-koa-mocks';

const stack = compose([requireAuth, rateLimiter]);

test('rejects unauthenticated requests', async () => {
  const ctx = createMockContext({ headers: {} });
  await expect(stack(ctx, async () => {})).rejects.toThrow('401');
});

Nesting Composed Stacks

Composed stacks can themselves be composed — they are just functions:

const loggingStack = compose([requestId, logger]);
const securityStack = compose([helmet(), cors()]);

const fullStack = compose([loggingStack, securityStack]);
app.use(fullStack);

Up Next

koa-mount takes a sub-application or composed middleware and mounts it under a path prefix — the next step in modular Koa architecture.

Mounting Sub-Apps →