跳到主要内容

请求响应

在 Express 应用中,每个路由处理函数都会接收两个核心对象:请求对象(req)和响应对象(res)。理解这两个对象的属性和方法,是构建 Web 应用的基础。

请求对象 (req)

请求对象代表了 HTTP 请求,包含了客户端发送的所有信息。它是 Node.js 原生 IncomingMessage 对象的扩展。

常用属性一览

属性说明示例
req.body请求体数据(需要解析中间件){ name: 'John' }
req.params路由参数{ id: '123' }
req.query查询字符串参数{ page: '1', limit: '10' }
req.headers请求头对象{ 'content-type': 'application/json' }
req.methodHTTP 方法'GET', 'POST'
req.url请求 URL 路径'/users?page=1'
req.pathURL 路径部分(不含查询字符串)'/users'
req.protocol协议'http''https'
req.hostname主机名'localhost'
req.ip客户端 IP 地址'127.0.0.1'
req.cookiesCookie 对象(需要 cookie-parser){ sessionId: 'abc123' }

路由参数 (req.params)

路由参数是 URL 路径中的动态部分,通过冒号 : 定义:

// 定义带参数的路由
app.get('/users/:userId/books/:bookId', (req, res) => {
// req.params 包含所有路由参数
const { userId, bookId } = req.params;

res.json({
message: `用户 ${userId} 的书籍 ${bookId}`
});
});

// GET /users/123/books/456
// req.params = { userId: '123', bookId: '456' }

路由参数的值总是字符串类型。如果需要数字,必须手动转换:

app.get('/users/:id', (req, res) => {
const id = parseInt(req.params.id, 10); // 转换为数字

if (isNaN(id)) {
return res.status(400).json({ error: 'ID 必须是数字' });
}

// 使用 id 查询数据库...
});

查询参数 (req.query)

查询参数是 URL 中 ? 后面的部分:

app.get('/search', (req, res) => {
// req.query 包含所有查询参数
const { q, page = '1', limit = '10' } = req.query;

res.json({
keyword: q,
pagination: {
page: parseInt(page),
limit: parseInt(limit)
}
});
});

// GET /search?q=express&page=2&limit=20
// req.query = { q: 'express', page: '2', limit: '20' }

查询参数也总是字符串类型。如果参数有多个相同名称,会变成数组:

// GET /filter?category=books&category=music
app.get('/filter', (req, res) => {
console.log(req.query.category); // ['books', 'music']
});

请求体 (req.body)

req.body 包含请求体中的数据,但需要先使用解析中间件:

// 解析 JSON 格式的请求体
app.use(express.json());

// 解析 URL 编码的请求体(表单数据)
app.use(express.urlencoded({ extended: true }));

app.post('/users', (req, res) => {
// 现在可以访问 req.body
const { name, email, age } = req.body;

res.status(201).json({
message: '用户创建成功',
user: { name, email, age }
});
});

// POST /users
// Content-Type: application/json
// Body: { "name": "John", "email": "[email protected]", "age": 25 }

重要安全提示req.body 的内容来自用户输入,所有值都不可信,使用前应该验证。

在 Express 5 中,如果请求没有请求体、Content-Type 不匹配或解析出错,req.body 会是 undefined。在 Express 4 中,默认是空对象 {}。这个变化要求你在访问 req.body 属性时更加谨慎:

// 不安全的写法
app.post('/data', (req, res) => {
// 如果 body 为空或 name 不存在,会出错
const name = req.body.name.toUpperCase();
});

// Express 5 安全的写法
app.post('/data', (req, res) => {
// 检查 body 是否存在
if (!req.body) {
return res.status(400).json({ error: '请求体不能为空' });
}

const name = req.body.name;
if (!name || typeof name !== 'string') {
return res.status(400).json({ error: 'name 字段必填且必须是字符串' });
}

const upperName = name.toUpperCase();
res.json({ name: upperName });
});

// 或使用可选链和空值合并
app.post('/data', (req, res) => {
const name = req.body?.name;

if (!name || typeof name !== 'string') {
return res.status(400).json({ error: 'name 字段必填且必须是字符串' });
}

res.json({ name: name.toUpperCase() });
});

请求头 (req.headers)

HTTP 请求头包含了请求的元信息:

app.get('/info', (req, res) => {
// 访问单个请求头(推荐方式)
const contentType = req.get('Content-Type');
const auth = req.get('Authorization');

// 访问所有请求头
console.log(req.headers);

res.json({
userAgent: req.headers['user-agent'],
contentType,
authorization: auth
});
});

常用请求头:

请求头说明
content-type请求体的媒体类型
authorization认证信息(如 Bearer Token)
user-agent客户端信息(浏览器、操作系统等)
accept客户端接受的响应类型
host目标主机名

请求方法 (req.method)

获取 HTTP 请求方法:

app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});

// GET /users → "GET /users"
// POST /users → "POST /users"

获取客户端信息

app.get('/client-info', (req, res) => {
res.json({
ip: req.ip, // 客户端 IP
ips: req.ips, // 代理链中的 IP 数组
hostname: req.hostname, // 主机名
protocol: req.protocol, // http 或 https
secure: req.secure, // 是否 HTTPS(等价于 req.protocol === 'https')
xhr: req.xhr // 是否 AJAX 请求
});
});

如果应用在反向代理(如 Nginx)后面,需要设置 trust proxy 才能获取真实 IP:

app.set('trust proxy', true);

app.get('/ip', (req, res) => {
res.json({ ip: req.ip });
});

需要 cookie-parser 中间件:

npm install cookie-parser
const cookieParser = require('cookie-parser');
app.use(cookieParser());

app.get('/read-cookie', (req, res) => {
// req.cookies 是一个对象
const sessionId = req.cookies.sessionId;

res.json({ sessionId });
});

文件上传 (req.file / req.files)

需要 multer 中间件处理文件上传:

npm install multer
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });

// 单文件上传
app.post('/upload', upload.single('avatar'), (req, res) => {
// req.file 包含文件信息
console.log(req.file);
/*
{
fieldname: 'avatar',
originalname: 'photo.jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
size: 102400,
destination: 'uploads/',
filename: 'abc123',
path: 'uploads/abc123'
}
*/

res.json({
message: '上传成功',
file: req.file
});
});

// 多文件上传(相同字段名)
app.post('/photos', upload.array('photos', 10), (req, res) => {
// req.files 是数组
res.json({ count: req.files.length });
});

// 多字段文件上传
app.post('/profile', upload.fields([
{ name: 'avatar', maxCount: 1 },
{ name: 'documents', maxCount: 5 }
]), (req, res) => {
// req.files 是对象
// { avatar: [...], documents: [...] }
res.json({ files: req.files });
});

响应对象 (res)

响应对象用于向客户端发送响应。它扩展了 Node.js 的 ServerResponse 对象。

发送响应的方法

res.send()

发送各种类型的响应,Express 会自动设置 Content-Type

// 发送字符串
app.get('/text', (req, res) => {
res.send('Hello World');
// Content-Type: text/html
});

// 发送对象/数组(自动转 JSON)
app.get('/json', (req, res) => {
res.send({ name: 'Express', version: '4.x' });
// Content-Type: application/json
});

// 发送 Buffer
app.get('/buffer', (req, res) => {
res.send(Buffer.from('Hello'));
// Content-Type: application/octet-stream
});

res.json()

明确发送 JSON 响应:

app.get('/api/user', (req, res) => {
res.json({
success: true,
data: {
id: 1,
name: 'John'
}
});
});

res.json()res.send() 发送对象时的区别:res.json() 会强制设置 Content-Typeapplication/json,而 res.send() 是根据内容类型自动推断。

res.status()

设置 HTTP 状态码,通常与其他响应方法链式调用:

// 创建成功
app.post('/users', (req, res) => {
res.status(201).json({ id: 1, name: 'New User' });
});

// 无内容
app.delete('/users/:id', (req, res) => {
res.status(204).send();
});

// 客户端错误
app.get('/error', (req, res) => {
res.status(400).json({ error: '参数错误' });
});

// 服务端错误
app.get('/server-error', (req, res) => {
res.status(500).json({ error: '服务器内部错误' });
});

常用 HTTP 状态码:

状态码说明使用场景
200成功一般成功响应
201已创建POST 创建资源成功
204无内容DELETE 成功或 PUT 更新成功
400错误请求参数验证失败
401未认证需要登录
403禁止访问权限不足
404未找到资源不存在
409冲突资源已存在
500服务器错误程序错误

设置响应头

app.get('/headers', (req, res) => {
// 设置单个响应头
res.set('X-Custom-Header', 'value');

// 设置多个响应头
res.set({
'Cache-Control': 'no-cache',
'X-Request-Id': '12345'
});

res.json({ message: '响应头已设置' });
});
// 设置 Cookie
app.get('/set-cookie', (req, res) => {
res.cookie('name', 'express', {
maxAge: 900000, // 有效期(毫秒)
httpOnly: true, // 仅服务器可访问
secure: true, // 仅 HTTPS 传输
sameSite: 'strict', // CSRF 防护
domain: 'example.com',
path: '/'
});

res.send('Cookie 已设置');
});

// 清除 Cookie
app.get('/clear-cookie', (req, res) => {
res.clearCookie('name');
res.send('Cookie 已清除');
});

重定向

// 重定向到相对路径
app.get('/old', (req, res) => {
res.redirect('/new');
});

// 重定向到外部 URL
app.get('/external', (req, res) => {
res.redirect('https://expressjs.com');
});

// 指定状态码(默认 302)
app.get('/permanent', (req, res) => {
res.redirect(301, '/new-permanent'); // 永久重定向
});

// 返回上一页(Express 4.x 写法)
// app.get('/back', (req, res) => {
// res.redirect('back'); // 重定向到 Referer 或 /
// });

// Express 5.x 写法:'back' 魔法字符串已移除
app.get('/back', (req, res) => {
res.redirect(req.get('Referrer') || '/'); // 显式指定来源页
});

文件操作

// 下载文件
app.get('/download', (req, res) => {
res.download('/files/report.pdf');
});

// 下载时指定文件名
app.get('/download-custom', (req, res) => {
res.download('/files/report.pdf', '月度报告.pdf', (err) => {
if (err) {
console.error('下载出错:', err);
}
});
});

// 发送文件(直接显示)
app.get('/file', (req, res) => {
// root 选项指定根目录
res.sendFile('index.html', { root: __dirname + '/public' });
});

// 发送文件作为附件下载
app.get('/attachment', (req, res) => {
res.attachment('/files/data.csv');
res.send('col1,col2\nval1,val2');
});

渲染模板

Express 支持多种模板引擎(如 EJS、Pug、Handlebars):

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

// 渲染模板
app.get('/profile', (req, res) => {
res.render('profile', {
title: '用户资料',
user: {
name: 'John',
email: '[email protected]'
}
});
});

实战模式

统一响应格式

在团队协作中,统一响应格式能提高代码可维护性:

// utils/response.js
class Response {
static success(res, data, message = '操作成功', code = 0) {
return res.json({
code,
message,
data
});
}

static error(res, message = '操作失败', code = 1, status = 400) {
return res.status(status).json({
code,
message,
data: null
});
}

static paginated(res, data, pagination) {
return res.json({
code: 0,
message: '查询成功',
data,
pagination
});
}
}

module.exports = Response;

使用示例:

const Response = require('../utils/response');

// 成功响应
app.get('/users', async (req, res) => {
const users = await User.find();
Response.success(res, users, '获取用户列表成功');
});

// 错误响应
app.post('/users', async (req, res) => {
if (!req.body.name) {
return Response.error(res, '姓名不能为空', 400, 400);
}

const user = await User.create(req.body);
Response.success(res, user, '创建成功', 201);
});

分页响应

app.get('/posts', async (req, res) => {
const { page = 1, limit = 10 } = req.query;

const skip = (parseInt(page) - 1) * parseInt(limit);

// 并行查询数据和总数
const [posts, total] = await Promise.all([
Post.find().skip(skip).limit(parseInt(limit)),
Post.countDocuments()
]);

res.json({
code: 0,
data: posts,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / parseInt(limit))
}
});
});

条件响应

根据请求类型返回不同格式:

app.get('/data', (req, res) => {
const data = { name: 'Express', version: '4.x' };

// 检查 Accept 头
const accept = req.get('Accept');

if (accept?.includes('application/json')) {
res.json(data);
} else if (accept?.includes('text/html')) {
res.render('data', { data });
} else {
res.send(`名称: ${data.name}, 版本: ${data.version}`);
}
});

流式响应

对于大文件或实时数据,使用流式响应:

const fs = require('fs');

app.get('/large-file', (req, res) => {
const stream = fs.createReadStream('./large-file.txt');

res.set('Content-Type', 'text/plain');
stream.pipe(res);

stream.on('error', (err) => {
console.error('流错误:', err);
res.status(500).end('服务器错误');
});
});

// Server-Sent Events
app.get('/events', (req, res) => {
res.set({
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});

// 每秒发送一次时间
const interval = setInterval(() => {
res.write(`data: ${new Date().toISOString()}\n\n`);
}, 1000);

// 客户端断开连接时清理
req.on('close', () => {
clearInterval(interval);
});
});

请求响应完整示例

下面是一个完整的用户 API 示例,展示了请求处理和响应发送的各种场景:

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

// 中间件
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 模拟数据库
let users = [
{ id: 1, name: 'Alice', email: '[email protected]' },
{ id: 2, name: 'Bob', email: '[email protected]' }
];

// GET /users - 获取用户列表(支持分页和搜索)
app.get('/users', (req, res) => {
const { page = 1, limit = 10, search = '' } = req.query;

// 过滤
let filtered = users;
if (search) {
filtered = users.filter(u =>
u.name.toLowerCase().includes(search.toLowerCase())
);
}

// 分页
const start = (parseInt(page) - 1) * parseInt(limit);
const paginated = filtered.slice(start, start + parseInt(limit));

res.json({
code: 0,
data: paginated,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: filtered.length
}
});
});

// GET /users/:id - 获取单个用户
app.get('/users/:id', (req, res) => {
const id = parseInt(req.params.id);
const user = users.find(u => u.id === id);

if (!user) {
return res.status(404).json({
code: 404,
message: '用户不存在'
});
}

res.json({ code: 0, data: user });
});

// POST /users - 创建用户
app.post('/users', (req, res) => {
const { name, email } = req.body;

// 验证
if (!name || !email) {
return res.status(400).json({
code: 400,
message: '姓名和邮箱不能为空'
});
}

// 创建
const newUser = {
id: users.length + 1,
name,
email
};

users.push(newUser);

res.status(201).json({
code: 0,
message: '创建成功',
data: newUser
});
});

// PUT /users/:id - 更新用户
app.put('/users/:id', (req, res) => {
const id = parseInt(req.params.id);
const index = users.findIndex(u => u.id === id);

if (index === -1) {
return res.status(404).json({
code: 404,
message: '用户不存在'
});
}

users[index] = { ...users[index], ...req.body, id };

res.json({
code: 0,
message: '更新成功',
data: users[index]
});
});

// DELETE /users/:id - 删除用户
app.delete('/users/:id', (req, res) => {
const id = parseInt(req.params.id);
const index = users.findIndex(u => u.id === id);

if (index === -1) {
return res.status(404).json({
code: 404,
message: '用户不存在'
});
}

users.splice(index, 1);

res.status(204).send();
});

app.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});

小结

本章详细介绍了 Express 的请求和响应对象:

请求对象 (req)

  • req.params - 路由参数
  • req.query - 查询字符串参数
  • req.body - 请求体数据
  • req.headers - 请求头
  • req.method - HTTP 方法
  • req.ip - 客户端 IP
  • req.cookies - Cookie

响应对象 (res)

  • res.send() - 发送响应
  • res.json() - 发送 JSON
  • res.status() - 设置状态码
  • res.redirect() - 重定向
  • res.download() - 文件下载
  • res.sendFile() - 发送文件
  • res.render() - 渲染模板
  • res.cookie() / res.clearCookie() - Cookie 操作

理解这些属性和方法,是构建 Express 应用的基础。

练习

  1. 创建一个 API,返回请求的所有信息(方法、路径、参数、请求头等)
  2. 实现一个带分页和搜索功能的用户列表 API
  3. 创建一个文件上传接口,支持图片上传并返回访问 URL
  4. 实现统一响应格式的工具类,并在路由中使用