《Express 初学者笔记:再也不怕搞混 req 和 res 了》

43 阅读5分钟

前言

hello,同学们好👋。

刚开始用express搭建Web服务器时,大家是不是遇到了跟小凹一样的问题,还不是很了解两个对象:req(请求)和 res(响应)。路由写了一大堆,结果不是 req.body 读不出来,就是响应发了一半报错。
其实它们没那么复杂。花 10 分钟看完这篇文章,你会对这两个对象了如指掌。

本文基于 Express 4.x,Node.js 环境。所有代码均可直接运行验证。

一、Request 对象 —— 客户端发来了什么?

每收到一个请求,Express 都会把请求的所有信息打包成一个 req 对象,作为第一个参数传给路由处理函数。

app.get('/some-path', (req, res) => {
  // 所有请求相关的操作都通过 req 来获取
});

下面我们按使用频率,分组来看它的属性和方法。

1. 获取路由参数(req.params

在路由路径中用 : 定义的动态部分,会收集在 req.params 里。

// 路由:/users/:userId/books/:bookId
app.get('/users/:userId/books/:bookId', (req, res) => {
  console.log(req.params);
  // 输出:{ userId: '123', bookId: '456' }
});

2. 获取查询字符串(req.query

问号 ? 后面的参数,Express 会自动帮你转成对象。

// 请求:/search?keyword=express&page=2
app.get('/search', (req, res) => {
  console.log(req.query.keyword); // 'express'
  console.log(req.query.page);    // '2'(注意:是字符串,不是数字)
});

3. 获取请求体(req.body)—— 重点

POST、PUT 请求提交的数据(JSON 或表单)都存在这里。但是 Express 默认不会解析请求体,必须手动挂载中间件

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

// 这两行中间件至关重要,缺一个都可能读不到数据
app.use(express.json());             // 解析 application/json
app.use(express.urlencoded({ extended: true })); // 解析表单数据

app.post('/login', (req, res) => {
  console.log(req.body.username);
  console.log(req.body.password);
});

⚠️ 新手最常踩的坑req.body 是 undefined,99% 是因为忘了加上面两行中间件。

4. 获取请求头(req.headers 和 req.get()

req.headers 是原始对象,键名统一为小写。推荐用 req.get() 方法,大小写不敏感。

app.get('/', (req, res) => {
  // 两种方式都可以
  const userAgent1 = req.headers['user-agent'];
  const userAgent2 = req.get('User-Agent');   // 推荐
});

5. 获取 Cookie(需配合中间件)

Express 默认不解析 Cookie,需要安装 cookie-parser

npm install cookie-parser
const cookieParser = require('cookie-parser');
app.use(cookieParser());           // 普通 cookie
// app.use(cookieParser('秘钥'));  // 带签名的 cookie

app.get('/', (req, res) => {
  console.log(req.cookies);        // { theme: 'dark', sessionId: 'abc' }
});

6. 其他常用属性速查

属性说明示例值
req.method请求方法'GET''POST'
req.url完整路径(含查询串)'/users?sort=asc'
req.path不含查询串的路径'/users'
req.hostname主机名'localhost'
req.ip客户端 IP'::1' 或 '192.168.1.1'
req.protocol协议'http'
req.secure是否为 HTTPStrue / false
req.xhr是否为 AJAX 请求true / false

7. 常用方法

  • req.accepts(types) :检查客户端能接受什么格式。
if (req.accepts('json')) {
  res.json({ data: '...' });
} else {
  res.send('...');
}
  • req.is(type) :检查请求的 Content-Type
if (req.is('multipart/form-data')) {
  // 处理文件上传
}

二、Response 对象 —— 如何回复客户端?

res 对象代表服务器将要发给客户端的响应。任何一个路由处理函数都必须通过 res 来结束请求,否则请求会一直挂起。

1. 发送数据的三驾马车

res.send([body]) —— 万能方法

根据传入数据的类型,自动设置合适的 Content-Type

app.get('/test', (req, res) => {
  // 字符串 → Content-Type: text/html
  res.send('<h1>Hello World</h1>');

  // 对象或数组 → Content-Type: application/json
  res.send({ name: 'Express', version: 4 });

  // Buffer → 触发文件下载
  res.send(Buffer.from('hello'));
});
res.json([body]) —— 明确返回 JSON

和 res.send(对象) 几乎一样,但语义更清晰。

app.get('/api/user', (req, res) => {
  res.json({ id: 1, name: 'Alice' });
});
res.end([data]) —— 底层结束方法

一般不直接使用,除非在处理原始流或 Buffer 时。

2. 文件操作

res.download(path [, filename]) —— 提示浏览器下载
app.get('/download', (req, res) => {
  res.download('/path/to/report.pdf', '2024-report.pdf');
});
res.sendFile(path [, options]) —— 在浏览器中展示

适合图片、PDF 预览等。

app.get('/logo', (req, res) => {
  res.sendFile('/public/logo.png', { root: __dirname });
});

3. 重定向

app.get('/old-page', (req, res) => {
  res.redirect(301, '/new-page');  // 永久重定向
  // 或 res.redirect('/new-page') 默认 302
});

4. 状态码与响应头

app.post('/login', (req, res) => {
  if (!req.body.password) {
    // 链式调用,先设状态码,再发数据
    res.status(400).json({ error: '密码不能为空' });
  }
});
// 设置单个响应头
res.set('Content-Type', 'text/plain');

// 批量设置
res.set({
  'X-Custom-Header': 'value',
  'Cache-Control': 'no-cache'
});

// 快捷设置 Content-Type
res.type('json');   // → 'application/json'

5. Cookie 操作

设置 Cookie 不需要额外中间件,直接用 res.cookie()

app.get('/set-theme', (req, res) => {
  res.cookie('theme', 'dark', {
    maxAge: 900000,     // 有效期(毫秒)
    httpOnly: true,     // 防止客户端 JS 读取(防 XSS)
    secure: true,       // 仅 HTTPS 下发送
    signed: true        // 需要配合 cookie-parser 的 secret
  });
  res.send('主题已设置为深色');
});

// 删除 Cookie
res.clearCookie('theme');

6. 一个重要属性:res.headersSent

用于检查响应头是否已经发送。可以避免重复发送响应头导致报错。

app.get('/check', (req, res) => {
  console.log(res.headersSent); // false
  res.send('ok');
  console.log(res.headersSent); // true
});

⚠️ 致命错误:响应一旦发送(调用了 sendjsonredirect 等方法),就不能再设置状态码或响应头,否则 Node.js 会抛出:

Error: Cannot set headers after they are sent to the client

三、总结速查表

Request 常用速查

我想获取什么用这个
路由参数 /user/5req.params.id
查询参数 ?sort=ascreq.query.sort
POST JSON / 表单数据req.body.field
请求头req.get('Authorization')
Cookiereq.cookies.name(需中间件)
请求方法req.method
客户端 IPreq.ip

Response 常用速查

我想做什么用这个方法
返回 HTML / 文本res.send('...')
返回 JSONres.json({...})
设置 HTTP 状态码res.status(404).send('Not found')
浏览器下载文件res.download('./file.pdf')
重定向res.redirect('/new-url')
设置响应头res.set('X-Header', 'value')
设置 Cookieres.cookie('key', 'value')
删除 Cookieres.clearCookie('key')

写在最后

req 和 res 是 Express 的灵魂,80% 的日常开发都在和它们打交道。把这篇文章当作速查手册,遇到问题回来翻一翻,慢慢就熟了。

如果觉得有帮助,欢迎点赞收藏~也欢迎在评论区交流你踩过的坑 😊

本文基于 Express 4.x 版本整理,如有更新或出入请以官方文档为准。