Koa middleware is an async function that accepts two parameters: ctx (context object) and next (function to call the next middleware). Developing custom middleware requires following specific patterns and best practices.
Basic middleware structure:
javascriptasync function myMiddleware(ctx, next) { // Pre-logic console.log('Request entered'); // Call next middleware await next(); // Post-logic console.log('Request completed'); } // Use middleware app.use(myMiddleware);
Common middleware types:
- Logger middleware:
javascriptfunction loggerMiddleware(ctx, next) { const start = Date.now(); await next(); const ms = Date.now() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); }
- Authentication middleware:
javascriptasync function authMiddleware(ctx, next) { const token = ctx.headers.authorization; if (!token) { ctx.throw(401, 'Unauthorized'); } try { const user = await verifyToken(token); ctx.state.user = user; await next(); } catch (error) { ctx.throw(401, 'Invalid token'); } }
- Error handling middleware:
javascriptasync function errorHandler(ctx, next) { try { await next(); } catch (err) { ctx.status = err.status || 500; ctx.body = { error: err.message, code: err.code }; ctx.app.emit('error', err, ctx); } }
- CORS middleware:
javascriptasync function corsMiddleware(ctx, next) { ctx.set('Access-Control-Allow-Origin', '*'); ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); ctx.set('Access-Control-Allow-Headers', 'Content-Type, Authorization'); if (ctx.method === 'OPTIONS') { ctx.status = 204; return; } await next(); }
- Body parser middleware:
javascriptasync function bodyParser(ctx, next) { if (ctx.method !== 'POST' && ctx.method !== 'PUT') { return await next(); } const chunks = []; for await (const chunk of ctx.req) { chunks.push(chunk); } const body = Buffer.concat(chunks).toString(); ctx.request.body = JSON.parse(body); await next(); }
Middleware development best practices:
- Naming conventions: Use descriptive function names like
authMiddleware,loggerMiddleware - Error handling: Use try-catch to catch errors, avoid affecting other middleware
- Performance optimization: Avoid time-consuming operations in middleware
- Configuration: Support configuration parameters for flexibility
- Documentation: Provide clear usage documentation and examples
Configurable middleware example:
javascriptfunction createLogger(options = {}) { const { format = 'default', includeQuery = false } = options; return async function logger(ctx, next) { const start = Date.now(); await next(); const ms = Date.now() - start; let log = `${ctx.method} ${ctx.url} - ${ms}ms`; if (includeQuery && Object.keys(ctx.query).length) { log += ` ${JSON.stringify(ctx.query)}`; } console.log(log); }; } // Use configurable middleware app.use(createLogger({ includeQuery: true }));
Middleware composition:
javascriptconst compose = require('koa-compose'); const middleware = compose([ loggerMiddleware, authMiddleware, errorHandler ]); app.use(middleware);
The key to developing high-quality middleware is understanding the onion model's execution flow, properly using pre and post logic, and ensuring middleware independence and reusability.