HonoJS 是一个轻量的NodeJS服务端框架,作用跟我们常见的Express和Koa等类似。Hono的特点是(官网自述):
- 快速、轻量级、基于Web标准构建
- 支持任何JavaScript运行时。

初始化
Hono 提供了初始化脚手架来简化项目创建,只需要通过一条命令即可完成创建。
$ pnpm create hono@latest hono-study
根据脚手架的指引选择实际的场景即可
创建完成之后即可启动项目体验
$ pnpm dev
# 浏览器访问localhost:3000 可以看到 【Hello Hono!】内容
Hono 项目默认使用了TypeScript,打开项目可以看到有一个index.ts文件,这个文件提供了我们刚才访问的内容
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => {
return c.text('Hello Hono!')
})
serve({
fetch: app.fetch,
port: 3000
}, (info) => {
console.log(`Server is running on http://localhost:${info.port}`)
})
可以看到还是经典的三段式结构。
API
Hono 的API 基本遵循了前辈们的使用习惯,并且加入了自己的特色用法。
App
每个Hono实例(const app = new Hono()产生的对象)都称之为一个App,实例上拥有的方法主要适用于绑定中间件、定义路由、启动服务等。
app.fetch(request, env, event) 用于获取应用入口,将这个入口传递给不同平台的启动器可以实现跨平台的兼容。上面的代码段中使用的就是将入口放到node-server中在NodeJS环境进行启动。
如果是在 Cloudflare Workers 中可以这样写:
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
return app.fetch(request, env, ctx)
},
}
这里通过不同的适配器将不同平台的请求转为统一的标准的Web 请求。
app.use([path,]middleware) 用于绑定中间件,第一个参数是可选的请求路径,不指定路径时对所有请求生效。
例如我们添加一个logger中间件用于在控制台输出请求的信息
import { Hono } from 'hono'
import { logger } from 'hono/logger'
const app = new Hono()
app.use(logger())
此时触发请求后可以在控制台看到请求耗时等信息
中间件本质上就是一个函数,会对请求进行前置或者后置处理,理论上只要符合
(c: Context, next: Next) => Promise<void>签名的函数都可以作为中间件使用。
例如logger中间件我们可以自己实现一个app.use(async (c, next) => { console.log(`[${c.req.method}] ${c.req.url}`) await next() })
app.HTTP_METHOD([path,]handler|middleware...) 用于绑定请求对应的处理函数,包括get、post、put、delete、all
app.get('/', (c) => c.text('GET /'))
app.post('/', (c) => c.text('POST /'))
app.put('/', (c) => c.text('PUT /'))
app.delete('/', (c) => c.text('DELETE /'))
app.all('/all', (c) => c.text('Any Method /all'))
还有
app.on(method|method[], path|path[], handler|middleware...)可以用于绑定请求处理,比起http方法的绑定,使用on绑定需要额外传一个请求方法的参数,使用on的优势是,可以批量绑定请求处理,因为方法参数和路径参数都支持数组类型。app.on(['GET', 'POST'], ['/path1', 'paht2'], (c) => { return c.text('multi bind.') })
app.basePath(path) 可以设置路由前缀,应用下所有的请求路径都会基于basPath来处理
const api = new Hono().basePath('/api')
api.get('/users', (c) => c.text('users')) // GET /api/users
basePath 必须在创建Hono实例时设置才会生效。
const api = new Hono() api.basePath('/api') // 这样设置不生效
app.notFound(handler) 可以用于绑定匹配不到路由的处理,notFound 只有在顶层应用可以设置
app.notFound((c) => {
return c.text('Custom 404 Message', 404)
})
app.onError(handler) 可以绑定错误处理,当程序抛出异常是可以通过 onError 钩子来捕获处理,如果存在嵌套绑定 onError 会就近处理。
app.onError((err, c) => {
console.error(`${err}`)
return c.text('Custom Error Message', 500)
})
路由
Hono 的路由非常灵活,支持多种方式的路径匹配。
- 基础路径:最普通最常见的额一种路由定义形式,指定固定的路径,将按照这个路径匹配请求。例如
/normal/request - 动态路径:路径中存在动态部分,动态部分可以作为参数使用。例如
/api/user/:userId - 正则路径:在动态路由的基础上添加匹配限制,只有符合正则表达式才会被匹配到。例如
/regexp/:name{[a-z]+}可以匹配到'/regexp/feng',但是无法匹配到'/regexp/feng1' - 通配路由:所有的路由都会匹配到。使用
*作为path即可
前面有说过app.basePath,除了这种方式还可以通过以下方式对路径进行编组,也可以实现basePath的能力(可以和basePath叠加使用)。
const api = new Hono()
api.get('/hello', (c) => c.text('Hello API.'))
const app = new Hono()
app.route('/api', api)
路由会按照注册顺序进行匹配,并且只会匹配一次
app.get('/api/:name', (c) => c.text('name'))
app.get('/api/a', (c) => c.text('a'))
上面这两个注册顺序下,访问/book/a时获取的结果是name,所有需要注意的是优先将固定路径的路由放在前面注册。
上下文
Context对象在每个请求中实例化,可用于存储值、设置HTTP状态码和头信息,以及访问HonoRequest和Response对象,Context对象在每个请求中实例化,直到返回响应才销毁。
- req req是HonoRequest的实例,用于访问请求相关的信息,如请求头等。
- status() 使用c.status()可以设置HTTP状态码,默认为200。如果状态码为200,可以不使用此方法。
- header() 使用c.header()可以设置响应的HTTP头信息。
- body() 使用c.body()可以返回HTTP响应体。当返回文本或HTML时,推荐使用c.text()或c.html()。
- text() 使用c.text()可以将文本作为Content-Type:text/plain返回。
- json() 使用c.json()可以将JSON作为Content-Type:application/json返回。
- html() 使用c.html()可以将HTML作为Content-Type:text/html返回。
- notFound() 使用c.notFound()可以返回一个404状态码的响应,也可以通过app.notFound()进行自定义。
- redirect() 使用c.redirect()可以进行重定向,默认状态码为302。
- res 可以通过c.res访问将要返回的Response对象。
- set() / get() 使用c.set()和c.get()可以在当前请求的生命周期内存储和获取任意键值对,这允许在中间件或从中间件到路由处理器之间传递特定值。
- var 可以通过c.var访问变量的值。如果需要创建提供自定义方法的中间件,可以像文中示例那样编写。
- render() / setRenderer() 使用c.setRenderer()可以在自定义中间件中设置布局,然后使用c.render()在该布局内创建响应。
- error 如果处理器抛出错误,错误对象将被放置在c.error中。可以在中间件中访问它。
其他的都很好理解不做过多介绍,要着重提及的是c.html()支持jsx语法的渲染,这对于模板渲染来说非常方便。
// RenderComponent.tsx
export function RenderComponent(props: RenderComponentProps) {
const {name} = props
return <div>Your name is {name}</div>
}
// index.tsx
app.get('/react/:name', (c) => {
const name = c.req.param('name') as string
return c.html(<RenderComponent name={name} />)
})

req
事关请求参数的获取 HonoRequest 有必要单开一节,HonoRequest 是基于 Request的一个对象,包含了Hono对于请求体的一些封装。
通过req可以获取请求数据的方式有很多
query([key]):获取请求路径中的query参数,如?name=feng,可以使用const name = c.req.query('name')来获取,如果同名参数有多个也可以使用const names = c.req.queries('name')获取数组;param([key]):获取请求路径中的param参数,如/user/1,在路径为/user/:id的请求处理中可以使用const id = c.req.param('id')来获取;header([key]):从请求头中获取制定的字段,const UA = c.req.header('User-Agent'),⚠️注意当使用header()获取全部请求头时,所有的请求头Key都是小驼峰格式;body:请求的 body可以有很多种类型,Hono支持直接转换的格式如下parseBody():用于格式化multipart/form-data和application/x-www-form-urlencoded类型的请求体;json():用于解析application/json格式的请求体text():用于解析text/plain格式的请求体arrayBuffer():格式化请求体为ArrayBuffer类型blob():格式化请求体为Blob类型formData():格式化请求体为FormData类型
Hono 对请求数据提供了校验的能力,通过validator中间件来定义校验的规则
import { validator } from 'hono/validator'
app.post(
'/user',
validator('form', (data, c) => {
if (!data.name) {
return c.json({ error: 'Name is required' }, 400)
}
return data
}),
(c) => {
const user = c.req.valid('form')
return c.json({ message: 'User created', user })
}
)
在validator中我们可以通过zod等校验工具来实现业务校验。
到这里我们已经掌握了Hono的基础用法,可以用Hono来实现一些简单的Demo了,当然Hono的能力并不只有这些,更多的使用姿势可以去官网发现

