人人都会AI编程

中间件核心机制、路由系统、错误处理

更新时间:2026-07-11

Express 的核心设计基于三个紧密配合的模块:中间件机制路由系统错误处理。它们是构建 Express 应用的骨架,只要理解了这三者,就能读懂绝大多数的 Express 代码,也能写出结构清晰的 Web 服务。

中间件核心机制

在 Express 中,中间件就是一个函数,它在请求(request)和响应(response)对象之间按顺序执行。每个中间件都可以对请求或响应对象进行加工,或者选择将请求传递给下一个中间件,也可以直接结束响应(如返回数据或重定向),从而中断链条。

一个标准的中间件函数签名为:

function middleware(req, res, next) {
  // 处理逻辑
  next(); // 传递给下一个中间件
}

其中 next 是一个函数,调用它表示“我处理完了,交给下一位”。如果不调用 next(),请求就会悬挂在当前位置,客户端会一直等待,直到超时。如果直接调用 res.send()res.json() 结束了响应,则不需要(也不应该)再调用 next

Express 的中间件是线性串行的,与传统 Koa 的洋葱模型不同。Express 没有内置的“回溯”机制——中间件执行完毕后,控制权不会自动回到上一个中间件。虽然可以通过一些惯用方法(如在响应结束后执行 next)实现类似效果,但通常这不是必要的,反而不如直接使用 res.send 终结请求来得清晰。

一个典型的 Express 应用其实就是一连串中间件的组合:

const express = require('express');
const app = express();

// 应用级别的中间件:解析 JSON 请求体
app.use(express.json());

// 自定义日志中间件
app.use((req, res, next) => {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
  next();
});

// 处理 /users 路径下所有请求的中间件
app.use('/users', usersRouter);

// 兜底中间件:404
app.use((req, res) => {
  res.status(404).json({ error: 'Not Found' });
});

app.listen(3000);

中间件可以挂载到不同类型的“路径”上,Express 会按照代码书写的顺序和路径匹配情况依次尝试执行。主要分为以下几种:

  • 应用级中间件:通过 app.use()app.METHOD() 挂载,作用于整个应用或指定前缀的路径。
  • 路由器级中间件:通过 express.Router() 创建,作用范围是当前路由器,写法与 app 一样。
  • 错误处理中间件:后面单独说明,通过四个参数签名定义。
  • 内置中间件:Express 从 4.x 开始内置了 express.json()(解析 JSON 请求体)和 express.urlencoded()(解析 URL 编码的表单数据),代替了早期需要 body-parser 的方式。
  • 第三方中间件:如 corshelmetmorgan 等,直接通过 app.use() 引入。

在实际开发中,通常会把中间件分为请求预处理(解析、日志、跨域)、业务路由、和错误处理三层,保证代码组织和职责清晰。

路由系统

Express 的路由系统本质上是中间件的一种特殊形式:根据 HTTP 方法和路径来筛选请求,并执行对应的处理函数。它内置在 appRouter 对象中,使用方式非常直观。

基本路由定义:

// 定义 GET /api/users 的处理
app.get('/api/users', (req, res) => {
  res.json([{ id: 1, name: 'Alice' }]);
});

// POST 方法
app.post('/api/users', (req, res) => {
  // 创建用户
  res.status(201).json({ id: 2, name: req.body.name });
});

// 动态路由参数
app.get('/api/users/:id', (req, res) => {
  const { id } = req.params; // req.params 包含路径参数
  res.json({ id, name: 'User ' + id });
});

// 链式处理一个路径上的多个方法
app.route('/api/users')
  .get((req, res) => { /* 查询列表 */ })
  .post((req, res) => { /* 新增用户 */ });

当应用规模扩大时,把所有路由都堆在 app.js 中会变得难以维护。这时使用 express.Router() 可以将路由按功能模块拆分:

// routes/users.js
const router = require('express').Router();

router.get('/', (req, res) => {
  res.json({ list: [] });
});

router.get('/:id', (req, res) => {
  res.json({ id: req.params.id });
});

module.exports = router;

// app.js
const usersRouter = require('./routes/users');
app.use('/api/users', usersRouter);

这种方式将路由组织成资源树,每个模块内部的路径都是相对路径,模块间不会互相干扰。路由级中间件也可以搭配使用,例如为某组路由添加统一的身份校验:

// authMiddleware 验证 token
router.use(authMiddleware);

router.get('/profile', (req, res) => {
  // 这里已经经过 authMiddleware 鉴权
  res.json(req.user);
});

路由匹配遵循“先定义先匹配”原则。一旦一个路由处理函数调用了 res.send 结束响应,后续的路由就不会再被执行。因此要注意路由顺序,尤其是动态路由可能“吞掉”后面的静态路由名称。例如,如果将 /:id 放在 /new 之前,那么 /new 会被当作一个 id 参数处理。解决办法就是把静态路径写在前面。

错误处理

Express 的错误处理同样基于中间件,只是它的函数签名不同——必须包含四个参数:(err, req, res, next)。Express 会将其识别为错误处理中间件,只有在发生错误时才会调用它。

错误的发生方式主要包括:

  • 抛出异常:同步代码中直接 throw new Error('...'),Express 会自动捕获并交给错误处理中间件。
  • 调用 next(err):在异步操作中捕获到错误后,通过 next(err) 显式传递给错误处理中间件。这是异步场景下推荐的错误传递方式。
  • Promise 拒绝未捕获:如果在 async 路由中直接 throw 而没有 try/catch,Express 从 5.x 版本开始会捕获,但 4.x 中可能不会。实际开发中建议包裹异步路由或使用错误捕获工具函数。

通常会在所有业务路由之后,定义错误处理中间件:

// 业务路由
app.use('/api', apiRouter);

// 404 处理(也算一种错误)
app.use((req, res, next) => {
  res.status(404).json({ error: 'Not Found' });
});

// 统一错误处理中间件
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.status || 500).json({
    error: err.message || 'Internal Server Error'
  });
});

注意:这里的 err 对象可以从上游通过 next(err) 传入,并且可以扩展额外属性(如 err.status)供错误处理中间件使用。常见做法是封装一个自定义错误类,携带 HTTP 状态码和消息。

在实际业务路由中处理异步错误时,可以这样:

app.get('/users/:id', async (req, res, next) => {
  try {
    const user = await findUser(req.params.id);
    if (!user) {
      const err = new Error('User not found');
      err.status = 404;
      throw err; // 被 catch 捕获
    }
    res.json(user);
  } catch (err) {
    next(err); // 传递给错误处理中间件
  }
});

为了减少模板代码,可以写一个高阶函数自动包装异步路由:

function asyncHandler(fn) {
  return (req, res, next) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
}

app.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await findUser(req.params.id);
  if (!user) {
    const err = new Error('User not found');
    err.status = 404;
    throw err;
  }
  res.json(user);
}));

这样业务代码中就不需要手动 try/catch 和 next(err),代码更加专注。

Express 的错误处理中间件可以有多个,按顺序链式执行。如果一个错误处理中间件调用了 next(err)(参数非空),则交给下一个错误处理中间件继续处理;如果调用 next() 无参数,则跳出错误处理链,进入正常中间件流程(但一般不这么做,容易造成混乱)。

从 Express 4.x 升级到 5.x 时,要注意 Promise 拒绝和同步抛错的捕获行为的改进,以及错误处理中间件用法的一致性。但核心设计思想不变:通过中间件链来处理错误,而非在每个路由里分别处理,从而实现集中化的错误日志记录和统一响应格式。


小结:Express 的中间件机制是它的灵魂,通过 (req, res, next) 串联起整个请求生命周期;路由系统将 URL 和方法映射到具体的处理逻辑;错误处理给了开发者一个兜底的网,保证异常不会直接导致进程崩溃并给客户端返回一致的错误形状。掌握这三者,就能得心应手地用 Express 构建清晰、健壮的 Web 服务。