Express路由系统详解
1. 路由基础回顾
路由是指应用程序如何响应客户端对特定端点的请求,这些端点由URI(或路径)和HTTP请求方法(GET、POST等)定义。在Express中,路由的基本结构如下:
app.METHOD(PATH, HANDLER)
其中:
app是Express实例METHOD是HTTP请求方法PATH是服务器上的路径HANDLER是当路由匹配时执行的函数
2. 路由方法
Express支持所有HTTP请求方法,最常用的包括:
2.1 GET路由
用于获取资源:
app.get('/users', (req, res) => {
res.send('获取用户列表')
})
2.2 POST路由
用于创建新资源:
app.post('/users', (req, res) => {
res.send('创建新用户')
})
2.3 PUT路由
用于更新现有资源:
app.put('/users/:id', (req, res) => {
res.send(`更新ID为${req.params.id}的用户`)
})
2.4 DELETE路由
用于删除资源:
app.delete('/users/:id', (req, res) => {
res.send(`删除ID为${req.params.id}的用户`)
})
2.5 特殊路由方法
- app.all(): 匹配所有HTTP方法
- app.use(): 用于中间件,通常不指定特定路径
3. 路由路径
路由路径与请求方法结合,定义了可以访问应用程序的端点。Express支持多种路由路径定义方式:
3.1 字符串路径
app.get('/about', (req, res) => {
res.send('关于我们')
})
3.2 字符串模式路径
使用特殊字符定义匹配模式:
// 匹配 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, abRABDOMcd, ab123cd 等
app.get('/ab*cd', (req, res) => {
res.send('ab*cd')
})
3.3 正则表达式路径
使用正则表达式进行更复杂的匹配:
// 匹配任何包含 a 的路径
app.get(/a/, (req, res) => {
res.send('/a/')
})
// 匹配 butterfly 和 dragonfly,但不匹配 butterflyman, dragonflyman 等
app.get(/.*fly$/, (req, res) => {
res.send('/.*fly$/')
})
4. 路由参数
路由参数是命名的URL段,用于捕获URL中指定位置的值。捕获的值填充到req.params对象中:
app.get('/users/:userId/books/:bookId', (req, res) => {
res.send(req.params)
// 对于 /users/34/books/8989,输出 { "userId": "34", "bookId": "8989" }
})
路由参数的名称必须由"单词字符"([A-Za-z0-9_])组成。
5. 路由处理程序
路由可以有多个回调函数,它们的行为类似于中间件,但有一个关键区别:这些回调函数可能会调用next('route')跳过其余的路由回调。
5.1 多个回调函数
app.get('/example/b', (req, res, next) => {
console.log('响应将由下一个函数发送')
next()
}, (req, res) => {
res.send('Hello from B!')
})
5.2 回调函数数组
const cb0 = (req, res, next) => {
console.log('CB0')
next()
}
const cb1 = (req, res, next) => {
console.log('CB1')
next()
}
const cb2 = (req, res) => {
res.send('Hello from C!')
}
app.get('/example/c', [cb0, cb1, cb2])
5.3 混合使用函数和数组
app.get('/example/d', [cb0, cb1], (req, res, next) => {
console.log('路由处理程序的剩余部分')
next()
}, (req, res) => {
res.send('Hello from D!')
})
6. 路由器
Express提供了一个Router对象,可以看作是一个"迷你应用程序",它可以执行中间件和路由功能,但不能独立启动服务器。
6.1 创建路由器
const express = require('express')
const router = express.Router()
6.2 在路由器上定义路由
// 中间件特定于该路由器
router.use((req, res, next) => {
console.log('Time: ', Date.now())
next()
})
// 定义根路由
router.get('/', (req, res) => {
res.send('Birds home page')
})
// 定义 about 路由
router.get('/about', (req, res) => {
res.send('About birds')
})
6.3 将路由器挂载到应用程序
app.use('/birds', router)
现在,应用程序将能够处理像 /birds/ 和 /birds/about 这样的请求。
7. 路由模块化
将相关路由组织到单独的文件中是一种良好的实践,这有助于保持代码的可维护性。
7.1 创建路由模块文件
创建 routes/users.js:
const express = require('express')
const router = express.Router()
// 获取所有用户
router.get('/', (req, res) => {
res.send('获取所有用户')
})
// 创建新用户
router.post('/', (req, res) => {
res.send('创建新用户')
})
// 获取单个用户
router.get('/:id', (req, res) => {
res.send(`获取ID为${req.params.id}的用户`)
})
module.exports = router
7.2 在主应用程序中使用路由模块
const express = require('express')
const app = express()
const usersRouter = require('./routes/users')
app.use('/users', usersRouter)
8. 高级路由技巧
8.1 链式路由处理
Express允许链式调用路由方法,这对于简化代码很有用:
app.route('/book')
.get((req, res) => {
res.send('获取图书列表')
})
.post((req, res) => {
res.send('创建新书')
})
.put((req, res) => {
res.send('更新书')
})
8.2 路由前缀
为一组路由添加统一的前缀:
// 所有以 /api 开头的请求都会使用这个路由器
app.use('/api', apiRouter)
8.3 路由中间件组合
为路由组应用特定的中间件:
// 为所有用户路由应用身份验证中间件
app.use('/users', authMiddleware, usersRouter)
9. 路由最佳实践
- 模块化路由:将相关路由组织到单独的文件中
- 使用路由器:利用Express Router创建可重用的路由处理程序
- 遵循RESTful约定:使用适当的HTTP方法表示操作类型
- 验证输入:始终验证和清理用户输入
- 错误处理:实现合理的错误处理逻辑
- 文档化路由:使用工具如Swagger记录API路由
10. 练习项目
创建一个模块化的Express应用程序,包含以下功能:
- 用户管理路由模块
- 产品管理路由模块
- 订单管理路由模块
- 适当的中间件应用
通过这个练习,你将掌握Express路由系统的高级用法,为构建复杂的Web应用程序打下坚实的基础。