Skip to content

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.js

2.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=2

3.4 三种参数的区别

类型来源示例
路径参数URL 路径段/users/:idreq.params.id
Query 参数URL ?/search?q=xreq.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,多层资源路径清晰