Run Middleware Only When the Path or Method Matches
Conditional Middleware
Not every middleware should run on every request. Learn to gate middleware execution by inspecting ctx.path or ctx.method, and how the koa-unless pattern inverts the condition cleanly.
What you'll learn
- Conditionally invoke next() based on ctx.path and ctx.method
- Implement an unless wrapper to skip middleware for specified routes
- Apply conditional middleware for auth, logging, and body parsing
app.use() registers middleware for every request. To restrict
execution to certain paths or methods, inspect ctx.path and ctx.method
before deciding whether to invoke next() or do real work.
Branching on ctx.path
The simplest approach: check the path at the top of the middleware.
app.use(async (ctx, next) => {
if (ctx.path.startsWith('/api')) {
// only run for /api/* routes
ctx.state.apiRequest = true;
}
await next(); // always call next so other routes still run
}); Skipping Middleware for Specific Routes
Sometimes you want a middleware to run everywhere except a few paths —
e.g., skip auth for /health or /login.
function unless(middleware, ...excludedPaths) {
return async (ctx, next) => {
if (excludedPaths.includes(ctx.path)) {
return next(); // skip the middleware, pass through
}
return middleware(ctx, next);
};
}
import auth from './auth-middleware.js';
app.use(unless(auth, '/health', '/login', '/register')); Gating on ctx.method
import bodyParser from 'koa-bodyparser';
// Only parse body for mutating requests
app.use(async (ctx, next) => {
if (['POST', 'PUT', 'PATCH'].includes(ctx.method)) {
return bodyParser()(ctx, next);
}
await next();
}); Combining Path and Method
app.use(async (ctx, next) => {
const isApiWrite =
ctx.path.startsWith('/api') && ctx.method !== 'GET';
if (isApiWrite) {
// enforce JSON content-type for API mutations
if (!ctx.is('application/json')) {
ctx.throw(415, 'Content-Type must be application/json');
}
}
await next();
}); Using @koa/router for Path-Scoped Middleware
For per-route middleware, router-level attachment is cleaner than global conditionals:
import Router from '@koa/router';
const router = new Router();
async function requireAdmin(ctx, next) {
if (!ctx.state.user?.isAdmin) ctx.throw(403);
await next();
}
// Only /admin routes get the requireAdmin check
router.use('/admin', requireAdmin);
router.get('/admin/users', listUsers); When to Use Each Approach
| Approach | Best for |
|---|---|
ctx.path branch in app.use | Broad path-prefix rules |
unless() wrapper | ”Run everywhere except…” logic |
Router .use() per prefix | Route-specific middleware |
| Method check | Body parsing, CSRF for mutations only |
Up Next
koa-compose lets you bundle several middleware functions into a single
reusable unit — ideal for shared stacks across apps.