Express 路由:请求与响应之间的映射

2 min read
Zekari
后端开发路由设计

路由是 Web 应用的导航系统。它决定了当用户发送一个请求时,服务器应该如何响应。

这个概念很简单:用户访问一个 URL,服务器返回对应的内容。但正是这种简单性,让路由成为构建 Web 应用最核心的机制之一。

路由是一种映射关系

本质上,路由就是建立 URL 和处理函数之间的映射。

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

这行代码说:当有人访问 /users 时,执行这个函数。就这么简单。

💡 Click the maximize icon to view in fullscreen

路由系统做的事情不复杂:接收请求,找到对应的函数,执行它。但这个简单的机制支撑了整个 Web 应用的运转。

为什么需要明确的映射

没有路由,服务器就是一个黑盒。用户发送请求,却不知道会得到什么。路由让这个过程变得可预测。

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

路由参数让 URL 变得动态。/users/1/users/2 是不同的请求,但它们共享同一个处理逻辑。这种模式很常见:URL 表达意图,参数表达细节。

路由让服务器的行为变得明确。开发者定义规则,用户遵循规则,系统就能运转。

不同的 HTTP 方法代表不同的意图

Express 路由不只是处理 URL,还要处理 HTTP 方法。同样的 URL,不同的方法,意味着不同的操作。

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

app.post('/users', (req, res) => {
  // 创建新用户
})

app.put('/users/:id', (req, res) => {
  // 更新用户信息
})

app.delete('/users/:id', (req, res) => {
  // 删除用户
})

这就是 RESTful 设计的基础。URL 表示资源,HTTP 方法表示操作。GET 是读取,POST 是创建,PUT 是更新,DELETE 是删除。

HTTP 方法不只是技术约定,它们是语义层面的设计。选择正确的方法能让 API 更容易理解和使用。

  • GET:安全且幂等,不改变服务器状态
  • POST:创建新资源,不是幂等的
  • PUT:更新整个资源,幂等
  • PATCH:更新部分资源
  • DELETE:删除资源,幂等

幂等性很重要。如果一个请求失败了,客户端可以安全地重试幂等操作,而不用担心产生副作用。

中间件让路由变得可组合

Express 的强大之处在于中间件。路由不只是简单的映射,它是一个处理链。

const authenticate = (req, res, next) => {
  if (req.headers.authorization) {
    next()
  } else {
    res.status(401).send('未授权')
  }
}

app.get('/dashboard', authenticate, (req, res) => {
  res.send('仪表板内容')
})

authenticate 是一个中间件。它在路由处理函数之前运行,决定是否继续执行。如果验证失败,请求就在这里终止。

💡 Click the maximize icon to view in fullscreen

中间件让功能可以组合。认证、日志、错误处理——这些横切关注点不需要在每个路由里重复。它们是独立的函数,可以按需组合。

路由分组让代码更清晰

当应用变大,路由也会增多。把所有路由写在一起会变得混乱。Express 提供了 Router 来组织路由。

// users.js
const express = require('express')
const router = express.Router()

router.get('/', (req, res) => {
  res.send('用户列表')
})

router.get('/:id', (req, res) => {
  res.send(`用户 ${req.params.id}`)
})

module.exports = router

// app.js
const usersRouter = require('./users')
app.use('/users', usersRouter)

这样,所有用户相关的路由都在 users.js 里。主文件保持简洁,每个模块专注于自己的职责。

路由分组不只是组织代码,它也反映了系统的逻辑结构。/users 是一个资源,/posts 是另一个资源,它们各自独立。

错误处理是路由设计的一部分

路由不只是处理成功的情况,还要处理失败。Express 的错误处理中间件专门用来捕获错误。

app.get('/users/:id', async (req, res, next) => {
  try {
    const user = await getUserById(req.params.id)
    res.send(user)
  } catch (error) {
    next(error)
  }
})

app.use((err, req, res, next) => {
  console.error(err)
  res.status(500).send('服务器错误')
})

next(error) 把错误传递给错误处理中间件。这种机制让错误处理集中化,不需要在每个路由里写 try-catch

Express 的错误处理中间件必须放在所有路由之后。顺序很重要:

// 先定义路由
app.get('/users', handler)

// 最后定义错误处理
app.use((err, req, res, next) => {
  // 处理错误
})

如果把错误处理放在前面,它就捕获不到后面路由的错误。

路由的性能考虑

路由匹配是有成本的。Express 按顺序检查每个路由,直到找到匹配的。

app.get('/users/:id', handler1)
app.get('/users/new', handler2)

这个顺序有问题。/users/new 会被第一个路由匹配,因为 :id 可以匹配任何值。正确的做法是把更具体的路由放在前面:

app.get('/users/new', handler2)
app.get('/users/:id', handler1)

路由的顺序影响匹配逻辑。越具体的规则应该越早定义。

最后

路由是 Web 应用的骨架。它定义了服务器如何响应不同的请求,如何组织功能,如何处理错误。

Express 的路由设计很简洁:一个 URL,一个方法,一个处理函数。但这种简洁性支撑了无数复杂的应用。

理解路由不只是学习语法,而是理解服务器如何与外界交互。每个路由都是一个承诺:当你发送这个请求,我会给你这个响应。信任建立在这些承诺之上。