TL;DR
Hono 是超快的多运行时 Web 框架(比 Express 快 2.5 倍),内置 TypeScript、中间件、Zod 验证、JWT 认证、CORS、OpenAPI 和 RPC 模式。
Key Takeaways
- 无需修改代码即可在所有主流 JavaScript 运行时上运行。
- 内置 JWT、CORS、ETag、日志和压缩中间件。
- 基于 Zod 的验证提供运行时类型检查和自动 TypeScript 推断。
- RPC 模式提供端到端类型安全,无需代码生成。
- OpenAPI 集成自动生成 Swagger 文档。
- 在 Bun 上每秒处理 130K+ 请求,性能超过 Express 2.5 倍。
Hono 是一个小巧、简洁、超快的 Web 框架,专为边缘计算设计。凭借一流的 TypeScript 支持和零依赖,Hono 几乎可以在所有 JavaScript 运行时上运行。
什么是 Hono?
Hono(日语"火焰")是 2022 年创建的轻量级 Web 框架,使用 Web 标准 API,无需 polyfill 即可跨运行时移植。
- 超快:RegExpRouter 在 Bun 上达到 130K+ 请求/秒
- 极小:核心约 14KB,零外部依赖
- 多运行时:Cloudflare Workers、Deno、Bun、Node.js、AWS Lambda
- 类型安全:一流 TypeScript 支持和 RPC 模式
- 丰富中间件:内置 JWT、CORS、ETag、日志、压缩等
- Web 标准:基于 Request/Response 构建
安装和设置
Hono 为每个运行时提供启动模板。
# Create a new Hono project
npm create hono@latest my-app
# Select your target runtime:
# cloudflare-workers / deno / bun / nodejs / aws-lambda
cd my-app && npm install
# Or install manually
npm install hono
# Project structure
# my-app/
# ├── src/index.ts # Entry point
# ├── package.json
# ├── tsconfig.json
# └── wrangler.toml # (Cloudflare only)路由
Hono 提供强大的路由系统,支持路径参数、通配符和正则约束。
基本路由
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
app.post('/users', async (c) => {
const body = await c.req.json()
return c.json({ id: 1, ...body }, 201)
})
app.put('/users/:id', async (c) => {
const id = c.req.param('id')
const body = await c.req.json()
return c.json({ id, ...body })
})
app.delete('/users/:id', (c) => {
return c.json({ deleted: c.req.param('id') })
})
app.on(['PUT', 'PATCH'], '/items/:id', (c) => {
return c.json({ updated: true })
})
app.all('/api/*', (c) => c.json({ method: c.req.method }))
export default app分组和嵌套路由
使用 app.route() 将路由组织成逻辑组。
import { Hono } from 'hono'
// routes/users.ts
const users = new Hono()
users.get('/', (c) => c.json([{ id: 1, name: 'Alice' }]))
users.get('/:id', (c) => c.json({ id: c.req.param('id') }))
users.post('/', async (c) => c.json(await c.req.json(), 201))
// routes/posts.ts
const posts = new Hono()
posts.get('/', (c) => c.json([{ id: 1, title: 'Hello' }]))
posts.get('/:id', (c) => c.json({ id: c.req.param('id') }))
// Main app — mount route groups
const app = new Hono()
app.route('/api/users', users)
app.route('/api/posts', posts)
export default app路由参数和通配符
// Named parameters
app.get('/users/:id', (c) => c.json({ id: c.req.param('id') }))
// Multiple parameters
app.get('/orgs/:orgId/repos/:repoId', (c) => {
const { orgId, repoId } = c.req.param()
return c.json({ orgId, repoId })
})
// Optional parameter
app.get('/articles/:slug/:format?', (c) => {
const format = c.req.param('format') || 'html'
return c.json({ slug: c.req.param('slug'), format })
})
// Wildcard
app.get('/files/*', (c) => c.text('File: ' + c.req.path))
// Regex constraint
app.get('/posts/:id{[0-9]+}', (c) => {
return c.json({ id: Number(c.req.param('id')) })
})中间件
Hono 中间件遵循洋葱模型,内置丰富中间件且易于自定义。
内置中间件
import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
import { etag } from 'hono/etag'
import { compress } from 'hono/compress'
import { secureHeaders } from 'hono/secure-headers'
import { basicAuth } from 'hono/basic-auth'
import { bearerAuth } from 'hono/bearer-auth'
import { prettyJSON } from 'hono/pretty-json'
const app = new Hono()
app.use('*', logger()) // Log method, path, status
app.use('/api/*', cors()) // Cross-origin requests
app.use('*', etag()) // Caching headers
app.use('*', compress()) // Gzip/brotli
app.use('*', secureHeaders()) // CSP, X-Frame-Options
app.use('*', prettyJSON()) // ?pretty for formatted output
app.use('/admin/*', basicAuth({
username: 'admin', password: 'secret',
}))
app.use('/api/private/*', bearerAuth({
token: 'my-secret-token',
}))自定义中间件
import { createMiddleware } from 'hono/factory'
// Timing middleware
const timer = createMiddleware(async (c, next) => {
const start = Date.now()
await next()
c.header('X-Response-Time', (Date.now() - start) + 'ms')
})
// Typed auth middleware
type Env = { Variables: { user: { id: string; role: string } } }
const auth = createMiddleware<Env>(async (c, next) => {
const token = c.req.header('Authorization')
if (!token) return c.json({ error: 'Unauthorized' }, 401)
c.set('user', { id: '123', role: 'admin' })
await next()
})
const app = new Hono<Env>()
app.use('*', timer)
app.use('/protected/*', auth)
app.get('/protected/profile', (c) => {
const user = c.get('user') // fully typed!
return c.json({ user })
})请求处理
Hono 上下文对象提供便捷方法访问请求数据。
app.post('/upload', async (c) => {
// Headers
const contentType = c.req.header('Content-Type')
// Query parameters
const page = c.req.query('page') // single
const tags = c.req.queries('tag') // array
// JSON body
const json = await c.req.json()
// Form data
const form = await c.req.formData()
const file = form.get('file') as File
// URL info
const url = c.req.url // full URL
const path = c.req.path // pathname
const method = c.req.method // HTTP method
// Path parameters
const id = c.req.param('id')
// Raw body
const text = await c.req.text()
const buffer = await c.req.arrayBuffer()
return c.json({ received: true })
})响应辅助函数
Hono 提供 JSON、HTML、文本、重定向、流式等响应辅助函数。
// JSON, text, HTML responses
app.get('/json', (c) => c.json({ message: 'hello' }))
app.get('/error', (c) => c.json({ error: 'not found' }, 404))
app.get('/text', (c) => c.text('Hello World'))
app.get('/page', (c) => c.html('<h1>Hello</h1>'))
// Redirects
app.get('/old', (c) => c.redirect('/new'))
app.get('/moved', (c) => c.redirect('/permanent', 301))
// Headers and status
app.get('/custom', (c) => {
c.header('X-Custom', 'value')
c.header('Cache-Control', 'max-age=3600')
c.status(201)
return c.json({ created: true })
})
// Streaming
app.get('/stream', (c) => {
return c.streamText(async (stream) => {
for (let i = 0; i < 5; i++) {
await stream.writeln('Chunk ' + i)
await stream.sleep(1000)
}
})
})Zod 验证
Hono 通过 @hono/zod-validator 与 Zod 集成。
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'
// Validate JSON body
const userSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).optional(),
})
app.post('/users', zValidator('json', userSchema), (c) => {
const body = c.req.valid('json') // fully typed
return c.json({ user: body }, 201)
})
// Validate query parameters
const listSchema = z.object({
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().max(100).default(20),
search: z.string().optional(),
})
app.get('/users', zValidator('query', listSchema), (c) => {
const { page, limit, search } = c.req.valid('query')
return c.json({ page, limit, search })
})
// Validate path params + custom error handling
app.get('/users/:id',
zValidator('param', z.object({ id: z.coerce.number().positive() }),
(result, c) => {
if (!result.success) {
return c.json({ errors: result.error.flatten() }, 422)
}
}
),
(c) => c.json({ id: c.req.valid('param').id })
)上下文对象
上下文对象 (c) 是每个 Hono 处理器的核心。
type AppEnv = {
Bindings: { DATABASE_URL: string; API_KEY: string }
Variables: { requestId: string }
}
const app = new Hono<AppEnv>()
app.use('*', async (c, next) => {
c.set('requestId', crypto.randomUUID())
await next()
})
app.get('/demo', (c) => {
const url = c.req.url // request URL
const dbUrl = c.env.DATABASE_URL // env bindings
const reqId = c.get('requestId') // typed variables
c.header('X-Request-Id', reqId) // set headers
return c.json({ requestId: reqId, url })
})
// waitUntil (Cloudflare Workers)
app.post('/webhook', (c) => {
c.executionCtx.waitUntil(
fetch('https://analytics.example.com/track', {
method: 'POST',
body: JSON.stringify({ event: 'webhook' }),
})
)
return c.json({ ok: true })
})运行时适配器
Hono 的多运行时支持是其最大优势之一。
Cloudflare Workers
// src/index.ts — Cloudflare Workers
import { Hono } from 'hono'
type Bindings = {
MY_KV: KVNamespace
MY_DB: D1Database
MY_BUCKET: R2Bucket
API_KEY: string
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/kv/:key', async (c) => {
const val = await c.env.MY_KV.get(c.req.param('key'))
return val ? c.json({ val }) : c.json({ error: 'Not found' }, 404)
})
app.get('/db/users', async (c) => {
const { results } = await c.env.MY_DB
.prepare('SELECT * FROM users LIMIT 10')
.all()
return c.json(results)
})
export default appDeno
// main.ts — Deno
import { Hono } from 'https://deno.land/x/hono/mod.ts'
const app = new Hono()
app.get('/', (c) => c.text('Hello from Deno!'))
app.get('/env', (c) => {
return c.json({ runtime: 'deno', port: Deno.env.get('PORT') })
})
Deno.serve(app.fetch)
// Run: deno run --allow-net --allow-env main.tsBun
// index.ts — Bun (fastest runtime for Hono)
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello from Bun!'))
app.get('/file', async (c) => {
const data = await Bun.file('./data.json').json()
return c.json(data)
})
export default { port: 3000, fetch: app.fetch }
// Run: bun run index.tsNode.js
// index.ts — Node.js
import { Hono } from 'hono'
import { serve } from '@hono/node-server'
const app = new Hono()
app.get('/', (c) => c.text('Hello from Node.js!'))
app.get('/health', (c) => {
return c.json({ status: 'ok', version: process.version })
})
serve({ fetch: app.fetch, port: 3000 })
// Install: npm install hono @hono/node-server
// Run: npx tsx index.tsAWS Lambda
// lambda.ts — AWS Lambda
import { Hono } from 'hono'
import { handle } from 'hono/aws-lambda'
const app = new Hono()
app.get('/', (c) => c.text('Hello from Lambda!'))
app.get('/items', (c) => {
return c.json([{ id: 1, name: 'Item A' }])
})
// Export handler for API Gateway or Lambda URL
export const handler = handle(app)错误处理
Hono 通过 app.onError() 和 HTTPException 类提供结构化错误处理。
import { Hono } from 'hono'
import { HTTPException } from 'hono/http-exception'
const app = new Hono()
// Global error handler
app.onError((err, c) => {
if (err instanceof HTTPException) {
return c.json(
{ error: err.message, status: err.status },
err.status
)
}
console.error(err)
return c.json({ error: 'Internal Server Error' }, 500)
})
// 404 handler
app.notFound((c) => {
return c.json({ error: 'Not Found', path: c.req.path }, 404)
})
// Throw HTTPException in handlers
app.get('/users/:id', async (c) => {
const user = await findUser(c.req.param('id'))
if (!user) {
throw new HTTPException(404, {
message: 'User not found',
})
}
return c.json(user)
})
// Custom error with response
app.get('/protected', (c) => {
throw new HTTPException(401, {
message: 'Authentication required',
res: new Response('Unauthorized', {
status: 401,
headers: { 'WWW-Authenticate': 'Bearer' },
}),
})
})测试
Hono 通过 app.request() 提供内置测试客户端。
// app.test.ts — using Vitest
import { describe, it, expect } from 'vitest'
import app from './app'
describe('API', () => {
it('GET / returns OK', async () => {
const res = await app.request('/')
expect(res.status).toBe(200)
expect(await res.text()).toBe('OK')
})
it('GET /users/:id returns user', async () => {
const res = await app.request('/users/42')
const data = await res.json()
expect(data.id).toBe('42')
})
it('POST /users creates user', async () => {
const res = await app.request('/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice' }),
})
expect(res.status).toBe(201)
const data = await res.json()
expect(data.name).toBe('Alice')
})
it('returns 404 for unknown routes', async () => {
const res = await app.request('/nonexistent')
expect(res.status).toBe(404)
})
it('handles auth with Bearer token', async () => {
const res = await app.request('/api/me', {
headers: { Authorization: 'Bearer my-token' },
})
expect(res.status).toBe(200)
})
})JWT 认证
Hono 包含内置的 JWT 中间件用于令牌验证。
import { Hono } from 'hono'
import { jwt, sign } from 'hono/jwt'
const app = new Hono()
const SECRET = 'my-jwt-secret-key'
// Login — generate token
app.post('/auth/login', async (c) => {
const { email, password } = await c.req.json()
if (email !== 'admin@test.com') {
return c.json({ error: 'Invalid credentials' }, 401)
}
const token = await sign({
sub: 'user-123', email, role: 'admin',
exp: Math.floor(Date.now() / 1000) + 3600,
}, SECRET)
return c.json({ token })
})
// Protect routes
app.use('/api/*', jwt({ secret: SECRET }))
app.get('/api/me', (c) => {
const payload = c.get('jwtPayload')
return c.json({ userId: payload.sub, role: payload.role })
})
// Role-based access control
const requireRole = (role: string) => async (c: any, next: any) => {
if (c.get('jwtPayload').role !== role) {
return c.json({ error: 'Forbidden' }, 403)
}
await next()
}
app.delete('/api/admin/users/:id', requireRole('admin'),
(c) => c.json({ deleted: c.req.param('id') })
)CORS 配置
内置 CORS 中间件处理跨域请求。
import { cors } from 'hono/cors'
// Allow all origins (development)
app.use('/api/*', cors())
// Restrict to specific origins (production)
app.use('/api/*', cors({
origin: ['https://myapp.com', 'https://staging.myapp.com'],
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization'],
exposeHeaders: ['X-Total-Count'],
maxAge: 86400,
credentials: true,
}))
// Dynamic origin
app.use('/api/*', cors({
origin: (origin) => {
return origin.endsWith('.myapp.com') ? origin : 'https://myapp.com'
},
}))OpenAPI 集成
@hono/zod-openapi 自动生成 Swagger 文档。
import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi'
import { swaggerUI } from '@hono/swagger-ui'
const app = new OpenAPIHono()
const getUserRoute = createRoute({
method: 'get',
path: '/users/{id}',
request: {
params: z.object({
id: z.string().openapi({ example: '123' }),
}),
},
responses: {
200: {
content: { 'application/json': {
schema: z.object({
id: z.string(), name: z.string(), email: z.string().email(),
}),
}},
description: 'User found',
},
},
})
app.openapi(getUserRoute, (c) => {
const { id } = c.req.valid('param')
return c.json({ id, name: 'Alice', email: 'a@b.com' })
})
// Serve OpenAPI spec + Swagger UI
app.doc('/doc', {
openapi: '3.0.0',
info: { title: 'My API', version: '1.0.0' },
})
app.get('/ui', swaggerUI({ url: '/doc' }))RPC 模式
RPC 模式提供端到端类型安全,无需代码生成。
// ---- Server (server.ts) ----
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const app = new Hono()
.get('/api/posts', (c) => {
return c.json([{ id: 1, title: 'Hello Hono' }])
})
.post('/api/posts',
zValidator('json', z.object({
title: z.string(), content: z.string(),
})),
(c) => c.json({ id: 2, ...c.req.valid('json') }, 201)
)
export type AppType = typeof app
export default app
// ---- Client (client.ts) ----
import { hc } from 'hono/client'
import type { AppType } from './server'
const client = hc<AppType>('http://localhost:3000')
// Fully typed — IDE autocomplete + compile-time checks
const res = await client.api.posts.$get()
const data = await res.json() // typed as { id: number; title: string }[]
const newPost = await client.api.posts.$post({
json: { title: 'New', content: 'Hello!' },
}) // TypeScript error if shape is wrong性能基准
Hono 在基准测试中持续超越 Express、Fastify 和 Koa。
Benchmark Results (simple JSON, 10s)
- Hono (Bun): ~130,000 请求/秒
- Fastify (Node.js): ~75,000 请求/秒
- Express (Node.js): ~50,000 请求/秒
- Hono (Deno): ~95,000 请求/秒
- Hono (Workers): 亚毫秒冷启动,~80,000 请求/秒
// Performance tips
// 1. RegExpRouter (default) — fastest for static routes
const app = new Hono() // uses RegExpRouter
// 2. LinearRouter — better for dynamic route registration
import { LinearRouter } from 'hono/router/linear-router'
const app = new Hono({ router: new LinearRouter() })
// 3. Use c.json() over new Response()
app.get('/fast', (c) => c.json({ ok: true }))
// 4. Stream large responses
app.get('/large', (c) => {
return c.streamText(async (stream) => {
for (const chunk of data) await stream.write(chunk)
})
})
// 5. Cache with ETag
import { etag } from 'hono/etag'
app.use('/api/*', etag())常见问题
Hono 是什么,与 Express 有何不同?
Hono 是基于 Web 标准 API 的超快轻量级框架,可在多个运行时运行,速度快 2-3 倍。
Hono 能在生产中替代 Express 吗?
可以。Hono 已生产就绪,被 Cloudflare、Vercel 等使用。
哪个运行时对 Hono 最快?
Bun 最高 130K+ 请求/秒,Deno 约 95K,Workers 延迟最低。
Hono 如何处理验证?
通过 @hono/zod-validator 与 Zod 集成,自动验证并推断类型。
Hono 支持 WebSocket 吗?
支持,通过 hono/websocket 在支持的运行时上使用。
如何部署 Hono 到 Cloudflare Workers?
用 "npm create hono@latest" 创建项目,选 cloudflare-workers,然后 "wrangler deploy"。
Hono RPC 模式是什么?
RPC 模式在服务器和客户端间提供端到端类型安全,无需代码生成。
Hono 能自动生成 OpenAPI 文档吗?
可以,@hono/zod-openapi 从路由定义自动生成 OpenAPI 3.0 规范。