路由参数 #
一、参数类型 #
1.1 Express中的参数类型 #
| 类型 | 获取方式 | 示例 |
|---|---|---|
| 路径参数 | req.params | /users/:id |
| 查询参数 | req.query | /search?q=express |
| 请求体参数 | req.body | POST请求体 |
| 请求头参数 | req.headers | Authorization |
二、路径参数 #
2.1 基本用法 #
使用冒号 : 定义路径参数:
javascript
app.get('/users/:id', (req, res) => {
console.log(req.params);
res.json({ userId: req.params.id });
});
访问 /users/123:
json
{ "userId": "123" }
2.2 多个路径参数 #
javascript
app.get('/posts/:postId/comments/:commentId', (req, res) => {
const { postId, commentId } = req.params;
res.json({
postId,
commentId,
message: `文章${postId}的评论${commentId}`
});
});
访问 /posts/1/comments/5:
json
{
"postId": "1",
"commentId": "5",
"message": "文章1的评论5"
}
2.3 可选参数 #
使用 ? 表示可选参数:
javascript
app.get('/books/:category/:id?', (req, res) => {
const { category, id } = req.params;
if (id) {
res.json({ category, id, message: '获取单本书' });
} else {
res.json({ category, message: '获取分类列表' });
}
});
2.4 参数验证 #
使用正则表达式验证参数格式:
javascript
app.get('/users/:id(\\d+)', (req, res) => {
res.json({ userId: req.params.id });
});
只有数字ID才会匹配,如 /users/123。
javascript
app.get('/files/:filename(\\w+\\.\\w+)', (req, res) => {
res.json({ filename: req.params.filename });
});
匹配文件名格式,如 /files/document.pdf。
2.5 app.param()中间件 #
为特定参数添加预处理:
javascript
app.param('id', (req, res, next, id) => {
console.log(`处理ID参数: ${id}`);
if (!/^\d+$/.test(id)) {
return res.status(400).json({ error: 'ID必须是数字' });
}
req.userId = parseInt(id);
next();
});
app.get('/users/:id', (req, res) => {
res.json({ userId: req.userId });
});
2.6 参数转换 #
javascript
app.param('userId', async (req, res, next, userId) => {
try {
const user = await User.findById(userId);
if (!user) {
return res.status(404).json({ error: '用户不存在' });
}
req.user = user;
next();
} catch (error) {
next(error);
}
});
app.get('/users/:userId', (req, res) => {
res.json(req.user);
});
三、查询参数 #
3.1 基本用法 #
javascript
app.get('/search', (req, res) => {
const { q, page, limit } = req.query;
res.json({ query: q, page, limit });
});
访问 /search?q=express&page=2&limit=10:
json
{
"query": "express",
"page": "2",
"limit": "10"
}
3.2 默认值处理 #
javascript
app.get('/products', (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const sort = req.query.sort || 'createdAt';
const order = req.query.order || 'desc';
res.json({ page, limit, sort, order });
});
3.3 数组参数 #
javascript
app.get('/filter', (req, res) => {
let { tags } = req.query;
if (tags && !Array.isArray(tags)) {
tags = [tags];
}
res.json({ tags: tags || [] });
});
访问 /filter?tags=node&tags=express:
json
{
"tags": ["node", "express"]
}
3.4 对象参数 #
Express支持对象形式的查询参数:
javascript
app.get('/search', (req, res) => {
res.json(req.query);
});
访问 /search?user[name]=张三&user[age]=25:
json
{
"user": {
"name": "张三",
"age": "25"
}
}
3.5 复杂查询构建 #
javascript
app.get('/products', async (req, res) => {
const {
category,
minPrice,
maxPrice,
inStock,
brand,
sort,
page = 1,
limit = 10
} = req.query;
const query = {};
if (category) query.category = category;
if (minPrice || maxPrice) {
query.price = {};
if (minPrice) query.price.$gte = parseFloat(minPrice);
if (maxPrice) query.price.$lte = parseFloat(maxPrice);
}
if (inStock !== undefined) query.inStock = inStock === 'true';
if (brand) query.brand = brand;
const sortOption = {};
if (sort) {
const [field, order] = sort.split(':');
sortOption[field] = order === 'desc' ? -1 : 1;
}
res.json({
query,
sort: sortOption,
pagination: { page: parseInt(page), limit: parseInt(limit) }
});
});
四、请求体参数 #
4.1 JSON请求体 #
javascript
app.use(express.json());
app.post('/users', (req, res) => {
const { name, email, age } = req.body;
res.json({ name, email, age });
});
请求:
bash
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"张三","email":"zhangsan@example.com","age":25}'
4.2 URL编码请求体 #
javascript
app.use(express.urlencoded({ extended: true }));
app.post('/login', (req, res) => {
const { username, password } = req.body;
res.json({ username });
});
4.3 表单数据 #
javascript
app.post('/register', (req, res) => {
const { name, email, password, confirmPassword } = req.body;
if (password !== confirmPassword) {
return res.status(400).json({ error: '两次密码不一致' });
}
res.status(201).json({ name, email });
});
4.4 文件上传 #
javascript
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.single('avatar'), (req, res) => {
res.json({
file: req.file,
body: req.body
});
});
五、请求头参数 #
5.1 获取请求头 #
javascript
app.get('/headers', (req, res) => {
const userAgent = req.headers['user-agent'];
const contentType = req.headers['content-type'];
const authorization = req.headers['authorization'];
res.json({ userAgent, contentType, authorization });
});
5.2 Authorization头 #
javascript
app.get('/protected', (req, res) => {
const authHeader = req.headers['authorization'];
if (!authHeader) {
return res.status(401).json({ error: '缺少认证信息' });
}
const token = authHeader.split(' ')[1];
res.json({ token });
});
5.3 自定义请求头 #
javascript
app.get('/api/data', (req, res) => {
const apiVersion = req.headers['x-api-version'];
const clientId = req.headers['x-client-id'];
res.json({ apiVersion, clientId });
});
5.4 Content-Type处理 #
javascript
app.post('/data', (req, res) => {
const contentType = req.headers['content-type'];
if (contentType !== 'application/json') {
return res.status(415).json({ error: '不支持的媒体类型' });
}
res.json(req.body);
});
六、Cookie参数 #
6.1 安装cookie-parser #
bash
npm install cookie-parser
6.2 基本用法 #
javascript
const cookieParser = require('cookie-parser');
app.use(cookieParser());
app.get('/set-cookie', (req, res) => {
res.cookie('name', 'express', {
maxAge: 900000,
httpOnly: true
});
res.json({ message: 'Cookie已设置' });
});
app.get('/get-cookie', (req, res) => {
res.json({ cookies: req.cookies });
});
6.3 签名Cookie #
javascript
app.use(cookieParser('secret-key'));
app.get('/set-signed', (req, res) => {
res.cookie('token', 'abc123', { signed: true });
res.json({ message: '签名Cookie已设置' });
});
app.get('/get-signed', (req, res) => {
res.json({ signedCookies: req.signedCookies });
});
七、参数验证 #
7.1 手动验证 #
javascript
app.post('/users', (req, res) => {
const { name, email, password, age } = req.body;
const errors = [];
if (!name || name.trim().length === 0) {
errors.push('用户名不能为空');
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!email || !emailRegex.test(email)) {
errors.push('请输入有效的邮箱地址');
}
if (!password || password.length < 6) {
errors.push('密码至少6个字符');
}
if (age && (isNaN(age) || age < 0 || age > 150)) {
errors.push('请输入有效的年龄');
}
if (errors.length > 0) {
return res.status(400).json({ errors });
}
res.status(201).json({ name, email, age });
});
7.2 使用express-validator #
bash
npm install express-validator
javascript
const { body, validationResult } = require('express-validator');
const validateUser = [
body('name')
.notEmpty().withMessage('用户名不能为空')
.isLength({ min: 2, max: 50 }).withMessage('用户名2-50个字符'),
body('email')
.isEmail().withMessage('请输入有效的邮箱地址')
.normalizeEmail(),
body('password')
.isLength({ min: 6 }).withMessage('密码至少6个字符')
.matches(/\d/).withMessage('密码必须包含数字'),
body('age')
.optional()
.isInt({ min: 0, max: 150 }).withMessage('年龄必须在0-150之间')
];
app.post('/users', validateUser, (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
res.status(201).json(req.body);
});
7.3 路径参数验证 #
javascript
const { param, validationResult } = require('express-validator');
app.get('/users/:id',
param('id').isMongoId().withMessage('无效的用户ID'),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
res.json({ userId: req.params.id });
}
);
7.4 查询参数验证 #
javascript
const { query, validationResult } = require('express-validator');
app.get('/search',
query('q').notEmpty().withMessage('搜索关键词不能为空'),
query('page').optional().isInt({ min: 1 }).toInt(),
query('limit').optional().isInt({ min: 1, max: 100 }).toInt(),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
res.json(req.query);
}
);
八、参数清理 #
8.1 数据清洗 #
javascript
const sanitizeInput = (req, res, next) => {
if (req.body) {
Object.keys(req.body).forEach(key => {
if (typeof req.body[key] === 'string') {
req.body[key] = req.body[key].trim();
}
});
}
next();
};
app.use(sanitizeInput);
8.2 使用express-validator清理 #
javascript
const { body } = require('express-validator');
app.post('/users', [
body('name').trim().escape(),
body('email').normalizeEmail(),
body('age').toInt()
], (req, res) => {
res.json(req.body);
});
九、完整示例 #
9.1 用户API #
javascript
const express = require('express');
const { body, param, query, validationResult } = require('express-validator');
const app = express();
app.use(express.json());
let users = [
{ id: 1, name: '张三', email: 'zhangsan@example.com', age: 25 },
{ id: 2, name: '李四', email: 'lisi@example.com', age: 30 }
];
app.get('/users',
query('page').optional().isInt({ min: 1 }).toInt().default(1),
query('limit').optional().isInt({ min: 1, max: 100 }).toInt().default(10),
(req, res) => {
const { page, limit } = req.query;
const start = (page - 1) * limit;
const end = start + limit;
res.json({
page,
limit,
total: users.length,
data: users.slice(start, end)
});
}
);
app.get('/users/:id',
param('id').isInt({ min: 1 }).withMessage('无效的用户ID').toInt(),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const user = users.find(u => u.id === req.params.id);
if (!user) {
return res.status(404).json({ error: '用户不存在' });
}
res.json(user);
}
);
app.post('/users', [
body('name').notEmpty().withMessage('用户名不能为空').trim(),
body('email').isEmail().withMessage('请输入有效的邮箱').normalizeEmail(),
body('age').optional().isInt({ min: 0, max: 150 }).toInt()
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const newUser = {
id: users.length + 1,
...req.body
};
users.push(newUser);
res.status(201).json(newUser);
});
app.put('/users/:id', [
param('id').isInt({ min: 1 }).toInt(),
body('name').optional().notEmpty().trim(),
body('email').optional().isEmail().normalizeEmail(),
body('age').optional().isInt({ min: 0, max: 150 }).toInt()
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const index = users.findIndex(u => u.id === req.params.id);
if (index === -1) {
return res.status(404).json({ error: '用户不存在' });
}
users[index] = { ...users[index], ...req.body };
res.json(users[index]);
});
app.delete('/users/:id',
param('id').isInt({ min: 1 }).toInt(),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const index = users.findIndex(u => u.id === req.params.id);
if (index === -1) {
return res.status(404).json({ error: '用户不存在' });
}
users.splice(index, 1);
res.status(204).send();
}
);
app.listen(3000);
十、总结 #
路由参数要点:
| 参数类型 | 获取方式 | 用途 |
|---|---|---|
| 路径参数 | req.params | 资源标识 |
| 查询参数 | req.query | 过滤、分页 |
| 请求体 | req.body | 创建、更新数据 |
| 请求头 | req.headers | 认证、元信息 |
| Cookie | req.cookies | 会话管理 |
下一步,让我们学习路由模块化!
最后更新:2026-03-28