阿里云主机折上折
  • 微信号
Current Site:Index > The reuse and modularization of middleware

The reuse and modularization of middleware

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

Reusability and Modularization of Middleware

Express middleware is the core mechanism for handling HTTP requests. By breaking down functionality into reusable modules, it can significantly improve code maintainability and development efficiency. Proper middleware design makes routing logic clearer, avoids code duplication, and facilitates team collaboration.

Basic Reusability Patterns for Middleware

Express middleware is essentially a function with a specific signature. The simplest way to reuse it is to extract the middleware function as an independent module. For example, a middleware that records request time can be encapsulated as follows:

// middleware/requestTime.js
module.exports = function requestTime(req, res, next) {
  req.requestTime = Date.now()
  next()
}

// app.js
const requestTime = require('./middleware/requestTime')
app.use(requestTime)

This pattern is particularly suitable for logic that needs to be reused across multiple routes, such as authentication, data preprocessing, etc. More complex middleware can be configured using factory functions:

// middleware/logger.js
module.exports = function(format) {
  return function(req, res, next) {
    console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`)
    next()
  }
}

// app.js
const logger = require('./middleware/logger')
app.use(logger('combined'))

Modular Middleware Composition

Multiple related middleware can be combined into a module package and exported uniformly via index.js. For example, building a collection of API security-related middleware:

// middleware/security/index.js
const helmet = require('helmet')
const rateLimit = require('express-rate-limit')

module.exports = {
  basicProtection: helmet(),
  apiLimiter: rateLimit({
    windowMs: 15 * 60 * 1000,
    max: 100
  }),
  cors: require('./cors')
}

It can be imported as a whole or selectively:

const { basicProtection, apiLimiter } = require('./middleware/security')
app.use(basicProtection)
app.use('/api/', apiLimiter)

Route-Level Middleware Encapsulation

For middleware specific to a group of routes, modularization can be achieved more finely using Router. For example, a user system routing module:

// routes/users.js
const router = require('express').Router()
const auth = require('../middleware/auth')
const validator = require('../middleware/userValidator')

router.use(auth.required)
router.get('/profile', validator.getProfile, getUserProfile)
router.post('/update', validator.updateUser, updateUser)

module.exports = router

The main application file only needs to mount the routing module:

// app.js
app.use('/users', require('./routes/users'))

Dynamic Middleware Loading Mechanism

Dynamic loading of middleware via configuration files enables environment-specific management. Create middleware-loader.js:

const fs = require('fs')
const path = require('path')

module.exports = function(app) {
  const env = process.env.NODE_ENV || 'development'
  const config = require(`./config/${env}.json`)

  config.middlewares.forEach(mw => {
    const middleware = require(path.join(__dirname, mw.path))
    app.use(middleware(config.options[mw.name]))
  })
}

Example configuration file:

// config/development.json
{
  "middlewares": [
    { "name": "logger", "path": "./middleware/logger" },
    { "name": "debug", "path": "./middleware/debugTool" }
  ],
  "options": {
    "logger": { "level": "verbose" }
  }
}

Middleware Testing Strategies

Reusable middleware should be independently testable. Example of HTTP-layer testing using supertest:

// test/authMiddleware.test.js
const request = require('supertest')
const express = require('express')
const auth = require('../middleware/auth')

test('auth middleware rejects missing token', async () => {
  const app = express()
  app.get('/protected', auth.required, (req, res) => res.sendStatus(200))
  
  const res = await request(app)
    .get('/protected')
    .expect(401)

  expect(res.body.error).toBe('Unauthorized')
})

For pure function middleware, direct invocation testing can be used:

// test/queryParser.test.js
const queryParser = require('../middleware/queryParser')

test('transforms comma-separated strings to arrays', () => {
  const req = { query: { tags: 'js,node,express' } }
  const next = jest.fn()
  
  queryParser(req, {}, next)
  
  expect(req.query.tags).toEqual(['js', 'node', 'express'])
  expect(next).toHaveBeenCalled()
})

Error Handling Patterns for Middleware

Error-handling middleware requires special design considerations. Creating a reusable error handler:

// middleware/errorHandler.js
module.exports = function(options = {}) {
  return function(err, req, res, next) {
    if (options.log) {
      console.error('[ERROR]', err.stack)
    }

    res.status(err.status || 500).json({
      error: options.expose ? err.message : 'Internal Server Error'
    })
  }
}

// app.js
const errorHandler = require('./middleware/errorHandler')
app.use(errorHandler({ 
  expose: process.env.NODE_ENV === 'development'
}))

For async middleware, wrapping is required:

// middleware/asyncHandler.js
module.exports = function(handler) {
  return function(req, res, next) {
    Promise.resolve(handler(req, res, next))
      .catch(next)
  }
}

// routes/users.js
router.get('/:id', asyncHandler(async (req, res) => {
  const user = await User.findById(req.params.id)
  if (!user) throw new Error('Not found')
  res.json(user)
}))

Performance Optimization for Middleware

High-frequency middleware should consider performance optimization. For example, caching ETag calculations for static resources:

// middleware/staticCache.js
const crypto = require('crypto')
const fs = require('fs').promises

module.exports = function(root) {
  const cache = new Map()

  return async function(req, res, next) {
    if (req.method !== 'GET') return next()
    
    const filePath = path.join(root, req.path)
    try {
      let etag = cache.get(filePath)
      const stats = await fs.stat(filePath)
      
      if (!etag || etag.mtime < stats.mtimeMs) {
        const content = await fs.readFile(filePath)
        etag = {
          value: crypto.createHash('md5').update(content).digest('hex'),
          mtime: stats.mtimeMs
        }
        cache.set(filePath, etag)
      }
      
      res.set('ETag', etag.value)
      if (req.headers['if-none-match'] === etag.value) {
        return res.sendStatus(304)
      }
    } catch (err) {
      // Pass through errors like file not found
    }
    next()
  }
}

Type Safety for Middleware

In TypeScript projects, middleware type constraints can be defined:

// types/middleware.ts
import { Request, Response, NextFunction } from 'express'

export interface Middleware {
  (req: Request, res: Response, next: NextFunction): void
}

export interface AsyncMiddleware {
  (req: Request, res: Response, next: NextFunction): Promise<void>
}

// middleware/auth.ts
const authMiddleware: Middleware = (req, res, next) => {
  if (!req.headers.authorization) {
    return res.status(401).json({ error: 'Unauthorized' })
  }
  next()
}

Dependency Injection for Middleware

For middleware requiring external dependencies, a dependency injection pattern can be adopted:

// middleware/dbContext.js
module.exports = function(db) {
  return function(req, res, next) {
    req.db = {
      users: db.collection('users'),
      posts: db.collection('posts')
    }
    next()
  }
}

// app.js
const MongoClient = require('mongodb').MongoClient
const dbContext = require('./middleware/dbContext')

MongoClient.connect(uri, (err, client) => {
  const db = client.db('myapp')
  app.use(dbContext(db))
})

AOP Practices in Middleware

Aspect-oriented programming is particularly suitable for middleware. For example, implementing method execution time measurement:

// middleware/benchmark.js
module.exports = function(label) {
  return function(req, res, next) {
    const start = process.hrtime()
    
    res.on('finish', () => {
      const diff = process.hrtime(start)
      console.log(`${label} took ${diff[0] * 1e3 + diff[1] / 1e6}ms`)
    })
    
    next()
  }
}

// routes/api.js
router.use(require('../middleware/benchmark')('API'))
router.get('/data', /* ... */)

Version Compatibility for Middleware

Middleware design for handling API version compatibility:

// middleware/versioning.js
module.exports = function(options) {
  return function(req, res, next) {
    const version = req.get('X-API-Version') || options.default
    if (!options.supported.includes(version)) {
      return res.status(406).json({
        error: `Unsupported API version. Supported: ${options.supported.join(', ')}`
      })
    }
    
    req.apiVersion = version
    res.set('X-API-Version', version)
    next()
  }
}

// app.js
app.use(require('./middleware/versioning')({
  default: 'v1',
  supported: ['v1', 'v2']
}))

Metaprogramming Applications in Middleware

Using Proxy to implement dynamic middleware properties:

// middleware/featureToggle.js
module.exports = function(features) {
  return function(req, res, next) {
    req.features = new Proxy({}, {
      get(target, name) {
        return features.includes(name) 
          ? require(`./features/${name}`)
          : null
      }
    })
    next()
  }
}

// routes/admin.js
router.post('/audit', (req, res) => {
  if (req.features.advancedAudit) {
    req.features.advancedAudit.log(req.body)
  }
  // ...
})

Lifecycle Extension for Middleware

Adding lifecycle hooks to middleware:

// middleware/lifecycle.js
module.exports = function() {
  const hooks = {
    pre: [],
    post: []
  }

  const middleware = function(req, res, next) {
    runHooks('pre', req)
      .then(() => next())
      .then(() => runHooks('post', req))
      .catch(next)
  }

  middleware.hook = function(type, fn) {
    hooks[type].push(fn)
    return this
  }

  async function runHooks(type, req) {
    for (const hook of hooks[type]) {
      await hook(req)
    }
  }

  return middleware
}

// app.js
const lifecycle = require('./middleware/lifecycle')()
lifecycle.hook('pre', req => console.log('Request started'))
lifecycle.hook('post', req => console.log('Request completed'))
app.use(lifecycle)

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

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