node.jsExpress中间件

Express 是 Node.js 生态中最主流的轻量级 Web 框架,而中间件(Middleware) 是 Express 的核心设计灵魂。它将 HTTP 请求的处理流程拆解为一系列可插拔、可复用的函数单元,实现了逻辑解耦、功能扩展和流程控制,是 Express 「一切皆中间件」设计理念的核心体现。


一、中间件核心概念

1. 定义

中间件是能够访问请求对象(req)响应对象(res) ,以及请求 - 响应周期中 next 回调函数的函数。它可以在请求到达路由处理函数之前、响应返回客户端之前执行任意逻辑,是请求处理流程中的「过滤器 / 处理器」。

2. 核心三要素

  • req:HTTP 请求对象,包含请求参数、请求头、请求体、Cookie 等所有请求信息
  • res:HTTP 响应对象,用于向客户端返回状态码、响应头、响应体等
  • next:回调函数,调用后将控制权交给当前栈中下一个匹配的中间件

3. 执行模型

Express 中间件采用线性顺序执行模型 (区别于 Koa 的洋葱模型):请求到达后,严格按照中间件的定义顺序和路径匹配规则依次执行,直到某个中间件结束响应(如 res.send()),或触发错误进入错误处理中间件。 若需要实现「后置处理逻辑」,通常通过监听 resfinish 事件实现。


二、五大类中间件与用法

Express 中间件按作用范围和功能分为 5 大类:应用级、路由级、错误处理、内置、第三方。

1. 应用级中间件

绑定到 express 实例(app 对象)上的中间件,通过 app.use()app.METHOD() 定义,作用于全局应用。

(1)全局无路径中间件

所有请求都会触发,无路径限制。

javascript

运行

复制代码
const express = require('express');
const app = express();

// 全局日志中间件
app.use((req, res, next) => {
  console.log(`[${new Date().toLocaleString()}] ${req.method} ${req.path}`);
  next(); // 必须调用,否则请求会挂起无响应
});

app.get('/', (req, res) => res.send('Hello World'));
app.listen(3000);
(2)指定路径的应用级中间件

仅当请求路径以指定路径为前缀时才执行。

javascript

运行

复制代码
// 仅 /user 前缀的请求会触发
app.use('/user', (req, res, next) => {
  console.log('访问用户模块');
  next();
});

// 匹配 /user/info、/user/123 等路径
app.get('/user/info', (req, res) => res.send('用户信息'));
(3)多中间件串联

同一路径可挂载多个中间件,支持连续传参或数组形式。

javascript

运行

复制代码
// 连续传入
app.use('/admin',
  (req, res, next) => { console.log('身份校验'); next(); },
  (req, res, next) => { console.log('权限校验'); next(); }
);

// 数组形式(适合模块化复用)
const verifyIdentity = (req, res, next) => next();
const verifyPermission = (req, res, next) => next();
app.use('/admin', [verifyIdentity, verifyPermission]);

2. 路由级中间件

绑定到 express.Router() 实例上的中间件,语法与应用级完全一致,核心作用是模块化拆分路由,适配大型项目。

javascript

运行

复制代码
const express = require('express');
const app = express();
const userRouter = express.Router(); // 创建路由实例

// 路由级中间件:仅作用于 userRouter 下的所有路由
userRouter.use((req, res, next) => {
  console.log('用户模块请求拦截');
  next();
});

// 路由定义
userRouter.get('/info', (req, res) => res.send('用户信息'));
userRouter.post('/login', (req, res) => res.send('登录'));

// 将路由挂载到应用的 /user 路径下
app.use('/user', userRouter);
app.listen(3000);

3. 错误处理中间件

专门用于统一捕获和处理请求流程中的异常,必须定义 4 个参数err, req, res, next。Express 通过参数数量识别错误处理中间件,缺少任意一个参数都会失效。

  • 触发方式:普通中间件 / 路由中调用 next(err) 手动传递错误;同步代码抛出的错误会被自动捕获。
  • 放置规则:必须放在所有普通中间件和路由之后

javascript

运行

复制代码
// 业务路由
app.get('/error', (req, res, next) => {
  try {
    throw new Error('参数校验失败');
  } catch (err) {
    err.status = 400;
    next(err); // 将错误传递给错误处理中间件
  }
});

// 全局错误处理中间件(4个参数缺一不可)
app.use((err, req, res, next) => {
  console.error('错误堆栈:', err.stack);
  const statusCode = err.status || 500;
  res.status(statusCode).json({
    code: statusCode,
    message: err.message || '服务器内部错误'
  });
});

注意:异步代码(Promise、async/await)中的错误无法自动捕获,必须手动 try/catch 后调用 next(err) 传递,否则会导致进程崩溃。

4. 内置中间件

Express 4.16.0 之后内置了高频中间件,无需额外 npm 安装:

表格

内置中间件 功能说明
express.static 托管静态资源(图片、CSS、JS、文件等)
express.json 解析 application/json 格式请求体,结果挂载到 req.body
express.urlencoded 解析 application/x-www-form-urlencoded 表单请求体
express.raw 解析二进制请求体,返回 Buffer 格式
express.text 解析纯文本格式请求体
常用示例

javascript

运行

复制代码
// 1. 静态资源服务:访问 /static/xxx 读取 public 目录下文件
app.use('/static', express.static('public'));

// 2. 解析 JSON 请求体(POST/PUT 接口必备)
app.use(express.json());

// 3. 解析表单提交;extended: false 用 querystring,true 用 qs 支持嵌套对象
app.use(express.urlencoded({ extended: false }));

// 测试:POST 提交数据可通过 req.body 获取
app.post('/api/data', (req, res) => {
  res.json({ received: req.body });
});

5. 第三方中间件

社区生态提供的功能中间件,是 Express 扩展性的核心来源,通过 npm 安装后引入使用。

高频第三方中间件
  1. morgan:HTTP 请求日志记录

    bash

    运行

    复制代码
    npm install morgan

    javascript

    运行

    复制代码
    const morgan = require('morgan');
    app.use(morgan('dev')); // dev:简洁彩色日志;combined:完整标准日志
  2. cors:跨域资源共享处理

    bash

    运行

    复制代码
    npm install cors

    javascript

    运行

    复制代码
    const cors = require('cors');
    // 全局开启跨域
    app.use(cors());
    // 精细化配置
    app.use(cors({ origin: 'https://example.com', credentials: true }));
  3. helmet:安全加固,自动设置 HTTP 安全响应头

    bash

    运行

    复制代码
    npm install helmet

    javascript

    运行

    复制代码
    const helmet = require('helmet');
    app.use(helmet()); // 自动启用 XSS 防护、HSTS 等十余项安全策略
  4. cookie-parser :解析 Cookie,结果挂载到 req.cookies

    bash

    运行

    复制代码
    npm install cookie-parser

    javascript

    运行

    复制代码
    const cookieParser = require('cookie-parser');
    app.use(cookieParser('签名密钥')); // 支持签名 Cookie 防篡改
  5. express-session:服务端会话管理

    bash

    运行

    复制代码
    npm install express-session

    javascript

    运行

    复制代码
    const session = require('express-session');
    app.use(session({
      secret: '加密密钥',
      resave: false,
      saveUninitialized: true,
      cookie: { maxAge: 24 * 60 * 60 * 1000 } // 会话有效期1天
    }));
  6. multer :处理 multipart/form-data 格式的文件上传


三、核心执行机制与 next () 用法

1. next () 的三种调用方式

表格

写法 作用
next() 正常移交控制权,执行下一个匹配的中间件
next('route') 跳过当前路由的剩余中间件,直接进入下一个同路径路由(仅对 app.METHOD() 生效)
next(err) 抛出错误,跳过后续所有普通中间件,直接进入错误处理中间件
next ('route') 示例

javascript

运行

复制代码
app.get('/test',
  (req, res, next) => {
    if (req.query.skip === '1') return next('route'); // 跳过后续中间件
    next();
  },
  (req, res) => res.send('执行了第二个中间件')
);

// 第二个同路径路由
app.get('/test', (req, res) => {
  res.send('进入了下一个路由处理');
});

2. 匹配与执行规则

  1. 顺序优先:严格按照代码定义顺序执行,先定义先匹配。
  2. 前缀匹配(use)app.use('/api', fn) 会匹配所有以 /api 开头的路径(/api/api/user/api/user/123)。
  3. 精确匹配(METHOD)app.get()app.post() 等方法级中间件,需同时匹配路径和 HTTP 方法。
  4. 响应终止原则 :一旦执行 res.send()res.json()res.end() 等结束响应的方法,后续中间件逻辑不再生效;若仍尝试发送响应,会抛出 Cannot set headers after they are sent to the client 错误。

四、自定义中间件实战

1. 接口响应耗时统计

javascript

运行

复制代码
app.use((req, res, next) => {
  const startTime = Date.now();
  // 监听响应完成事件,实现后置统计
  res.on('finish', () => {
    const duration = Date.now() - startTime;
    console.log(`${req.method} ${req.originalUrl} | ${res.statusCode} | ${duration}ms`);
  });
  next();
});

2. 登录权限校验中间件

javascript

运行

复制代码
// 抽离为独立模块,按需复用
const authMiddleware = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) {
    return res.status(401).json({ code: 401, message: '未携带登录凭证' });
  }
  // Token 校验逻辑...
  req.user = { id: 1, username: 'admin' }; // 校验通过后挂载用户信息
  next();
};

// 仅需要登录的接口使用
app.get('/user/profile', authMiddleware, (req, res) => {
  res.json({ user: req.user });
});

3. 异步中间件的正确写法

javascript

运行

复制代码
// 异步中间件必须捕获异常并传递给 next
const asyncMiddleware = (req, res, next) => {
  (async () => {
    try {
      await db.query('SELECT * FROM users');
      next();
    } catch (err) {
      next(err);
    }
  })();
};

五、最佳实践与常见坑点

1. 全局中间件推荐顺序

合理的顺序能避免很多异常问题,建议遵循以下排列:

  1. 安全类中间件(helmet、cors)
  2. 日志中间件(morgan)
  3. 请求体解析中间件(json、urlencoded)
  4. Cookie / Session 解析中间件
  5. 静态资源托管
  6. 业务路由
  7. 404 兜底处理
  8. 全局错误处理中间件

2. 常见坑点

  • 忘记调用 next():请求会一直挂起,最终超时无响应。
  • 错误处理中间件参数缺失:4 个参数缺一不可,少参数会被识别为普通中间件。
  • 异步错误未捕获 :async 函数、Promise 中的错误必须手动 try/catchnext(err)
  • 重复发送响应 :多个中间件中都调用 res.send() 会触发响应头重复设置错误。
  • 路径匹配混淆use 是前缀匹配,get/post 是精确路径匹配,使用时注意区分。

3. 工程化建议

大型项目中建议将中间件抽离为独立模块(如 middleware/logger.jsmiddleware/auth.js),按需引入,避免主文件臃肿;通用中间件统一在入口文件注册,业务中间件局部挂载。