Middleware composition and reuse techniques
Middleware Composition and Reuse Techniques
The Koa2 middleware mechanism is based on the onion model, enabling flexible control of request processing flows by composing different middleware functions. Properly combining and reusing middleware can significantly improve code maintainability and avoid repetitive logic. Below are specific practical approaches, progressing from basic composition to advanced patterns.
Basic Middleware Composition Methods
The simplest way to compose middleware is by chaining app.use()
calls:
const Koa = require('koa');
const app = new Koa();
app
.use(async (ctx, next) => {
console.log('Middleware 1 start');
await next();
console.log('Middleware 1 end');
})
.use(async (ctx, next) => {
console.log('Middleware 2 start');
await next();
console.log('Middleware 2 end');
});
This basic composition produces the classic onion-style execution order:
Middleware 1 start → Middleware 2 start → Middleware 2 end → Middleware 1 end
Modular Middleware Encapsulation
Encapsulating business logic into independent modules is the foundation of reuse. A typical example is error-handling middleware:
// error-handler.js
module.exports = function() {
return async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = { error: err.message };
ctx.app.emit('error', err, ctx);
}
};
};
// app.js
const errorHandler = require('./error-handler');
app.use(errorHandler());
Middleware Factory Pattern
Create configurable middleware instances using factory functions:
function logger(format = ':method :url') {
return async (ctx, next) => {
const str = format
.replace(':method', ctx.method)
.replace(':url', ctx.url);
console.log(str);
await next();
};
}
app.use(logger());
app.use(logger(':method :url :status'));
Middleware Merging Techniques
The koa-compose
package can combine multiple middleware into a single middleware:
const compose = require('koa-compose');
const middleware1 = async (ctx, next) => { /*...*/ };
const middleware2 = async (ctx, next) => { /*...*/ };
const combined = compose([middleware1, middleware2]);
app.use(combined);
Conditional Middleware Loading
Dynamically load middleware based on the runtime environment:
const conditionalMiddleware = (env) => {
const devMiddleware = require('koa-dev-middleware');
const prodMiddleware = require('koa-compress');
return env === 'development'
? devMiddleware()
: prodMiddleware();
};
app.use(conditionalMiddleware(process.env.NODE_ENV));
Middleware Parameter Passing
Pass data between middleware via the context object:
app.use(async (ctx, next) => {
ctx.state.user = await getUser(ctx.headers.authorization);
await next();
});
app.use(async ctx => {
// Access data set by upstream middleware
console.log(ctx.state.user);
});
Middleware Short-Circuit Control
Control flow interruption via next()
:
// Authorization middleware
app.use(async (ctx, next) => {
if (!ctx.state.user) {
ctx.status = 401;
return; // Interrupt subsequent middleware execution
}
await next();
});
Middleware Composition Patterns in Practice
Authentication + Authorization Composition Example
const auth = compose([
// JWT parsing
async (ctx, next) => {
const token = ctx.get('Authorization');
ctx.state.user = jwt.verify(token, SECRET);
await next();
},
// Permission check
async (ctx, next) => {
if (!ctx.state.user.roles.includes('admin')) {
throw new Error('Forbidden');
}
await next();
}
]);
app.use(auth);
Request Preprocessing Chain
const preprocess = compose([
bodyParser(),
cors(),
helmet(),
requestId()
]);
app.use(preprocess);
Middleware Performance Optimization
Avoid redundant computations in middleware:
// Anti-pattern
app.use(async (ctx, next) => {
const data = await expensiveOperation(); // Executes on every request
ctx.state.data = data;
await next();
});
// Optimized solution
let cachedData;
app.use(async (ctx, next) => {
if (!cachedData) {
cachedData = await expensiveOperation(); // Executes only once
}
ctx.state.data = cachedData;
await next();
});
Middleware Error Handling Strategies
Layered error handling approach:
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
// Business errors
if (err.isBusinessError) {
ctx.status = 400;
ctx.body = { code: err.code };
return;
}
// System errors
ctx.status = 500;
ctx.app.emit('error', err, ctx);
}
});
Middleware Testing Solutions
Test middleware composition using supertest
:
const request = require('supertest');
const app = require('../app');
describe('middleware stack', () => {
it('should process auth middleware', async () => {
const res = await request(app)
.get('/')
.set('Authorization', 'Bearer token');
expect(res.status).toEqual(200);
});
});
Dynamic Middleware Orchestration
Adjust middleware order at runtime:
function createApp(middlewares) {
const app = new Koa();
middlewares.forEach(mw => app.use(mw));
return app;
}
const standardStack = [helmet(), bodyParser()];
const devStack = [...standardStack, morgan('dev')];
const prodStack = [...standardStack, compress()];
Middleware Metaprogramming
Enhance middleware functionality with decorators:
function measureTime(target) {
return async function timedMiddleware(ctx, next) {
const start = Date.now();
await target(ctx, next);
const duration = Date.now() - start;
ctx.set('X-Response-Time', `${duration}ms`);
};
}
app.use(measureTime(async (ctx) => {
ctx.body = await fetchData();
}));
Middleware with TypeScript
Strongly-typed middleware definition example:
interface KoaContextWithUser extends Koa.Context {
state: {
user?: {
id: string;
name: string;
};
};
}
const authMiddleware: Koa.Middleware<KoaContextWithUser> = async (ctx, next) => {
const user = await getUser(ctx.headers.authorization);
ctx.state.user = user;
await next();
};
Middleware Version Control
Implement version-compatible middleware for APIs:
function versionedMiddleware(versions) {
return async (ctx, next) => {
const version = ctx.get('X-API-Version') || 'v1';
const middleware = versions[version] || versions.default;
return middleware(ctx, next);
};
}
app.use(versionedMiddleware({
v1: legacyMiddleware,
v2: modernMiddleware,
default: modernMiddleware
}));
Middleware Debugging Techniques
Use debugging namespaces to locate issues:
const debug = require('debug')('app:middleware');
app.use(async (ctx, next) => {
debug('Request start %s %s', ctx.method, ctx.url);
const start = Date.now();
await next();
const duration = Date.now() - start;
debug('Request finished in %dms', duration);
});
Middleware and Event System Integration
Extend middleware functionality via event mechanisms:
app.use(async (ctx, next) => {
ctx.app.emit('request_start', ctx);
try {
await next();
ctx.app.emit('request_success', ctx);
} catch (err) {
ctx.app.emit('request_error', err, ctx);
throw err;
}
});
app.on('request_error', (err, ctx) => {
console.error(`Request failed: ${ctx.url}`, err);
});
Middleware Execution Order Visualization
Generate middleware stack diagrams:
function inspectMiddleware(app) {
return app.middleware.map((mw, i) => ({
id: i,
name: mw._name || mw.name || 'anonymous',
toString: () => `${i}.${mw.name}`
}));
}
console.log(inspectMiddleware(app));
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:自定义中间件的封装与发布
下一篇:中间件安全注意事项