Node系列 · Express:路由
一个 Express 项目动辄几十上百个接口——全部堆在
app.js里会迅速失控。express.Router()把路由按业务模块拆成独立文件,是 Express 项目结构的标准做法。
一、express.Router() 是什么
Router 是带路径前缀的迷你 app——可以注册中间件、挂载路由、最后 app.use() 引入:
javascript
const express = require('express');
const router = express.Router();
router.get('/users', listUsers);
router.post('/users', createUser);
module.exports = router;挂载到主应用:
javascript
const userRoutes = require('./routes/users');
app.use('/api', userRoutes);
// 现在 GET /api/users 触发 listUsers二、模块化路由拆分
项目结构:
text
app.js
routes/
├── users.js
├── posts.js
└── auth.js
controllers/
├── userController.js
├── postController.js
└── authController.js2.1 路由文件(routes/users.js)
javascript
const express = require('express');
const router = express.Router();
const ctrl = require('../controllers/userController');
router.get('/', ctrl.list);
router.get('/:id', ctrl.detail);
router.post('/', ctrl.create);
router.put('/:id', ctrl.update);
router.delete('/:id', ctrl.remove);
module.exports = router;2.2 控制器(controllers/userController.js)
javascript
exports.list = async (req, res, next) => {
try {
const users = await User.findAll();
res.json({ users });
} catch (err) {
next(err);
}
};
exports.detail = async (req, res, next) => {
try {
const user = await User.findByPk(req.params.id);
if (!user) return res.status(404).json({ error: 'Not found' });
res.json(user);
} catch (err) {
next(err);
}
};
exports.create = async (req, res, next) => {
try {
const user = await User.create(req.body);
res.status(201).json(user);
} catch (err) {
next(err);
}
};
exports.update = async (req, res, next) => {
try {
const [count] = await User.update(req.body, {
where: { id: req.params.id },
});
if (count === 0) return res.status(404).json({ error: 'Not found' });
res.json({ id: req.params.id, ...req.body });
} catch (err) {
next(err);
}
};
exports.remove = async (req, res, next) => {
try {
const count = await User.destroy({ where: { id: req.params.id } });
if (count === 0) return res.status(404).json({ error: 'Not found' });
res.status(204).end();
} catch (err) {
next(err);
}
};2.3 主入口(app.js)
javascript
const express = require('express');
const app = express();
// 通用中间件
app.use(express.json());
app.use(require('morgan')('dev'));
// 业务路由
app.use('/api/users', require('./routes/users'));
app.use('/api/posts', require('./routes/posts'));
app.use('/api/auth', require('./routes/auth'));
// 404 + 错误处理
app.use((req, res) => res.status(404).json({ error: 'Not found' }));
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: 'Server error' });
});
app.listen(3000);每个业务模块自包含 router + controller,主入口只负责装配——单一职责。
三、路由参数
3.1 路径参数
javascript
router.get('/users/:id', (req, res) => {
console.log(req.params.id); // 字符串
});
router.get('/posts/:postId/comments/:commentId', (req, res) => {
const { postId, commentId } = req.params;
});3.2 带正则约束
javascript
router.get('/users/:id(\\d+)', (req, res) => {
// 只匹配数字 id
});
router.get('/files/:filename(*)', (req, res) => {
// filename 可以包含斜杠
});3.3 Query 参数
javascript
router.get('/search', (req, res) => {
console.log(req.query); // { keyword: 'node', page: '2' }
});
// GET /search?keyword=node&page=23.4 三种参数的区别
| 类型 | 来源 | 示例 |
|---|---|---|
| 路径参数 | URL 路径段 | /users/:id → req.params.id |
| Query 参数 | URL ? 后 | /search?q=x → req.query.q |
| Body 参数 | 请求体 | POST /api + JSON → req.body |
四、route() 链式调用
同一路径不同方法用 route() 集中:
javascript
router.route('/users')
.get(listUsers)
.post(createUser)
.put(updateAllUsers); // 批量更新
router.route('/users/:id')
.get(detailUser)
.put(updateUser)
.patch(partialUpdateUser)
.delete(removeUser);每条路径只写一次,方法链式——便于看清"对同一个资源的所有操作"。
五、路由级中间件
可以给某个 router 单独挂中间件:
javascript
const router = express.Router();
// 仅该 router 生效
router.use(requireAuth);
router.get('/private/data', (req, res) => {
res.json({ secret: '只有登录用户能看到' });
});也可以给特定路径:
javascript
router.use('/admin', requireAdmin);
router.get('/admin/users', listAllUsers);六、嵌套 router
Router 可以嵌套:
javascript
const postRoutes = require('./posts');
const router = express.Router();
router.use('/:postId/comments', postRoutes);
// /api/posts/:postId/comments 触发 posts.js 里的 router
app.use('/api/posts', router);适合复杂的多层资源路由。
七、最佳实践
| 场景 | 推荐 |
|---|---|
| 项目结构 | routes/ + controllers/ 分层 |
| 路由命名 | 复数资源名(/users、/posts) |
| 路径参数 | :id 不要加类型前缀(用 :userId 更好) |
| 控制器函数 | try/catch + next(err) 把错误传给错误中间件 |
| 路由分组 | 按业务模块(/api/users、/api/posts) |
| 鉴权 | 路由级中间件 router.use(requireAuth) |
八、小结
express.Router()创建模块化路由——主入口只负责装配- 推荐结构:
routes/(路由)+controllers/(业务逻辑) - 三种参数:
req.params(路径)/req.query(query)/req.body(body) route()链式:同一资源的所有方法集中在一处- 路由级中间件:用
router.use(auth)只对该路由生效 - 复杂项目用嵌套 router,多层资源路径清晰
