路由和中间件
🎯 学习目标
- 理解Express路由的基本概念和使用方法
- 掌握不同HTTP方法的路由定义
- 学会使用路由参数和查询参数
- 理解中间件的概念和执行顺序
- 掌握常用内置中间件和自定义中间件
- 学会组织和模块化路由
📚 路由基础
什么是路由?
路由是指确定应用程序如何响应客户端对特定端点的请求,该端点是URI(或路径)和特定HTTP请求方法(GET、POST等)的组合。
每个路由可以具有一个或多个处理函数,这些函数在路由匹配时执行。
基本路由定义
Express提供了多种定义路由的方法:
const express = require('express');
const app = express();
// GET方法路由
app.get('/', (req, res) => {
res.send('GET请求');
});
// POST方法路由
app.post('/', (req, res) => {
res.send('POST请求');
});
// PUT方法路由
app.put('/', (req, res) => {
res.send('PUT请求');
});
// DELETE方法路由
app.delete('/', (req, res) => {
res.send('DELETE请求');
});
路由方法
Express支持多种HTTP请求方法,对应不同的路由方法:
app.get()
app.post()
app.put()
app.delete()
app.patch()
app.head()
app.options()
还有特殊方法app.all()
,用于所有HTTP方法:
app.all('/secret', (req, res, next) => {
console.log('访问了secret页面');
next(); // 传递控制权到下一个处理器
});
🔄 路由路径
路由路径可以是字符串、字符串模式或正则表达式。
字符串路径
// 匹配 /about
app.get('/about', (req, res) => {
res.send('关于页面');
});
// 匹配 /random.text
app.get('/random.text', (req, res) => {
res.send('随机文本');
});
字符串模式路径
使用以下字符组合可以定义模式路径:
?
表示前面的字符是可选的+
表示前面的字符出现一次或多次*
表示任意字符的任意组合()
表示分组
// 匹配 acd 和 abcd
app.get('/ab?cd', (req, res) => {
res.send('ab?cd');
});
// 匹配 abcd、abbcd、abbbcd等
app.get('/ab+cd', (req, res) => {
res.send('ab+cd');
});
// 匹配 abcd、abxcd、abRANDOMcd等
app.get('/ab*cd', (req, res) => {
res.send('ab*cd');
});
// 匹配 /abe 和 /abcde
app.get('/a(bc)d', (req, res) => {
res.send('a(bc)d');
});
正则表达式路径
// 匹配任何包含字母a的路径
app.get(/a/, (req, res) => {
res.send('/a/');
});
// 匹配以fly结尾的路径
app.get(/.*fly$/, (req, res) => {
res.send('/.*fly$/');
});
📦 路由参数
路由参数是URL中的命名段,用于捕获URL中的值。捕获的值存储在req.params
对象中。
// 匹配 /user/123
app.get('/user/:userId', (req, res) => {
res.send(`用户ID: ${req.params.userId}`);
});
// 匹配 /user/123/books/456
app.get('/user/:userId/books/:bookId', (req, res) => {
res.send(`用户ID: ${req.params.userId}, 图书ID: ${req.params.bookId}`);
});
正则表达式参数
// 只匹配数字用户ID
app.get('/user/:userId(\d+)', (req, res) => {
res.send(`数字用户ID: ${req.params.userId}`);
});
🔍 查询参数
查询参数是URL中?
后面的部分,存储在req.query
对象中。
// 匹配 /search?q=javascript&category=tutorials
app.get('/search', (req, res) => {
const { q, category } = req.query;
res.send(`搜索: ${q}, 分类: ${category}`);
});
⚙️ 中间件
什么是中间件?
中间件函数是可以访问请求对象(req)、响应对象(res)和应用程序请求-响应循环中的下一个中间件函数的函数。下一个中间件函数通常由名为next
的参数表示。
中间件函数可以执行以下任务:
- 执行代码
- 修改请求和响应对象
- 结束请求-响应循环
- 调用堆栈中的下一个中间件
如果当前中间件函数没有结束请求-响应循环,它必须调用next()
将控制权传递给下一个中间件函数。否则,请求将被挂起。
中间件类型
Express应用程序可以使用以下类型的中间件:
- 应用级中间件
- 路由器级中间件
- 错误处理中间件
- 内置中间件
- 第三方中间件
应用级中间件
使用app.use()
和app.METHOD()
函数将应用级中间件绑定到应用程序对象实例。
const express = require('express');
const app = express();
// 不指定路径的中间件,每次请求都会执行
app.use((req, res, next) => {
console.log('时间:', Date.now());
next();
});
// 指定路径的中间件,只在路径匹配时执行
app.use('/user/:id', (req, res, next) => {
console.log('请求URL:', req.originalUrl);
next();
});
// 挂载到特定路径和方法
app.get('/user/:id', (req, res, next) => {
res.send(`用户信息: ${req.params.id}`);
});
路由器级中间件
路由器级中间件的工作方式与应用级中间件类似,只不过它绑定到express.Router()
实例。
const express = require('express');
const router = express.Router();
// 在路由器上使用中间件
router.use((req, res, next) => {
console.log('路由器级中间件');
next();
});
router.get('/user/:id', (req, res, next) => {
res.send(`用户ID: ${req.params.id}`);
});
app.use('/api', router);
错误处理中间件
错误处理中间件始终采用四个参数:(err, req, res, next)
。
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('服务器内部错误!');
});
内置中间件
Express提供了以下内置中间件函数:
express.static
- 提供静态文件express.json
- 解析JSON请求体express.urlencoded
- 解析URL编码的请求体
// 解析JSON请求体
app.use(express.json());
// 解析URL编码的请求体
app.use(express.urlencoded({ extended: true }));
// 提供静态文件
app.use(express.static('public'));
第三方中间件
安装并使用第三方中间件,例如morgan
日志中间件:
npm install morgan
const morgan = require('morgan');
app.use(morgan('combined'));
🔄 中间件执行顺序
中间件按照它们被加载的顺序执行。
const express = require('express');
const app = express();
// 第一个中间件
app.use((req, res, next) => {
console.log('第一个中间件');
next();
});
// 第二个中间件
app.use((req, res, next) => {
console.log('第二个中间件');
next();
});
// 路由处理器
app.get('/', (req, res) => {
res.send('Hello World!');
});
// 第三个中间件(不会执行,因为路由处理器已发送响应)
app.use((req, res, next) => {
console.log('第三个中间件');
next();
});
📁 模块化路由
使用express.Router
类创建模块化的路由处理器。
// routes/users.js
const express = require('express');
const router = express.Router();
// 中间件特定于此路由器
router.use((req, res, next) => {
console.log('用户路由时间:', Date.now());
next();
});
// 定义路由
router.get('/', (req, res) => {
res.send('用户首页');
});
router.get('/profile', (req, res) => {
res.send('用户资料');
});
module.exports = router;
// app.js
const usersRouter = require('./routes/users');
app.use('/users', usersRouter);
🛠️ 实践练习
创建一个简单的博客应用,包含以下路由:
- GET /posts - 获取所有文章列表
- GET /posts/:id - 获取特定文章
- POST /posts - 创建新文章
- PUT /posts/:id - 更新文章
- DELETE /posts/:id - 删除文章
实现一个日志中间件,记录每个请求的方法、URL和时间戳。
创建一个验证中间件,检查请求中是否包含API密钥。
使用express.Router组织博客应用的路由。