阿里云主机折上折
  • 微信号
Current Site:Index > The basic structure and writing specifications of middleware

The basic structure and writing specifications of middleware

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

Basic Structure of Middleware

Koa2 middleware is essentially an asynchronous function that takes two parameters: ctx and next. ctx encapsulates the request and response objects, while next represents the execution control of the next middleware. Middleware uses async/await syntax to handle asynchronous operations, forming an onion-model call chain.

async function middleware(ctx, next) {
  // Pre-processing
  console.log('Before next()');
  
  await next(); // Execute downstream middleware
  
  // Post-processing
  console.log('After next()');
}

The typical structure consists of three phases:

  1. Request pre-processing: Modify ctx state or intercept requests.
  2. next() call: Transfer control to downstream.
  3. Response post-processing: Modify response data or log records.

Writing Standards

Single Responsibility Principle

Each middleware should handle only a specific function. For example, an authentication middleware should not contain business logic:

// Good practice
async function auth(ctx, next) {
  const token = ctx.headers['authorization'];
  if (!verifyToken(token)) {
    ctx.throw(401, 'Unauthorized');
  }
  await next();
}

// Anti-pattern
async function authAndProcess(ctx, next) {
  // Mixing authentication with business logic
  const token = ctx.headers['authorization'];
  if (verifyToken(token)) {
    ctx.user = getUser(token);
    await processBusiness(ctx); // Business logic mixed in
  }
  await next();
}

Error Handling Standards

Synchronous errors are caught automatically, while asynchronous errors require explicit handling:

// Error handling middleware example
app.use(async (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); // Trigger application-level error event
  }
});

// Business middleware throwing an error
app.use(async ctx => {
  if (!ctx.query.requiredParam) {
    const err = new Error('Missing parameter');
    err.code = 'MISSING_PARAM';
    err.status = 400;
    throw err;
  }
});

Performance Optimization Tips

  1. Avoid blocking operations: Long synchronous tasks should be converted to asynchronous.
  2. Use caching wisely: Cache results of high-frequency operations.
  3. Control the number of middleware: Each request traverses the entire middleware stack.
// Caching example
const cache = new Map();
app.use(async (ctx, next) => {
  const key = ctx.url;
  if (cache.has(key)) {
    ctx.body = cache.get(key);
    return;
  }
  
  await next();
  
  if (ctx.status === 200) {
    cache.set(key, ctx.body);
  }
});

Advanced Patterns

Middleware Factory Functions

Create configurable middleware using closures:

function logger(format = ':method :url') {
  return async (ctx, next) => {
    const start = Date.now();
    await next();
    const duration = Date.now() - start;
    console.log(format
      .replace(':method', ctx.method)
      .replace(':url', ctx.url)
      .replace(':duration', duration)
    );
  };
}

app.use(logger());
app.use(logger(':method :url - :duration ms'));

Composing Middleware

Use koa-compose to create sub-middleware stacks:

const compose = require('koa-compose');

const middleware1 = async (ctx, next) => {
  console.log('Middleware 1 start');
  await next();
  console.log('Middleware 1 end');
};

const middleware2 = async (ctx, next) => {
  console.log('Middleware 2 start');
  await next();
  console.log('Middleware 2 end');
};

const combined = compose([middleware1, middleware2]);
app.use(combined);

Response Time Extension

Extend custom methods via context prototype:

// Extend ctx prototype
app.context.formatResponse = function(data) {
  this.type = 'application/json';
  this.body = {
    timestamp: Date.now(),
    data: data,
    status: this.status
  };
};

// Using the extended method
app.use(async ctx => {
  const users = await User.findAll();
  ctx.formatResponse(users);
});

Debugging and Testing

Debugging Techniques

  1. Use the debug module to label middleware.
  2. Insert debugging middleware to inspect context state.
const debug = require('debug')('koa:middleware');

app.use(async (ctx, next) => {
  debug('Request headers:', ctx.headers);
  const start = Date.now();
  await next();
  debug(`Response time: ${Date.now() - start}ms`);
});

Unit Test Example

Test middleware behavior using supertest:

const request = require('supertest');
const Koa = require('koa');

describe('Auth Middleware', () => {
  it('should 401 without token', async () => {
    const app = new Koa();
    app.use(authMiddleware);
    app.use(ctx => { ctx.body = 'OK' });
    
    await request(app.callback())
      .get('/')
      .expect(401);
  });
});

Best Practices

Configuration Management

Share configurations via app.context:

// During initialization
app.context.config = {
  maxRequests: 1000,
  timeout: 3000
};

// Usage in middleware
app.use(async (ctx, next) => {
  if (ctx.request.count > ctx.config.maxRequests) {
    ctx.throw(429);
  }
  await next();
});

Rate Limiting Implementation

Token bucket algorithm middleware example:

class TokenBucket {
  constructor(rate, capacity) {
    this.tokens = capacity;
    this.rate = rate;
    this.lastFill = Date.now();
  }

  take() {
    this.refill();
    if (this.tokens < 1) return false;
    this.tokens -= 1;
    return true;
  }

  refill() {
    const now = Date.now();
    const elapsed = (now - this.lastFill) / 1000;
    this.tokens = Math.min(
      this.capacity,
      this.tokens + elapsed * this.rate
    );
    this.lastFill = now;
  }
}

// Token bucket middleware usage
const bucket = new TokenBucket(10, 20); // 10 tokens/second, 20 max
app.use(async (ctx, next) => {
  if (!bucket.take()) {
    ctx.status = 429;
    ctx.body = 'Too Many Requests';
    return;
  }
  await next();
});

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱: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 ☕.