边缘函数框架实战:Hono、Itty Router 与轻量运行时

边缘函数框架深度实战:Hono 与 Itty Router 的中间件模型、路由与类型、Workers/Pages 与边缘运行时适配、单元测试与部署,帮你在 Cloudflare Workers 上写出可维护的边缘服务。

一、引言

裸用 addEventListener("fetch") 写边缘函数,三五条路由还能忍,一旦叠加鉴权、CORS、限流、多路由,代码就会迅速退化成「if 瀑布」。边缘函数框架解决的正是这个问题:用声明式的路由 + 中间件管道把横切逻辑组织起来,让边缘代码像写 Express 一样清晰。

本文聚焦两个主流轻量框架——Hono 与 Itty Router,拆解它们的路由模型、中间件机制、类型方案、运行时适配与测试部署,并给出「框架选型」的决策建议。适合已经在 Cloudflare Workers / Pages Functions 上写过基础函数、想升级工程化的读者。

二、先理解边缘运行时:Fetch API 标准

2.1 边缘函数运行的接口契约

几乎所有边缘运行时(Workers、Pages Functions、Deno Deploy、Bun、Vercel Edge)都实现了同一套 Web 标准接口:

请求:Request(method / url / headers / body)
响应:Response(status / headers / body)
入口:export default { fetch(request, env, ctx) } 或 addEventListener('fetch')

这套契约的好处是「一次编写,多处部署」。框架只需要在这层标准接口上做抽象,就能实现跨运行时的可移植性。

2.2 从裸 fetch 到框架的演进

// 裸写:路由全靠手写
export default {
  async fetch(request, env) {
    const url = new URL(request.url)
    if (url.pathname === '/api/users' && request.method === 'GET') {
      return Response.json({ users: [] })
    }
    if (url.pathname === '/health') {
      return Response.json({ ok: true })
    }
    return new Response('Not Found', { status: 404 })
  },
}
// 框架:路由声明式,中间件可复用
import { Hono } from 'hono'

const app = new Hono()

app.get('/api/users', (c) => c.json({ users: [] }))
app.get('/health', (c) => c.json({ ok: true }))
app.notFound((c) => c.text('Not Found', 404))

export default app

心法:框架不是「加魔法」,而是把 Web 标准接口的组织方式标准化——路由表、中间件链、上下文对象,这三件套就是边缘框架的全部核心。

三、Hono:类 Express 的边缘框架

3.1 为什么 Hono 适合边缘

Hono 是专为边缘运行时设计的 TypeScript 框架,几个关键特性:

特性说明
零依赖路由基于 Trie 树的路径匹配,编译后极小(约 14KB)
类 Express 心智app.get / app.use / app.post 上手成本低
多运行时适配Workers / Deno / Bun / Node / Vercel 一套代码
一等 TypeScript路由级类型推断,c.req.param 自动带类型
中间件生态官方提供 jwt、logger、cors、cache 等中间件

3.2 Hono 基础路由与上下文

import { Hono } from 'hono'

const app = new Hono()

// 路径参数 + 类型推断
app.get('/users/:id', (c) => {
  const id = c.req.param('id')   // id 自动推断为 string
  return c.json({ id })
})

// query 与 header
app.get('/search', (c) => {
  const q = c.req.query('q') ?? ''
  const lang = c.req.header('Accept-Language')
  return c.json({ q, lang })
})

// 设置响应头
app.post('/api/echo', async (c) => {
  const body = await c.req.json()
  c.header('X-Echoed', 'true')
  return c.json(body)
})

export default app

细节:c.req.param('id') 是 Hono 对类型系统最友好的设计——路由字面量里的占位符能推导出参数名,配合生成器连 c.req.param('id') 的类型都能自动得到。

3.3 Hono 的辅助函数

import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'

const app = new Hono()

app.use('*', logger())
app.use('/api/*', cors({ origin: 'https://example.com' }))

app.get('/', (c) => c.text('Hello'))
app.notFound((c) => c.json({ error: 'not_found' }, 404))
app.onError((err, c) => {
  console.error(err)
  return c.json({ error: 'internal' }, 500)
})

Hono 把 notFound 与 onError 也做成「路由」,这比裸 try-catch 更干净:404 与 500 都有唯一的处理点,中间件和路由共用同一套上下文。

四、Itty Router:极简路由库

4.1 Itty Router 的定位

Itty Router 是另一条路线:极简。它不是一个全功能框架,而是一个「路由 + 中间件」的最小集合,核心代码只有几百行,无任何依赖,适合:

- 只想做路由分发、不想学框架
- 希望包体最小(< 1KB)
- 与现有 Worker 代码渐进式整合

4.2 Itty Router 基本用法

import { Router } from 'itty-router'

const router = Router()

router
  .get('/', () => new Response('home'))
  .get('/users/:id', (req) => {
    const { id } = req.params
    return Response.json({ id })
  })
  .post('/api/echo', async (req) => {
    const body = await req.json()
    return Response.json(body)
  })
  .all('*', () => new Response('Not Found', { status: 404 }))

export default {
  async fetch(request) {
    return router.handle(request)
  },
}

对比:Itty Router 直接暴露原生 Request / Response,不做上下文封装——这意味着更少学习成本,也意味着「辅助方法」要自己写(如 JSON 响应封装)。Hono 则是「自带电池」,开箱即用。

4.3 Itty Router 中间件

import { Router } from 'itty-router'

const router = Router()

// 中间件:在路由之前执行,可提前返回响应
const withAuth = (request) => {
  const token = request.headers.get('Authorization')
  if (!token) {
    return new Response('Unauthorized', { status: 401 })
  }
  // 挂到 request 上,供后续路由读取
  request.user = { id: 'u_123' }
}

router
  .get('/me', withAuth, (request) => {
    return Response.json({ user: request.user })
  })
  .get('/health', () => Response.json({ ok: true }))

export default {
  async fetch(request) {
    return router.handle(request)
  },
}

Itty Router 的中间件就是「路由处理器数组里的任意函数」——它先于终处理器执行,若返回 Response 则短路,否则继续传参。这种「数组函数」模型直观且零魔法。

五、中间件模式:横切逻辑的组织方式

5.1 中间件链的执行模型

请求 → [logger] → [cors] → [auth] → [rate-limit] → 业务路由 → 响应
        ↑ 任一中间件 return Response 即短路,后续不再执行

5.2 用中间件实现统一鉴权

以 Hono 为例,把鉴权从业务路由里剥离:

import { Hono } from 'hono'
import { getCookie } from 'hono/cookie'

const app = new Hono()

// 鉴权中间件:校验会话,失败直接 401
app.use('/admin/*', async (c, next) => {
  const session = getCookie(c, 'session')
  const user = await validateSession(session)
  if (!user) return c.json({ error: 'unauthorized' }, 401)
  c.set('user', user)          // 把用户挂到上下文
  await next()                 // 放行
})

app.get('/admin/dashboard', (c) => {
  const user = c.get('user')   // 类型安全地读取
  return c.json({ welcome: user.name })
})

export default app

中间件模式的收益:

  1. 职责隔离:鉴权、CORS、日志各管一段,业务路由不再出现「if 没有 token」。
  2. 可组合:中间件按需挂载,/public/* 不挂鉴权、/admin/* 挂鉴权。
  3. 可测试:每个中间件可以独立注入测试。

5.3 中间件里的异步与错误

app.use('*', async (c, next) => {
  const start = Date.now()
  try {
    await next()
  } finally {
    const ms = Date.now() - start
    c.header('X-Timing', `${ms}ms`)
  }
})

铁律:中间件里 await next() 之后写「后处理」——这是与 Express 完全一致的洋葱模型。finally 保证无论下游成功还是抛错,计时逻辑都会执行。

六、类型安全与 RPC 风格开发

6.1 路由级类型推断

import { Hono } from 'hono'
import { hc } from 'hono/client'   // RPC 客户端

// 服务端定义
const app = new Hono()

app.get('/users/:id', (c) => {
  const id = c.req.param('id')
  return c.json({ id, name: 'Alice', age: 30 })
})

export type AppType = typeof app
export default app

// 客户端:完全类型安全的 fetch
// const client = hc<AppType>('/')
// const res = await client.users[':id'].$get({ param: { id: '1' } })
// const data = await res.json()  // 类型为 { id: string; name: string; age: number }

这是 Hono 最独特的卖点:服务端路由字面量直接生成类型化的客户端。前后端共享一个 AppType,路径、query、响应体全部静态检查,边缘 API 的联调成本降到最低。

6.2 类型安全的 env 与绑定

import { Hono } from 'hono'

// 定义绑定类型
type Env = {
  Bindings: {
    KV: KVNamespace
    API_KEY: string
  }
}

const app = new Hono<Env>()

app.get('/secret', (c) => {
  const key = c.env.API_KEY     // 类型为 string
  return c.json({ masked: key.slice(0, 4) + '****' })
})

通过泛型把 c.env 的类型固定下来,避免在代码里到处 (env as any)。绑定错名字、错类型在编译期就能发现。

七、运行时适配、测试与部署

7.1 一个框架适配多个运行时

// workers.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('hello'))
export default app
# 同一套代码,多目标构建
npx wrangler deploy --outdir dist/workers     # Cloudflare Workers
npx wrangler pages deploy dist                # Cloudflare Pages
deno run --allow-net main.ts                  # Deno Deploy
bun run src/index.ts                          # Bun / 本地开发

Hono 官方维护各运行时的适配层,c.req / c.res 会按平台差异自动桥接。选型时可以「先 Hono 后部署」,不锁死单一平台。

7.2 单元测试:用标准 Request/Response

// test/edge.test.ts
import { Hono } from 'hono'
import app from '../src/app'

const req = (path: string, init?: RequestInit) =>
  app.request(path, init)

Deno.test('GET /health 返回 ok', async () => {
  const res = await req('/health')
  assertEquals(res.status, 200)
  assertEquals(await res.json(), { ok: true })
})

Deno.test('GET /users/:id 带鉴权', async () => {
  const res = await req('/admin/dashboard', {
    headers: { Cookie: 'session=bad' },
  })
  assertEquals(res.status, 401)
})

测试的关键在于:框架不要求真实网络,直接 app.request() 就能构造请求——边缘函数的测试因此可以完全本地化、无需 mock 平台。

7.3 部署与版本回滚

# 生产部署(自动带版本号)
npx wrangler deploy --env production

# 回滚到上一个版本
npx wrangler rollback

# 灰度:先部署 10% 流量观察
npx wrangler deploy --env production --percentage 10

结合 部署与回滚策略 里的金丝雀思路,边缘函数同样可以按百分比放流量,把发布风险控制在最小范围。

八、总结

边缘函数框架的选型与落地,核心要点如下:

  1. 先吃透运行时契约:Request / Response / fetch 入口是边缘世界的通用语言,框架只是组织方式的标准化。
  2. Hono 适合完整工程:中间件生态、类型推断、RPC 客户端、多运行时适配,适合想严肃写边缘服务的团队。
  3. Itty Router 适合极简切入:几百行无依赖,适合只想做路由分发、与既有 Worker 渐进整合的场景。
  4. 中间件是横切逻辑的家:鉴权、CORS、日志、限流全部抽成中间件,业务路由保持干净。
  5. 类型安全值得投资:c.env 绑定类型、路由级 param 推断、hc() RPC 客户端,能把一类「手滑 bug」消灭在编译期。
  6. 测试不用 mock 网络:app.request() 直接本地构造请求,边缘代码可以跑出覆盖率。
  7. 部署与回滚要配套:用平台的版本回滚 + 百分比灰度,别让边缘函数成为不可回滚的「一次性代码」。

边缘函数不是「把代码塞到离用户近的地方」这么简单——它是带工程纪律的分布式计算。框架选对、中间件组织好,再配合 边缘缓存策略 的缓存分层与 API 网关与 BFF 的聚合思想,边缘服务才能真正又小又稳。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「tools」更多文章

  1. AI 网关与模型路由:多模型统一入口、fallback、限流与成本控制
  2. 密钥与环境配置:Vercel、Cloudflare 环境变量与密钥轮换实战
  3. Web 安全加固:CSP、HSTS、安全响应头与 XSS 防护实战