Express 中间件与 Router 完整解析
一、应用级全局中间件
app.use():注册应用级中间件。
路由模块(Router)、express.json()、(req, res, next) => {},在 Express 眼里都是中间件。
// 挂载 todo 路由模块
app.use("/api/todos", todoRouter);
含义是:把 todoRouter 挂到 /api/todos 下;请求进来时先匹配路径前缀,再交给 router 里的路由。
二、app.use 怎么区分参数?
Express 的规则很简单:
app.use([path,] ...middleware)
// 伪代码,表达 Express 的解析逻辑
app.use = function (...args) {
let path = "/"; // 默认:匹配所有路径
let middlewares = args;
if (typeof args[0] === "string" || isPathPattern(args[0])) {
path = args[0]; // 第一个是路径
middlewares = args.slice(1); // 后面才是中间件
} else if (typeof args[0] === "function") {
// 第一个是函数 → 没有写路径,用默认 "/"
path = "/";
middlewares = args;
}
// 把 middlewares 注册到 path 上
};
| 参数 | 是什么 |
|---|---|
| 字符串 / 正则 / 数组 | 挂载路径(mount path) |
| 函数 | 中间件(含 Router、错误处理中间件) |
Router 本质上也是函数,签名是:
(req, res, next) => void
所以能传给 app.use()。
三、常见用法
1. 只传一个函数 ------ 全局中间件
app.use((req, res, next) => {
console.log("请求来了");
next();
});
所有请求都会经过,没有路径限制。
你 index.js 第 8 行就是这种。
2. 只传一个「内置中间件函数」
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
同样是全局中间件,express.json() 返回的就是一个:
(req, res, next) => {}
3. 路径 + 路由中间件
app.use("/api/todos", todoRouter);
只有 URL 以 /api/todos 开头才会进入 todoRouter。
4. 路径 + 多个中间件(可链式写)
app.use("/api", authMiddleware, logMiddleware, someRouter);
5. 错误处理中间件(4 个参数)
app.use((error, req, res, next) => {
res.status(500).json({ error: error.message });
});
Express 靠参数个数是 4 个来识别错误中间件。
6. 只传 Router,不写路径
app.use(todoRouter); // 等价于挂载在 "/"
一共多少种?
如果按「语法形式」数,常见就 6 类:
-
只传函数
-
只传内置中间件
-
路径 + Router
-
路径 + 多个中间件
-
错误处理中间件
-
只传 Router
如果按「本质」数,其实就 2 种:
-
普通中间件
-
错误处理中间件
Router、express.json()、404 兜底,都属于普通中间件,只是职责不同。
四、app.get 和 router.get 的区别
两者语法一样,差别在「注册在哪、路径怎么算」。
| app.get | router.get | |
|---|---|---|
| 注册对象 | 主应用 app | 子路由 router |
| 路径 | 完整路径,如 /health |
相对路径,相对挂载点 |
| 匹配方法 | 只匹配 GET | 只匹配 GET |
| 典型用途 | 少量独立路由 | 一组相关路由拆到单独文件 |
小结
app.use 没有「路由模块」这个特殊类型------Router 就是中间件,只是里面又注册了 router.get/post/...。
区分参数:
-
第一个是路径(可选)
-
后面全是中间件函数(可多个)
app.get vs router.get:
-
app.get写绝对路径 -
router.get写相对路径 -
靠
app.use(prefix, router)拼成完整 URL
五、router.use 详解
router.use 和 app.use 语法、解析规则完全一样,区别只是作用范围:
router.use([path,] ...middleware)
第一个 path 同样是可选;
不传时默认 /,但这里的 / 是相对于 Router 被挂载的位置。
六、四种常见用法
用法 1:Router 级全局中间件
const router = Router();
router.use((req, res, next) => {
console.log("进入 todo 模块", req.path);
next();
});
router.get("/", ...);
router.get("/:id", ...);
配合:
app.use("/api/todos", router);
效果:
只有访问 /api/todos/* 时才会进这个中间件;
/health 不会。
用法 2:Router 内带子路径
router.use("/archive", archiveRouter);
若:
app.use("/api/todos", router);
则完整路径是:
/api/todos/archive/*
用法 3:某个子路径 + 中间件
router.use("/:id", validateId, loadTodo);
只对:
/api/todos/123
这类带 :id 的请求执行 validateId 和 loadTodo,
/api/todos
(列表)不会走。
用法 4:错误处理
router.use((err, req, res, next) => {
res.status(500).json({ error: err.message });
});
和 app.use 一样,4 个参数表示错误中间件,但只处理这个 router 链路上抛出的错误。
总结
app.use 本质
app.use([path,] ...middleware);
-
path 可选
-
middleware 可多个
-
Router 本质也是中间件
Router 本质
const router = Router();
Router 是一个中间件容器:
router.get(...)
router.post(...)
router.use(...)
最后通过:
app.use("/prefix", router);
挂载到主应用。
路径拼接规则
app.use("/api/todos", router);
router.get("/");
最终:
GET /api/todos
app.use("/api/todos", router);
router.get("/:id");
最终:
GET /api/todos/:id
错误中间件识别规则
(err, req, res, next)
只要参数个数为 4,Express 就会把它当成错误处理中间件。