Express 路由与请求处理
核心知识点
路由参数
Express 支持多种类型的路由参数:
- 路径参数:如
/users/:id中的id - 查询参数:如
?name=value中的键值对 - 正则表达式路由:使用正则表达式匹配路径
请求方法
Express 支持所有 HTTP 请求方法:
GET:获取资源POST:创建资源PUT:更新资源DELETE:删除资源PATCH:部分更新资源OPTIONS:获取资源的可用方法HEAD:获取资源的头部信息
请求体解析
对于包含请求体的请求(如 POST、PUT),需要使用中间件解析请求体:
express.json():解析 JSON 格式的请求体express.urlencoded():解析 URL 编码的表单数据multer:处理文件上传
路由模块化
使用 express.Router() 创建模块化的路由处理器,便于组织大型应用的代码结构。
路由中间件
为特定路由添加中间件,实现路由级别的功能增强。
实用案例
案例一:高级路由参数
const express = require('express');
const app = express();
const port = 3000;
// 基本路径参数
app.get('/users/:id', (req, res) => {
res.json({
userId: req.params.id,
message: '用户信息'
});
});
// 多个路径参数
app.get('/users/:id/posts/:postId', (req, res) => {
res.json({
userId: req.params.id,
postId: req.params.postId,
message: '用户文章'
});
});
// 正则表达式路由 - 匹配数字 ID
app.get('/products/:id(\d+)', (req, res) => {
res.json({
productId: req.params.id,
message: '产品信息(数字 ID)'
});
});
// 正则表达式路由 - 匹配字母开头的用户名
app.get('/profiles/:username([a-zA-Z][a-zA-Z0-9_]+)', (req, res) => {
res.json({
username: req.params.username,
message: '用户资料'
});
});
// 可选参数
app.get('/blog/:year?/:month?/:day?', (req, res) => {
const { year, month, day } = req.params;
res.json({
year: year || '全部',
month: month || '全部',
day: day || '全部',
message: '博客归档'
});
});
app.listen(port, () => {
console.log(`服务器运行在 http://localhost:${port}`);
});案例二:请求体解析
const express = require('express');
const app = express();
const port = 3000;
// 解析 JSON 请求体
app.use(express.json());
// 解析 URL 编码的表单数据
app.use(express.urlencoded({ extended: true }));
// 处理 GET 请求 - 查询参数
app.get('/search', (req, res) => {
res.json({
query: req.query,
message: '搜索结果'
});
});
// 处理 POST 请求 - JSON 数据
app.post('/api/users', (req, res) => {
res.json({
user: req.body,
message: '用户创建成功'
});
});
// 处理 PUT 请求 - 更新资源
app.put('/api/users/:id', (req, res) => {
res.json({
userId: req.params.id,
updates: req.body,
message: '用户更新成功'
});
});
// 处理 PATCH 请求 - 部分更新
app.patch('/api/users/:id', (req, res) => {
res.json({
userId: req.params.id,
partialUpdates: req.body,
message: '用户部分更新成功'
});
});
// 处理 DELETE 请求
app.delete('/api/users/:id', (req, res) => {
res.json({
userId: req.params.id,
message: '用户删除成功'
});
});
app.listen(port, () => {
console.log(`服务器运行在 http://localhost:${port}`);
});案例三:模块化路由
// routes/users.js
const express = require('express');
const router = express.Router();
// 用户相关路由
router.get('/', (req, res) => {
res.json({
users: [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
],
message: '用户列表'
});
});
router.get('/:id', (req, res) => {
res.json({
id: req.params.id,
name: '用户名称',
email: 'user@example.com'
});
});
router.post('/', (req, res) => {
res.json({
user: req.body,
message: '用户创建成功'
});
});
module.exports = router;
// routes/products.js
const express = require('express');
const router = express.Router();
// 产品相关路由
router.get('/', (req, res) => {
res.json({
products: [
{ id: 1, name: '产品1', price: 100 },
{ id: 2, name: '产品2', price: 200 }
],
message: '产品列表'
});
});
router.get('/:id', (req, res) => {
res.json({
id: req.params.id,
name: '产品名称',
price: 99.99,
description: '产品描述'
});
});
module.exports = router;
// app.js
const express = require('express');
const app = express();
const port = 3000;
const userRoutes = require('./routes/users');
const productRoutes = require('./routes/products');
// 中间件
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 注册路由
app.use('/api/users', userRoutes);
app.use('/api/products', productRoutes);
// 根路径
app.get('/', (req, res) => {
res.send('Express 模块化路由示例');
});
app.listen(port, () => {
console.log(`服务器运行在 http://localhost:${port}`);
});案例四:表单处理
const express = require('express');
const app = express();
const port = 3000;
// 解析表单数据
app.use(express.urlencoded({ extended: true }));
// 提供表单页面
app.get('/form', (req, res) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>表单示例</title>
</head>
<body>
<h1>用户注册</h1>
<form action="/submit-form" method="POST">
<div>
<label>姓名:</label>
<input type="text" name="name" required>
</div>
<div>
<label>邮箱:</label>
<input type="email" name="email" required>
</div>
<div>
<label>密码:</label>
<input type="password" name="password" required>
</div>
<div>
<label>性别:</label>
<input type="radio" name="gender" value="male" checked> 男
<input type="radio" name="gender" value="female"> 女
</div>
<div>
<label>爱好:</label>
<input type="checkbox" name="hobbies" value="reading"> 阅读
<input type="checkbox" name="hobbies" value="sports"> 运动
<input type="checkbox" name="hobbies" value="coding"> 编程
</div>
<div>
<label>城市:</label>
<select name="city">
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="guangzhou">广州</option>
</select>
</div>
<button type="submit">提交</button>
</form>
</body>
</html>
`);
});
// 处理表单提交
app.post('/submit-form', (req, res) => {
// 获取表单数据
const formData = req.body;
// 处理复选框数据(如果有多个值)
if (typeof formData.hobbies === 'string') {
formData.hobbies = [formData.hobbies];
}
// 发送响应
res.json({
message: '表单提交成功',
data: formData
});
});
app.listen(port, () => {
console.log(`服务器运行在 http://localhost:${port}`);
});案例五:文件上传处理
const express = require('express');
const multer = require('multer');
const path = require('path');
const app = express();
const port = 3000;
// 配置 multer 存储
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './uploads');
},
filename: function (req, file, cb) {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
}
});
// 创建 multer 实例
const upload = multer({ storage: storage });
// 提供文件上传页面
app.get('/upload', (req, res) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>文件上传</title>
</head>
<body>
<h1>文件上传示例</h1>
<form action="/upload-file" method="POST" enctype="multipart/form-data">
<div>
<label>用户名:</label>
<input type="text" name="username" required>
</div>
<div>
<label>上传文件:</label>
<input type="file" name="avatar" required>
</div>
<div>
<label>上传多个文件:</label>
<input type="file" name="photos" multiple>
</div>
<button type="submit">上传</button>
</form>
</body>
</html>
`);
});
// 处理单个文件上传
app.post('/upload-file', upload.fields([
{ name: 'avatar', maxCount: 1 },
{ name: 'photos', maxCount: 5 }
]), (req, res) => {
const files = req.files;
const formData = req.body;
res.json({
message: '文件上传成功',
user: formData.username,
avatar: files.avatar ? files.avatar[0].filename : null,
photos: files.photos ? files.photos.map(file => file.filename) : []
});
});
app.listen(port, () => {
console.log(`服务器运行在 http://localhost:${port}`);
});学习目标
- 掌握路由参数:能够使用路径参数、查询参数和正则表达式路由
- 处理多种请求方法:学会使用不同 HTTP 请求方法处理各种操作
- 解析请求体:能够解析 JSON 和表单格式的请求体数据
- 模块化路由:学会使用 express.Router() 创建模块化路由
- 处理表单数据:能够处理各种类型的表单输入,包括复选框、单选按钮等
- 文件上传:学会使用 multer 处理文件上传
- 路由中间件:能够为特定路由添加中间件
代码优化建议
1. 路由参数验证
不好的做法:
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
// 直接使用参数,没有验证
getUserById(userId, (err, user) => {
if (err) {
res.status(500).send('错误');
} else {
res.json(user);
}
});
});好的做法:
app.get('/users/:id', (req, res, next) => {
const userId = req.params.id;
// 验证参数
if (!/^\d+$/.test(userId)) {
return res.status(400).json({ error: '无效的用户 ID' });
}
next();
}, (req, res) => {
const userId = parseInt(req.params.id);
getUserById(userId, (err, user) => {
if (err) {
res.status(500).send('错误');
} else {
res.json(user);
}
});
});2. 错误处理
不好的做法:
app.post('/api/data', (req, res) => {
const data = req.body;
// 没有错误处理
saveData(data);
res.json({ message: '成功' });
});好的做法:
app.post('/api/data', async (req, res, next) => {
try {
const data = req.body;
// 验证数据
if (!data.name) {
return res.status(400).json({ error: '缺少必要字段' });
}
// 异步保存数据
await saveData(data);
res.json({ message: '成功' });
} catch (error) {
next(error); // 传递给错误处理中间件
}
});
// 全局错误处理中间件
app.use((err, req, res, next) => {
console.error('错误:', err);
res.status(500).json({ error: '服务器内部错误' });
});3. 路由组织
不好的做法:
const express = require('express');
const app = express();
// 所有路由都在一个文件中
app.get('/api/users', (req, res) => {...});
app.get('/api/users/:id', (req, res) => {...});
app.post('/api/users', (req, res) => {...});
app.put('/api/users/:id', (req, res) => {...});
app.delete('/api/users/:id', (req, res) => {...});
app.get('/api/products', (req, res) => {...});
app.get('/api/products/:id', (req, res) => {...});
app.post('/api/products', (req, res) => {...});
// 更多路由...好的做法:
// routes/users.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {...});
router.get('/:id', (req, res) => {...});
router.post('/', (req, res) => {...});
router.put('/:id', (req, res) => {...});
router.delete('/:id', (req, res) => {...});
module.exports = router;
// routes/products.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {...});
router.get('/:id', (req, res) => {...});
router.post('/', (req, res) => {...});
module.exports = router;
// app.js
const express = require('express');
const app = express();
const userRoutes = require('./routes/users');
const productRoutes = require('./routes/products');
app.use('/api/users', userRoutes);
app.use('/api/products', productRoutes);常见问题与解决方案
问题1:请求体解析失败
原因:
- 没有使用正确的中间件解析请求体
- 请求体格式与中间件不匹配
- 请求体过大
解决方案:
- 使用
express.json()解析 JSON 数据 - 使用
express.urlencoded()解析表单数据 - 使用
multer处理文件上传 - 调整中间件配置,如增加请求体大小限制
问题2:路由参数获取错误
原因:
- 路由定义顺序错误
- 路径参数与查询参数混淆
- 正则表达式路由语法错误
解决方案:
- 将具体路由放在通配符路由之前
- 使用
req.params获取路径参数,req.query获取查询参数 - 检查正则表达式语法
问题3:文件上传失败
原因:
- 没有使用
multer中间件 - 表单没有设置
enctype="multipart/form-data" - 上传目录权限问题
- 文件大小超过限制
解决方案:
- 安装并正确配置
multer - 确保表单设置了正确的 enctype
- 确保上传目录存在且有写入权限
- 调整
multer配置,增加文件大小限制
问题4:路由模块化后无法访问
原因:
- 路由文件路径错误
- 路由注册路径错误
- 路由导出或导入错误
解决方案:
- 检查文件路径是否正确
- 检查
app.use()的路径参数 - 确保使用
module.exports导出路由,使用require()导入路由
总结
通过本教程的学习,你应该能够:
- 掌握 Express 路由系统的高级特性,包括路径参数、查询参数和正则表达式路由
- 处理各种 HTTP 请求方法,实现完整的 RESTful API
- 解析不同格式的请求体数据,包括 JSON、表单和文件上传
- 使用 express.Router() 创建模块化的路由处理器,组织大型应用的代码结构
- 处理各种类型的表单输入,包括文本、复选框、单选按钮等
- 为特定路由添加中间件,实现路由级别的功能增强
- 编写清晰、可维护的 Express 路由代码
这些技能将帮助你构建功能完整、结构清晰的 Express 应用,为后续学习数据库集成、认证授权等高级功能打下坚实的基础。