Skip to content
Express.js 关键 API:内置中间件与 Router
内置中间件是什么
从 Express 4.16.0 开始,原先需要单独安装的四个中间件——express.json()、express.urlencoded()、express.static() 和 express.Router()——被直接内置到了框架中 [1]。前两个负责解析请求体并将结果挂载到 req.body,第三个提供静态文件服务,第四个用于创建模块化的路由实例。
在更早的版本中,开发者通常需要额外安装 body-parser 并手动 app.use(bodyParser.json())。现在这些步骤都可以用 express.json() 等内置方法替代,减少了外部依赖和样板代码 [2]。
express.json():解析 JSON 请求体
基本用法
挂载一次 express.json() 后,任何 Content-Type 为 application/json 的 POST/PUT/PATCH 请求都会将请求体解析为 JavaScript 对象,放到 req.body 上。
js
const express = require('express');
const app = express();
app.use(express.json());
app.post('/profile', (req, res) => {
console.dir(req.body); // { name: 'Alice', age: 25 }
res.json(req.body);
});
app.listen(3000);发起带 JSON 请求体的 POST 请求:
bash
curl -X POST http://localhost:3000/profile \
-H "Content-Type: application/json" \
-d '{"name":"Alice","age":25}'服务端控制台会打印解析后的对象,客户端则收到相同的 JSON 响应。如果请求没有请求体或 Content-Type 不匹配,req.body 会是 undefined,中间件不会报错,只是不做解析。
配置选项与边界行为
express.json() 接受一个选项对象,其中几个参数直接影响解析行为 [2]。
limit
默认值为 100kb。超过该大小的 JSON 请求体会被拒绝,中间件通过 next(err) 传递一个类型为 entity.too.large 的错误。需要接收更大请求体时,可以调整上限:
js
app.use(express.json({ limit: '1mb' }));参数接受的数字字符串(如 '1mb'、'500kb')由 bytes 库解析。
strict
默认 true,只接受数组或对象作为顶层 JSON 值。如果 API 设计为接收原始字符串或数字(例如 "hello"、123),则必须显式关闭该选项:
js
app.use(express.json({ strict: false }));此时 JSON.parse 能解析的任何内容都会被接受,req.body 可能为字符串、数字、布尔值等。
type
默认值为 application/json。可指定自定义 MIME 类型、文件扩展名或通配符。例如,让客户端发送 application/vnd.api+json 时也能被解析:
js
app.use(express.json({ type: 'application/vnd.api+json' }));如果传入一个函数,该函数接收原始 req 对象,返回 truthy 值时就会尝试解析。
verify
可选函数,签名为 (req, res, buf, encoding)。在解析动作之前调用,可以获取原始请求体的 Buffer。若函数内部抛出错误,解析会中止。可用于检查签名或做早期校验。
reviver
直接传给 JSON.parse 的第二个参数,用法与原生 JSON.parse 一致。
数据安全req.body 的形状完全由请求方决定,使用前需要做校验。类似 req.body.foo.toString() 的写法可能在 foo 不存在、不是字符串或 toString 属性被篡改时抛出异常 [4]。也就是说,req.body 上的所有属性都是不可信的。
express.urlencoded():处理 URL 编码表单
基本用法
浏览器表单默认提交格式为 application/x-www-form-urlencoded。若未设置 enctype,表单数据会被编码成 key=value&key2=value2 的字符串放在请求体中。使用 express.urlencoded() 可以解析这类请求:
js
app.use(express.urlencoded({ extended: true }));
app.post('/login', (req, res) => {
console.log(req.body.username);
console.log(req.body.password);
res.send('OK');
});提交表单:
bash
curl -X POST http://localhost:3000/login \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=alice&password=secret"req.body 将得到 { username: 'alice', password: 'secret' }。
extended 选项与其他配置
该中间件同样支持 limit、type、verify 等选项,行为与 express.json() 类似。特有的选项是 extended。
extended 决定了底层使用哪个解析库。
extended: true(多数情况下推荐)使用qs库,支持嵌套对象和数组。例如user[name]=Alice&user[age]=25会被解析为{ user: { name: 'Alice', age: '25' } },items[]=1&items[]=2解析为{ items: ['1', '2'] }。extended: false使用 Node.js 内置的querystring模块,不支持嵌套,只生成简单键值对。上述嵌套写法会被解析成带方括号的扁平 key。
新项目绝大多数场景都选择 extended: true,除非有特殊的兼容性需求,否则不必设为 false。
express.static():提供静态文件
基本用法
express.static() 接收一个本地目录路径作为参数,将目录下的文件直接通过 HTTP 返回,无需手动配置路由。其内部依赖 serve-static 模块 [6]。
假设项目目录下有 public 文件夹:
public/
index.html
style.css
logo.png一行代码即可启动静态服务:
js
app.use(express.static('public'));此时访问 http://localhost:3000/index.html 会返回 public/index.html。目录下有子目录时,路径映射规则与文件系统保持一致。默认还会尝试提供 index.html 作为目录的默认文件。
虚拟路径前缀与挂载顺序
可以给静态文件指定一个虚拟前缀,使 URL 与文件系统路径解耦:
js
app.use('/static', express.static('public'));此时 /static/style.css 映射到 public/style.css。请求路径中的 /static 前缀被剥离,剩余部分用于在 public 目录中查找文件。
挂载顺序会影响请求处理流。 如果在挂载 express.static 之后还定义了其他路由,对静态文件的请求会先命中静态中间件。文件存在时,express.static 直接发送文件并结束响应;文件不存在时,它调用 next() 将请求交给后续路由。
这意味着如果后端 API 和静态文件路径有重叠,就需要留意顺序。例如:
js
app.use(express.static('public'));
app.get('/hello', (req, res) => res.send('Hello'));若 public 目录下没有 hello 文件,GET 请求才会落到自定义路由;一旦 public/hello 文件存在,自定义路由不再触发。通常的保守做法是给静态资源指定独立前缀(如 /static),或者将 API 路由放在静态中间件之前,确保根路径下的文件不与 API 路径冲突。
express.Router():模块化路由
基本用法与挂载
当路由数量增多时,把所有 app.get()、app.post() 集中在入口文件会让文件迅速膨胀。express.Router() 创建一个“迷你应用”实例,可以像 app 一样在上面定义路由,最后作为中间件挂载到主应用上 [8]。
创建一个 Router 模块:
js
// routes/users.js
const router = require('express').Router();
router.get('/', (req, res) => {
res.send('user list');
});
router.get('/:id', (req, res) => {
res.send(`user ${req.params.id}`);
});
module.exports = router;在主应用中挂载:
js
const usersRouter = require('./routes/users');
app.use('/users', usersRouter);路径组合如下:
GET /users匹配router.get('/')GET /users/42匹配router.get('/:id'),req.params.id为'42'
Router 可以嵌套多层,也可以在一个 Router 内挂载另一个 Router,完全按照应用的层级结构组织代码。每个 Router 实例还能独立使用中间件,比如统一添加权限验证,只作用于这一挂载分支。
注意点
挂载顺序express.static 的路径覆盖问题已在上文说明。另一个容易忽略的点是 json 和 urlencoded 的挂载位置:如果放在某些中间件之后,那些中间件中访问 req.body 时会得到 undefined。因为 Express 的中间件按顺序执行,只有经过解析中间件后 req.body 才会有值。因此,这两个解析中间件通常应挂载在应用的最前面。
内存占用limit 配置如果被调得很大(例如 '50mb'),且请求量不小,可能快速消耗 Node.js 进程内存。尤其在同步解析 JSON 时,大请求体会一次性加载到内存再解析,没有流式能力。应根据实际业务设定合理的上限。
数据安全
解析出的 req.body 是不可信的数据容器。在存入数据库、写入文件或执行任何有副作用的操作前,应当显式地对字段做存在性、类型和取值范围校验。
版本可用性express.json 和 express.urlencoded 仅在 Express 4.16.0 及以上版本可用。如果还在维护更早版本的项目,这些方法是不可用的 [7]。
后续内容
本篇覆盖了四个内置中间件:
express.json()和express.urlencoded()解决请求体解析,将原始的 JSON 或表单字符串转换为req.body对象,并介绍了关键配置项与边界行为。express.static()将指定目录变为静态文件服务,虚拟路径前缀让 URL 结构与文件结构解耦,挂载顺序决定了路由和文件命中的优先级。express.Router()提供了路由拆分方式,每个Router实例都可携带自己的路由和中间件,最后挂载到主应用的路径上。
下一篇将进入 Express 的错误处理与异步异常捕获,包括同步/异步错误、next(err) 的行为以及统一错误处理中间件的编写方式。
