4、请求与响应对象:深入理解 Request 与 Response

这一章我们来聊聊 Express 里最核心的两个对象——Request 和 Response。说实话,我刚学 Express 那会儿,总觉得这两个东西就是拿来取参数、返回数据的。后来做项目多了才发现,这里面的门道还真不少。

你想想看,一个 API 请求从客户端发过来,到服务器处理完返回结果,中间所有的信息都在这两个对象里。搞懂了它们,你基本就掌握了 Express 的命脉。

Request 对象:你收到的每一份请求

Request 对象(通常简写为 req)封装了客户端发来的所有信息。我个人习惯把它分成三块来看:参数从哪里来、请求头里有什么、请求体里装了什么。

1. req.params:路由参数

路由参数是 URL 路径里动态变化的部分。比如你要查询某个用户的资料,URL 可能是 /users/123,这里的 123 就是用户 ID。

// 定义路由
app.get('/users/:id', (req, res) => {
  console.log(req.params); // { id: '123' }
  res.send(`用户ID是:${req.params.id}`);
});
我的经验:路由参数一定要做类型校验。我曾经在项目里直接拿 req.params.id 去查数据库,结果前端传了个字符串 "abc",数据库直接报错。后来我养成了习惯:所有路由参数进来先转数字,或者用中间件统一校验。

2. req.query:查询字符串

查询字符串就是 URL 问号后面的部分。比如 /users?page=1&limit=10,Express 会自动解析成对象。

// 请求:/search?keyword=node&page=2
app.get('/search', (req, res) => {
  const { keyword, page } = req.query;
  console.log(keyword); // 'node'
  console.log(page);    // '2'(注意:是字符串)
  res.send(`搜索关键词:${keyword},第${page}页`);
});
注意:req.query 里的值全是字符串。如果你需要数字,记得手动转换。我见过太多人直接拿 page 去做加法运算,结果变成了字符串拼接。

3. req.body:请求体

请求体通常用在 POST、PUT 等需要提交数据的请求中。但要注意,Express 默认不解析请求体,你需要用中间件。

// 必须引入 body-parser 或使用 Express 内置的 json 解析
app.use(express.json()); // 解析 JSON 格式的请求体
app.use(express.urlencoded({ extended: true })); // 解析表单数据

app.post('/users', (req, res) => {
  const { name, age } = req.body;
  console.log(name, age);
  res.send(`创建用户:${name}`);
});
避坑指南:我曾经遇到过一个诡异的问题——前端明明传了 JSON,但 req.body 始终是 undefined。排查了半天才发现,是忘了加 express.json() 中间件。嗯,这种低级错误犯过一次就再也不会忘了。

Response 对象:你返回的每一份响应

Response 对象(通常简写为 res)是你给客户端的回复。说白了,客户端发请求给你,你总得回点东西吧?

1. res.send():最通用的响应方法

res.send() 可以返回各种类型的数据:字符串、Buffer、对象、数组。Express 会自动设置 Content-Type。

app.get('/hello', (req, res) => {
  res.send('Hello World'); // 返回纯文本
});

app.get('/data', (req, res) => {
  res.send({ message: '成功', code: 200 }); // 自动转为 JSON
});
小技巧:我个人习惯在开发阶段用 res.send() 快速调试。但生产环境里,我建议明确指定返回类型,避免 Express 自动推断出错。

2. res.json():专门返回 JSON

这个方法和 res.send() 传对象的效果一样,但语义更明确。我推荐在 API 开发中统一使用 res.json()

app.get('/api/users', (req, res) => {
  const users = [
    { id: 1, name: '张三' },
    { id: 2, name: '李四' }
  ];
  res.json({
    success: true,
    data: users,
    total: 2
  });
});

3. res.render():渲染模板

如果你在用 Express 做服务端渲染(SSR),res.render() 就是你的好帮手。它会用模板引擎渲染 HTML 页面。

// 先设置模板引擎
app.set('view engine', 'ejs');

app.get('/profile', (req, res) => {
  const user = { name: '王五', age: 28 };
  res.render('profile', { user }); // 渲染 profile.ejs 模板
});
注意:现在前后端分离是主流,纯 API 服务很少用 res.render()。但如果你在做全栈项目,或者需要服务端渲染 SEO 友好的页面,这个方法还是很实用的。

4. res.redirect():重定向

重定向就是告诉客户端:「你要找的东西不在这里,去那边看看。」

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

app.get('/old-path-permanent', (req, res) => {
  res.redirect(301, '/new-path'); // 301 永久重定向
});
我的经验:301 和 302 的区别一定要搞清楚。301 是永久重定向,浏览器会缓存这个结果。如果你只是临时调整路径,用 302。我曾经因为用了 301 重定向,导致用户浏览器一直访问旧地址,折腾了好久才清除缓存。

链式调用:优雅的代码风格

Express 的 Response 对象支持链式调用。什么意思呢?就是你可以连续调用多个方法,不用每次都写 res

app.get('/chain', (req, res) => {
  res
    .status(201)          // 设置状态码
    .set('X-Custom-Header', 'hello') // 设置自定义头
    .json({ message: '创建成功' });  // 返回 JSON
});

我个人非常喜欢这种写法。它让代码看起来更紧凑,逻辑也更清晰。你想想看,如果不链式调用,你得写成这样:

// 不推荐:代码冗余
app.get('/chain', (req, res) => {
  res.status(201);
  res.set('X-Custom-Header', 'hello');
  res.json({ message: '创建成功' });
});
小建议:链式调用虽然好,但别滥用。如果逻辑太复杂,拆成多行反而更易读。我一般只在设置状态码、头信息和返回数据这三步时用链式。

总结一下

这一章我们聊了 Request 和 Response 的核心用法。说白了,Request 就是「你收到了什么」,Response 就是「你要回什么」。搞清楚了参数的三种来源(params、query、body),以及响应的四种方式(send、json、render、redirect),你就能应付大部分 API 开发场景了。

下一章我们会深入聊聊中间件——Express 的灵魂所在。到时候你会发现,理解了 Request 和 Response,中间件学起来会轻松很多。

课后练习:写一个用户注册接口,接收 POST 请求,从 body 里取用户名和密码,返回 JSON 格式的成功消息。试试用链式调用设置状态码为 201。