阿里云主机折上折
  • 微信号
Current Site:Index > Middleware composition and reuse techniques

Middleware composition and reuse techniques

Author:Chuan Chen 阅读数:58407人阅读 分类: Node.js

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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.