跳到主要内容

Express 路由怎么讲:匹配机制、参数处理、模块拆分与 REST 设计

面试速答(30 秒版)

  • Express 路由的核心作用是:根据请求方法和路径,把请求分发到对应的处理函数
  • 路由不只是 app.get("/xxx") 这么简单,它背后包含 路径匹配、参数提取、中间件挂载、模块拆分
  • 面试里真正需要讲清楚的是:路由怎么组织、为什么要分层、怎样避免写成巨型路由文件
  • 对中大型项目,常见做法是 Router 分模块,再配合控制器、服务层和中间件解耦。
  • 一句话总结:Express 路由本质上是“请求分发规则 + 局部中间件链”的组合。

一、先建立最基础的理解

一个最小的 Express 路由:

app.get("/users/:id", (req, res) => {
res.json({ id: req.params.id });
});

这段代码背后干了三件事:

  1. 限定只处理 GET 请求。
  2. 只匹配 /users/:id 这种路径。
  3. 把路径参数解析到 req.params,然后执行 handler。

如果面试官继续问“路由是什么”,比起只说“定义接口地址”,更完整的表达是:

  • 路由负责把 HTTP 方法、URL 结构和业务处理函数关联起来。

二、路由匹配机制怎么理解

1. 匹配维度有两个

  • HTTP 方法
  • 路径模式

例如:

app.get("/users", listUsers);
app.post("/users", createUser);

虽然路径一样,但方法不同,所以是两条不同路由。

2. 路径参数会被提取

app.get("/orders/:orderId/items/:itemId", (req, res) => {
res.json(req.params);
});

访问 /orders/10/items/2 时:

req.params; // { orderId: "10", itemId: "2" }

3. 查询参数不属于路由路径本身

比如请求:

/users?page=2&size=20

这里:

  • /users 是路由匹配部分
  • pagesizereq.query

这也是面试常考点,很多人会把 paramsquery 混掉。


三、Express 路由为什么建议模块化

小项目里,直接在 app.js 写所有路由还勉强能看。

一旦接口多起来,就会出现这些问题:

  • 单文件几百行甚至上千行
  • 用户、订单、支付逻辑混在一起
  • 中间件重复挂载
  • 测试边界不清晰

所以更合理的方式是按领域拆 Router

例如:

const express = require("express");
const userRouter = express.Router();

userRouter.get("/", listUsers);
userRouter.get("/:id", getUserById);
userRouter.post("/", createUser);

module.exports = userRouter;

主应用再挂载:

app.use("/users", userRouter);

这样带来的好处:

  • 路由职责清晰
  • 中间件可以局部挂载
  • 更适合团队协作

四、路由和中间件怎么配合

路由不是只能挂一个 handler。

router.get(
"/profile",
authMiddleware,
permissionMiddleware("user:read"),
getProfile
);

这表示在进入最终业务处理前,先过一段局部中间件链。

你可以把它理解成:

这种设计的核心价值是:

  • 公共能力复用,但作用域只限制在相关路由组。

五、路由设计要不要遵循 REST

面试里常会带到这个问题。

更稳的回答不是“必须 RESTful”,而是:

  • REST 是常见约定,目的是让 URL 和方法语义更统一。
  • 但工程里优先级更高的是可维护性、一致性和团队规范。

典型例子:

操作更常见的 REST 风格
查询用户列表GET /users
查询用户详情GET /users/:id
创建用户POST /users
更新用户PUT /users/:idPATCH /users/:id
删除用户DELETE /users/:id

面试补充点:

  • PUT 更偏整体替换。
  • PATCH 更偏局部更新。
  • 不是所有业务动作都能优雅映射成资源操作,比如“审核通过”“发券”“重试任务”这种命令式动作。

六、一个更像工程项目的路由分层

在中大型 Express 服务里,常见结构不是“路由里把所有逻辑写完”,而是拆成:

  1. router:只负责 URL 到 handler 的映射。
  2. controller:处理请求输入输出,协调服务。
  3. service:承载业务逻辑。
  4. repository/model:访问数据库。

例如:

router -> controller -> service -> db

为什么这样拆:

  • 路由层应该尽量薄。
  • 否则后面所有业务都会黏在 Express API 上,不利于测试和复用。

七、参数校验应该放哪

这是很高频的追问。

推荐思路:

  • 路由负责声明入口。
  • 参数校验放在路由级中间件或 controller 入口。
  • service 假设拿到的是“基本合法的数据”。

不要把校验散落到每一层,否则会出现:

  • 重复校验
  • 状态码不一致
  • 错误信息风格混乱

八、404 路由和兜底怎么处理

如果没有命中任何路由,通常要有统一 404 处理:

app.use((req, res) => {
res.status(404).json({ message: "Not Found" });
});

它要放在所有路由之后。

之后再接错误处理中间件。

这背后的思路是:

  • 先判断有没有匹配路由。
  • 没有命中时返回 404。
  • 如果命中了但执行报错,再由错误处理中间件兜底。

典型题与标准答法

1. Express 路由是什么?

  • 是基于 HTTP 方法和路径规则的请求分发机制。
  • 匹配成功后执行对应的处理函数和局部中间件链。

2. req.paramsreq.query 有什么区别?

  • params 来自路径占位符。
  • query 来自 URL 查询字符串。

3. 为什么要用 express.Router()

  • 为了模块化路由。
  • 便于按业务域拆分、局部挂载中间件和降低主入口复杂度。

4. 路由层为什么不能太重?

  • 因为它应该只关心请求分发和输入输出边界。
  • 业务逻辑都堆在路由层会导致耦合严重、测试困难。

常见追问

1. 一个路由文件该怎么拆?

  • 常见是按资源域拆,比如 userorderauth
  • 不建议按 HTTP 方法拆。

2. URL 一定要完全 RESTful 吗?

  • 不一定。
  • 但至少要保持风格统一,避免一部分像资源,一部分像 RPC,最后没人看得懂。

3. 版本路由怎么做?

  • 可以挂前缀,比如 /api/v1/users
  • 也可以在网关层做版本隔离。

易错点

  • paramsquery 混为一谈。
  • 路由文件里直接写大量数据库和业务逻辑。
  • 所有接口都堆在一个 app.js
  • 没有统一 404 和错误兜底。
  • REST 只背 URL 形式,不理解资源语义和工程妥协。

速记要点

  • 路由 = 方法匹配 + 路径匹配 + 处理链分发。
  • params 来自路径,query 来自查询串。
  • Router 的价值是模块化和局部中间件。
  • 路由层要薄,业务逻辑下沉到 controller / service。
  • 404 处理放路由后,错误处理中间件放最后。