在 Express 中,req.params 保存路由路径中命名段的值(/users/:id 对应 req.params.id),req.query 保存 ? 后面的查询字符串(/search?q=node 对应 req.query.q),req.body 保存请求体中发送的数据。每个请求都会填充 req.params 和 req.query,但在 app.use(express.json()) 这样的请求体解析器读取它之前,req.body 一直是 undefined。params 和 query 中的值永远是字符串。
第一个请求返回 {"params":{"userId":"7"},"query":{"sort":"new","limit":"2"}}。注意引号:7 和 2 都是字符串,所以在做运算或与数字比较之前,要先用 Number() 转换。现在把方法改成 POST,路径保持 /users/7/posts,发送请求体 {"title": "Hello", "draft": true}:响应是 201 和 "body":{"title":"Hello","draft":true},其中 true 是真正的布尔值,因为它来自 JSON。
req.params、req.query 与 req.body 的区别
req.params | req.query | req.body | |
|---|---|---|---|
| 来源 | 路径,/users/:id | 查询字符串,?sort=new | 请求体 |
| 示例 URL 或请求体 | /users/7 | /users?sort=new | {"name": "Ada"} |
| 值 | { id: '7' } | { sort: 'new' } | { name: 'Ada' } |
| 类型 | 字符串 | 字符串和字符串数组 | JSON 里是什么就是什么 |
| 需要的设置 | 路由中的 :name | 无 | app.use(express.json()) |
| 典型用途 | 指定哪个资源 | 筛选、排序、分页 | 要创建或更新的数据 |
一个路由可以有多个参数,每个名称都会成为 req.params 的一个键:/teams/:teamId/members/:memberId 会同时给出两个 id。连字符或点号也能分隔参数,所以 /flights/:from-:to 会把 /flights/LAX-SFO 变成 { from: 'LAX', to: 'SFO' }。
重复和缺失的 req.query 键
出现两次的键会变成数组,缺失的键是 undefined,所以要给选项设一个默认值:
响应是 {"page":1,"tags":["red","sale"],"raw":{"tag":["red","sale"]}}。发送 GET /products?tag=red&page=3,tags 仍然是数组 ["red"],因为 [].concat() 会把单个字符串或数组都变成数组。Express 5 用 Node 的简单解析器解析查询字符串,所以 ?filter[size]=m 得到的键是 "filter[size]",而不是一个嵌套对象;Express 4 则会把它解析成嵌套对象。
req.body 需要 express.json()
Node 以字节流的形式交付请求体。express.json() 会读取并解析它,然后设置 req.body,但只在请求带有 Content-Type: application/json 时才这样做。HTML 表单发送的是 application/x-www-form-urlencoded,需要改用 express.urlencoded()。
发送 POST /signup,请求体为 {"email": "ada@example.com"},你会得到 201 和 {"email":"ada@example.com","name":"anonymous"}。发送空请求体则得到 400。?? {} 很重要:没有它,任何没有 JSON 请求体的请求都会在解构 undefined 时抛出 TypeError。要真正校验结构和类型,请使用 zod 这类 schema 库。
req.headers 与 req.get()
req.headers 是一个包含所有请求头的对象,名称都是小写。req.get(name) 读取单个请求头并且不区分大小写,所以 req.get('Content-Type') 和 req.get('content-type') 返回同一个值。还有几个你会用到的属性:
path 是 /whoami,originalUrl 是 /whoami?debug=1。在请求栏中添加请求头 Authorization: Bearer abc 后再发送一次:token 变成 "Bearer abc"。在用 app.use('/api', router) 挂载的路由器里,req.path 会去掉 /api 前缀,而 req.originalUrl 会保留它(挂载的内容见 Express 路由)。
常见错误
在 GET 请求中读取 req.body。 浏览器和大多数客户端在 GET 请求中都不发送请求体。把 GET 的选项放进查询字符串。
用真值判断查询参数。 ?admin=false 得到的是字符串 'false',它是真值,所以 if (req.query.admin) 会通过。应该比较字符串:req.query.admin === 'true'。
发送 JSON 却没有设置请求头。 以 text/plain 提交 JSON 的客户端会得到 req.body === undefined。在客户端设置 Content-Type: application/json。
常见问题
在 Express 中如何获取客户端的 IP 地址?
读取 req.ip。在代理或负载均衡器后面,它是代理的地址,除非你设置了 app.set('trust proxy', 1),这样 Express 就会改为读取 X-Forwarded-For 请求头。
在 Express 中如何获取请求的完整 URL?
req.originalUrl 是客户端发来的路径加查询字符串。用 `${req.protocol}://${req.get('host')}${req.originalUrl}` 拼出绝对 URL。
在 Express 中如何处理文件上传?
文件上传以 multipart/form-data 格式到达,express.json() 和 express.urlencoded() 都不会解析它。使用 multer 这类包,它会把文件放进 req.file 或 req.files,把其他字段放进 req.body。
如何从请求中读取 cookie?
原始请求头在 req.get('cookie') 中。要在 req.cookies 中得到一个对象,请安装 cookie-parser 包并添加 app.use(cookieParser())。