路由系统详解:从入门到实战

路由,说白了就是API的「门牌号」。客户端发来请求,服务器得知道该找谁处理。我刚开始学Express时,觉得路由不就是个路径匹配嘛,后来踩过坑才明白——路由设计的好坏,直接决定了API的可维护性。

路由概念:请求的「导航系统」

每个HTTP请求都包含两个核心信息:路径(path)方法(method)。路由的作用,就是根据这两个信息,把请求分发给对应的处理函数。

核心公式:路由 = 路径 + HTTP方法 + 处理函数

举个例子:

app.get('/users', (req, res) => {
  res.send('获取用户列表');
});

这段代码的意思是:当客户端用GET方法访问/users路径时,执行后面的回调函数。嗯,就是这么简单。

HTTP方法:CRUD的「动作指令」

RESTful API中,不同的HTTP方法对应不同的操作。我个人习惯这样对应:

HTTP方法 对应操作 典型场景
GET 查询 获取资源列表或详情
POST 创建 新增用户、提交订单
PUT 全量更新 替换整个资源
DELETE 删除 移除资源

你想想看,如果所有操作都用GET,那API就乱套了。我在项目中遇到过团队新人把所有请求都写成GET,结果删除操作也能被浏览器直接访问,那叫一个惊险。

避坑指南:我曾经见过一个项目,用GET方法做删除操作,结果搜索引擎爬虫把整个数据库删光了。记住:修改数据一定要用POST/PUT/DELETE,别偷懒。

路由参数:动态路径的「占位符」

实际开发中,路径往往是动态的。比如获取某个用户的详情,路径可能是/users/123,其中123是用户ID。这时候就需要路由参数。

app.get('/users/:id', (req, res) => {
  const userId = req.params.id;
  res.send(`获取用户 ${userId} 的信息`);
});

:id就是路由参数,Express会自动解析并挂载到req.params上。我个人习惯给参数起有意义的名字,比如:userId:orderId,而不是:a:b——你想想看,三个月后回来看代码,谁还记得:a是啥?

还可以有多个参数:

app.get('/users/:userId/orders/:orderId', (req, res) => {
  const { userId, orderId } = req.params;
  res.send(`用户 ${userId} 的订单 ${orderId}`);
});

小技巧:路由参数默认是字符串。如果需要数字,记得用parseIntNumber转换。我曾经因为忘记转换,导致1 + 1 = '11'的诡异bug,排查了半天。

路由处理函数:请求的「终点站」

每个路由最终都要有一个处理函数。这个函数接收两个核心参数:req(请求对象)和res(响应对象)。

app.post('/users', (req, res) => {
  // 1. 获取请求体数据
  const { name, email } = req.body;
  
  // 2. 业务逻辑处理
  const newUser = { id: Date.now(), name, email };
  
  // 3. 返回响应
  res.status(201).json(newUser);
});

处理函数里一般做三件事:解析请求 → 处理业务 → 返回响应。我建议保持函数简短,如果逻辑复杂,就拆成多个中间件或单独的函数。

经验之谈:处理函数里不要写超过50行的代码。如果超过,说明该拆分了。我在重构一个老项目时,见过一个路由处理函数写了300行——那简直是灾难。

链式路由:优雅的「路由组织」

同一个路径,不同方法,如果分开写,代码会显得散乱。链式路由可以解决这个问题:

app.route('/users')
  .get((req, res) => {
    res.send('获取用户列表');
  })
  .post((req, res) => {
    res.send('创建新用户');
  })
  .put((req, res) => {
    res.send('批量更新用户');
  })
  .delete((req, res) => {
    res.send('删除所有用户');
  });

你看,这样是不是清晰多了?所有针对/users的操作都集中在一起。我个人非常喜欢这种写法,尤其是在写RESTful API时,一个资源对应一个route(),维护起来特别方便。

链式路由还可以配合中间件:

app.route('/users/:id')
  .all((req, res, next) => {
    // 这个中间件对所有方法生效
    console.log(`访问用户 ${req.params.id}`);
    next();
  })
  .get((req, res) => {
    res.send(`获取用户 ${req.params.id}`);
  })
  .put((req, res) => {
    res.send(`更新用户 ${req.params.id}`);
  })
  .delete((req, res) => {
    res.send(`删除用户 ${req.params.id}`);
  });

实用技巧:.all()方法可以捕获所有HTTP方法,适合做权限校验、日志记录等通用操作。我经常用它来做「这个资源是否存在」的预检查。

实战建议:路由设计原则

说了这么多,最后分享几个我在项目中总结的原则:

  1. 路径用名词复数/users而不是/user/orders而不是/getOrder
  2. 嵌套不要超过三层/users/:userId/orders可以,但/users/:userId/orders/:orderId/items/:itemId就太深了
  3. 版本号放路径里/api/v1/users,方便后续升级
  4. 路由参数要有意义:别用:a:b这种让人猜谜的命名

嗯,路由系统就讲到这里。说白了,路由就是API的「交通指挥员」,设计好了,整个项目都清爽。下一章我们聊聊中间件——那才是Express真正的灵魂。