Skip to content

路由与类型魔法

farrow-http 最强大的特性之一就是类型安全的路由系统。URL 模式会自动推导参数类型,并在运行时验证。

路径参数的自动推导

还记得 Express 里要手动 parseInt() 吗? farrow-http 说:"不需要!"

typescript
// 🎯 自动类型推导和验证
app.get('/users/<id:int>').use((req) => {
  req.params.id  // 类型: number (已验证为整数)
  return Response.json({ userId: req.params.id })
})

app.get('/posts/<slug:string>').use((req) => {
  req.params.slug  // 类型: string
  return Response.json({ postSlug: req.params.slug })
})

app.get('/products/<price:float>').use((req) => {
  req.params.price  // 类型: number (浮点数)
  return Response.json({ price: req.params.price })
})

支持的类型:

  • <id:int>number (整数)
  • <name:string>string
  • <price:float>number (浮点数)
  • <active:boolean>boolean
  • <userId:id>string (非空标识符)

可选参数和数组参数

typescript
// 可选参数 (?)
app.get('/articles/<category:string>/<id?:int>').use((req) => {
  const { category, id } = req.params
  // category: string (必需)
  // id?: number (可选)

  if (id) {
    return Response.json({ message: `分类 ${category} 的第 ${id} 篇文章` })
  }
  return Response.json({ message: `${category} 分类的所有文章` })
})

// 一个或多个 (+)
app.get('/tags/<tags+:string>').use((req) => {
  req.params.tags  // 类型: string[] (至少一个)
  return Response.json({ tags: req.params.tags })
})

// 零个或多个 (*)
app.get('/categories/<cats*:string>').use((req) => {
  req.params.cats  // 类型: string[] | undefined
  return Response.json({ categories: req.params.cats || [] })
})

联合类型 (枚举值)

typescript
// 只接受指定的值
app.get('/news/<category:tech|business|sports>').use((req) => {
  // req.params.category: 'tech' | 'business' | 'sports'
  return Response.json({ category: req.params.category })
})

// 字面量类型 (花括号)
app.get('/api/<version:{v1}|{v2}>').use((req) => {
  // req.params.version: 'v1' | 'v2'
  return Response.json({ version: req.params.version })
})

查询参数

typescript
// 查询参数自动验证
app.get('/search?<q:string>&<page?:int>&<limit?:int>').use((req) => {
  const { q, page = 1, limit = 10 } = req.query
  // q: string (必需)
  // page?: number (可选,默认值 1)
  // limit?: number (可选,默认值 10)

  return Response.json({
    query: q,
    page,
    limit,
    results: []
  })
})

// 混合路径参数和查询参数
app.get('/<category:string>?<sort:asc|desc>&<minPrice?:float>').use((req) => {
  const { category } = req.params
  const { sort, minPrice } = req.query
  // category: string
  // sort: 'asc' | 'desc'
  // minPrice?: number

  return Response.json({ category, sort, minPrice })
})

HTTP 方法

typescript
// GET
app.get('/users').use(() => {
  return Response.json({ users: [] })
})

// POST
app.post('/users').use((req) => {
  return Response.status(201).json({ created: true })
})

// PUT
app.put('/users/<id:int>').use((req) => {
  return Response.json({ updated: req.params.id })
})

// PATCH
app.patch('/users/<id:int>').use((req) => {
  return Response.json({ patched: req.params.id })
})

// DELETE
app.delete('/users/<id:int>').use((req) => {
  return Response.status(204).empty()
})

// HEAD
app.head('/users/<id:int>').use((req) => {
  return Response.status(200).empty()
})

// OPTIONS
app.options('/users').use(() => {
  return Response
    .status(200)
    .header('Allow', 'GET, POST, PUT, DELETE')
    .empty()
})

路由模块化

typescript
import { Router } from 'farrow-http'

// 创建独立路由器
const userRouter = Router()

userRouter.get('/').use(() => {
  return Response.json({ users: getAllUsers() })
})

userRouter.get('/<id:int>').use((req) => {
  const user = getUserById(req.params.id)
  if (!user) {
    return Response.status(404).json({ error: '用户不存在' })
  }
  return Response.json(user)
})

// 主应用挂载路由
const app = Http()
app.route('/api/users').use(userRouter)

// URL 映射:
// GET /api/users     → userRouter.get('/')
// GET /api/users/123 → userRouter.get('/<id:int>')

嵌套路由

typescript
const app = Http({ basenames: ['/api'] })

// 第一层: /api
const v1Router = app.route('/v1')        // /api/v1
const v2Router = app.route('/v2')        // /api/v2

// 第二层: /api/v1/...
const userRouter = Router()
const postRouter = Router()

v1Router.route('/users').use(userRouter)  // /api/v1/users
v1Router.route('/posts').use(postRouter)  // /api/v1/posts

// 第三层: /api/v1/users/...
userRouter.get('/').use(() => {
  return Response.json({ users: [] })      // GET /api/v1/users
})

userRouter.get('/<id:int>').use((req) => {
  return Response.json({ userId: req.params.id })  // GET /api/v1/users/123
})

动态路由匹配

typescript
// 通配符路由 (捕获所有)
app.get('/*').use((req) => {
  return Response.json({ path: req.pathname })
})

// 特定前缀
app.route('/api/*').use((req) => {
  return Response.json({ apiPath: req.pathname })
})

实用示例

typescript
const app = Http()

// 资源 CRUD
const bookRouter = Router()

bookRouter.get('/').use(() => {
  return Response.json({ books: getBooks() })
})

bookRouter.get('/<id:int>').use((req) => {
  const book = getBookById(req.params.id)
  if (!book) {
    return Response.status(404).json({ error: 'Book not found' })
  }
  return Response.json(book)
})

bookRouter.post('/').use((req) => {
  const book = createBook(req.body)
  return Response.status(201).json(book)
})

bookRouter.put('/<id:int>').use((req) => {
  const book = updateBook(req.params.id, req.body)
  return Response.json(book)
})

bookRouter.delete('/<id:int>').use((req) => {
  deleteBook(req.params.id)
  return Response.status(204).empty()
})

app.route('/api/books').use(bookRouter)

这是一个第三方 Farrow 文档站 | 用 ❤️ 和 TypeScript 构建