This is the full developer documentation for Hono.
# Start of Hono documentation
# Hono
Hono - 在日语中是火焰🔥的意思 - 是一个基于 Web 标准构建的小型、简单且超快的 Web 框架。它适用于任何 JavaScript 运行时:Cloudflare Workers、Fastly Compute、Deno、Bun、Vercel、Netlify、AWS Lambda、Lambda@Edge 和 Node.js。
¥Hono - ***means flame🔥 in Japanese*** - is a small, simple, and ultrafast web framework built on Web Standards.
It works on any JavaScript runtime: Cloudflare Workers, Fastly Compute, Deno, Bun, Vercel, Netlify, AWS Lambda, Lambda@Edge, and Node.js.
快速,但不仅仅是快速。
¥Fast, but not only fast.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hono!'))
export default app
```
## 快速入门
¥Quick Start
只需运行这个:
¥Just run this:
::: code-group
```sh [npm]
npm create hono@latest
```
```sh [yarn]
yarn create hono
```
```sh [pnpm]
pnpm create hono@latest
```
```sh [bun]
bun create hono@latest
```
```sh [deno]
deno init --npm hono@latest
```
:::
## 功能
¥Features
* 超快 🚀 - 路由 `RegExpRouter` 非常快。不使用线性循环。快速。
¥**Ultrafast** 🚀 - The router `RegExpRouter` is really fast. Not using linear loops. Fast.
* 轻量级 🪶 - `hono/tiny` 预设小于 14kB。Hono 没有任何依赖,仅使用 Web 标准。
¥**Lightweight** 🪶 - The `hono/tiny` preset is under 14kB. Hono has zero dependencies and uses only the Web Standards.
* 多运行时 🌍 - 适用于 Cloudflare Workers、Fastly Compute、Deno、Bun、AWS Lambda 或 Node.js。相同的代码在所有平台上运行。
¥**Multi-runtime** 🌍 - Works on Cloudflare Workers, Fastly Compute, Deno, Bun, AWS Lambda, or Node.js. The same code runs on all platforms.
* 包含适配 🔋 - Hono 具有内置中间件、自定义中间件、第三方中间件和助手。包含适配。
¥**Batteries Included** 🔋 - Hono has built-in middleware, custom middleware, third-party middleware, and helpers. Batteries included.
* Delightful DX 😃 - 超级干净的 API。一流的 TypeScript 支持。现在,我们得到了 "类型"。
¥**Delightful DX** 😃 - Super clean APIs. First-class TypeScript support. Now, we've got "Types".
## 用例
¥Use-cases
Hono 是一个类似于 Express 的简单 Web 应用框架,没有前端。但它在 CDN Edges 上运行,并允许你与中间件结合使用时构建更大的应用。以下是一些用例示例。
¥Hono is a simple web application framework similar to Express, without a frontend.
But it runs on CDN Edges and allows you to construct larger applications when combined with middleware.
Here are some examples of use-cases.
* 构建 Web API
¥Building Web APIs
* 后端服务器的代理
¥Proxy of backend servers
* CDN 前端
¥Front of CDN
* Edge 应用
¥Edge application
* 库的基本服务器
¥Base server for a library
* 全栈应用
¥Full-stack application
## 谁在使用 Hono?
¥Who is using Hono?
| 项目 | 平台 | 用于什么? |
| ---------------------------------------------------------------------------------- | ------------------ | -------------------------------------------------------- |
| [cdnjs](https://cdnjs.com) | Cloudflare Workers | 免费开源 CDN 服务。Hono 用于 API 服务器。 |
| [Cloudflare D1](https://www.cloudflare.com/developer-platform/d1/) | Cloudflare Workers | 无服务器 SQL 数据库。Hono 用于内部 API 服务器。 |
| [Cloudflare Workers KV](https://www.cloudflare.com/developer-platform/workers-kv/) | Cloudflare Workers | 无服务器键值数据库。Hono 用于内部 API 服务器。 |
| [BaseAI](https://baseai.dev) | 本地 AI 服务器 | 带内存的无服务器 AI 代理管道。一个用于 Web 的开源代理 AI 框架。带有 Hono 的 API 服务器。 |
| [Unkey](https://unkey.dev) | Cloudflare Workers | 一个开源 API 身份验证和授权。Hono 用于 API 服务器。 |
| [OpenStatus](https://openstatus.dev) | Bun | 一个开源网站和 API 监控平台。Hono 用于 API 服务器。 |
| [Deno 基准测试](https://deno.com/benchmarks) | Deno | 基于 V8 构建的安全 TypeScript 运行时。Hono 用于基准测试。 |
以及以下内容。
¥And the following.
* [Drivly](https://driv.ly/) - Cloudflare Workers
* [repeat.dev](https://repeat.dev/) - Cloudflare Workers
你想看更多吗?参见 [谁在生产中使用 Hono?](https://github.com/orgs/honojs/discussions/1510)。
¥Do you want to see more? See [Who is using Hono in production?](https://github.com/orgs/honojs/discussions/1510).
## 1 分钟内掌握 Hono
¥Hono in 1 minute
使用 Hono 为 Cloudflare Workers 创建应用的演示。
¥A demonstration to create an application for Cloudflare Workers with Hono.

## 超快
¥Ultrafast
与 Cloudflare Workers 的其他路由相比,Hono 是最快的。
¥**Hono is the fastest**, compared to other routers for Cloudflare Workers.
```
Hono x 402,820 ops/sec ±4.78% (80 runs sampled)
itty-router x 212,598 ops/sec ±3.11% (87 runs sampled)
sunder x 297,036 ops/sec ±4.76% (77 runs sampled)
worktop x 197,345 ops/sec ±2.40% (88 runs sampled)
Fastest is Hono
✨ Done in 28.06s.
```
参见 [更多基准](/docs/concepts/benchmarks)。
¥See [more benchmarks](/docs/concepts/benchmarks).
## 轻量级
¥Lightweight
Hono 很小。使用 `hono/tiny` 预设,其大小在最小化时低于 14KB。有许多中间件和适配器,但它们仅在使用时打包在一起。有关上下文,Express 的大小为 572KB。
¥**Hono is so small**. With the `hono/tiny` preset, its size is **under 14KB** when minified. There are many middleware and adapters, but they are bundled only when used. For context, the size of Express is 572KB.
```
$ npx wrangler dev --minify ./src/index.ts
⛅️ wrangler 2.20.0
--------------------
⬣ Listening at http://0.0.0.0:8787
- http://127.0.0.1:8787
- http://192.168.128.165:8787
Total Upload: 11.47 KiB / gzip: 4.34 KiB
```
## 多个路由
¥Multiple routers
Hono 有多个路由。
¥**Hono has multiple routers**.
RegExpRouter 是 JavaScript 世界中最快的路由。它使用在调度之前创建的单个大型正则表达式来匹配路由。使用 SmartRouter,它支持所有路由模式。
¥**RegExpRouter** is the fastest router in the JavaScript world. It matches the route using a single large Regex created before dispatch. With **SmartRouter**, it supports all route patterns.
LinearRouter 可以非常快速地注册路由,因此它适用于每次初始化应用的环境。PatternRouter 只是添加和匹配模式,使其变小。
¥**LinearRouter** registers the routes very quickly, so it's suitable for an environment that initializes applications every time. **PatternRouter** simply adds and matches the pattern, making it small.
参见 [有关路由的更多信息](/docs/concepts/routers)。
¥See [more information about routes](/docs/concepts/routers).
## Web 标准
¥Web Standards
由于使用了 Web 标准,Hono 可以在很多平台上运行。
¥Thanks to the use of the **Web Standards**, Hono works on a lot of platforms.
* Cloudflare Workers
* Cloudflare 页面
¥Cloudflare Pages
* Fastly 计算
¥Fastly Compute
* Deno
* Bun
* Vercel
* AWS Lambda
* Lambda@Edge
* 其他
¥Others
通过使用 [Node.js 适配器](https://github.com/honojs/node-server),Hono 可以在 Node.js 上运行。
¥And by using [a Node.js adapter](https://github.com/honojs/node-server), Hono works on Node.js.
参见 [有关 Web 标准的更多信息](/docs/concepts/web-standard)。
¥See [more information about Web Standards](/docs/concepts/web-standard).
## 中间件和助手
¥Middleware & Helpers
Hono 有许多中间件和助手。这使 "写得少,做得多" 成为现实。
¥**Hono has many middleware and helpers**. This makes "Write Less, do more" a reality.
开箱即用,Hono 提供中间件和助手:
¥Out of the box, Hono provides middleware and helpers for:
* [基本身份验证](/docs/middleware/builtin/basic-auth)
¥[Basic Authentication](/docs/middleware/builtin/basic-auth)
* [承载身份验证](/docs/middleware/builtin/bearer-auth)
¥[Bearer Authentication](/docs/middleware/builtin/bearer-auth)
* [主体限制](/docs/middleware/builtin/body-limit)
¥[Body Limit](/docs/middleware/builtin/body-limit)
* [缓存](/docs/middleware/builtin/cache)
¥[Cache](/docs/middleware/builtin/cache)
* [压缩](/docs/middleware/builtin/compress)
¥[Compress](/docs/middleware/builtin/compress)
* [上下文存储](/docs/middleware/builtin/context-storage)
¥[Context Storage](/docs/middleware/builtin/context-storage)
* [Cookie](/docs/helpers/cookie)
* [CORS](/docs/middleware/builtin/cors)
* [ETag](/docs/middleware/builtin/etag)
* [html](/docs/helpers/html)
* [JSX](/docs/guides/jsx)
* [JWT 身份验证](/docs/middleware/builtin/jwt)
¥[JWT Authentication](/docs/middleware/builtin/jwt)
* [日志器](/docs/middleware/builtin/logger)
¥[Logger](/docs/middleware/builtin/logger)
* [语言](/docs/middleware/builtin/language)
¥[Language](/docs/middleware/builtin/language)
* [漂亮的 JSON](/docs/middleware/builtin/pretty-json)
¥[Pretty JSON](/docs/middleware/builtin/pretty-json)
* [安全标头](/docs/middleware/builtin/secure-headers)
¥[Secure Headers](/docs/middleware/builtin/secure-headers)
* [SSG](/docs/helpers/ssg)
* [流式传输](/docs/helpers/streaming)
¥[Streaming](/docs/helpers/streaming)
* [GraphQL 服务器](https://github.com/honojs/middleware/tree/main/packages/graphql-server)
¥[GraphQL Server](https://github.com/honojs/middleware/tree/main/packages/graphql-server)
* [Firebase 身份验证](https://github.com/honojs/middleware/tree/main/packages/firebase-auth)
¥[Firebase Authentication](https://github.com/honojs/middleware/tree/main/packages/firebase-auth)
* [Sentry](https://github.com/honojs/middleware/tree/main/packages/sentry)
* 其他!
¥Others!
例如,使用 Hono 添加 ETag 和请求日志记录只需要几行代码:
¥For example, adding ETag and request logging only takes a few lines of code with Hono:
```ts
import { Hono } from 'hono'
import { etag } from 'hono/etag'
import { logger } from 'hono/logger'
const app = new Hono()
app.use(etag(), logger())
```
参见 [有关中间件的更多信息](/docs/concepts/middleware)。
¥See [more information about Middleware](/docs/concepts/middleware).
## 开发者体验
¥Developer Experience
Hono 提供了令人愉悦的 "开发者体验"。
¥Hono provides a delightful "**Developer Experience**".
借助 `Context` 对象,可以轻松访问请求/响应。此外,Hono 是用 TypeScript 编写的。Hono 有 "类型"。
¥Easy access to Request/Response thanks to the `Context` object.
Moreover, Hono is written in TypeScript. Hono has "**Types**".
例如,路径参数将是字面量类型。
¥For example, the path parameters will be literal types.

Validator 和 Hono Client `hc` 启用了 RPC 模式。在 RPC 模式下,你可以使用自己喜欢的验证器(例如 Zod),并轻松地与客户端共享服务器端 API 规范并构建类型安全的应用。
¥And, the Validator and Hono Client `hc` enable the RPC mode. In RPC mode,
you can use your favorite validator such as Zod and easily share server-side API specs with the client and build type-safe applications.
参见 [Hono Stacks](/docs/concepts/stacks)。
¥See [Hono Stacks](/docs/concepts/stacks).
# 第三方中间件
¥Third-party Middleware
第三方中间件是指未打包在 Hono 包中的中间件。大多数中间件都利用了外部库。
¥Third-party middleware refers to middleware not bundled within the Hono package.
Most of this middleware leverages external libraries.
### 身份验证
¥Authentication
* [Auth.js(Next Auth)](https://github.com/honojs/middleware/tree/main/packages/auth-js)
* [Clerk Auth](https://github.com/honojs/middleware/tree/main/packages/clerk-auth)
* [OAuth 提供程序](https://github.com/honojs/middleware/tree/main/packages/oauth-providers)
¥[OAuth Providers](https://github.com/honojs/middleware/tree/main/packages/oauth-providers)
* [OIDC 身份验证](https://github.com/honojs/middleware/tree/main/packages/oidc-auth)
¥[OIDC Auth](https://github.com/honojs/middleware/tree/main/packages/oidc-auth)
* [Firebase Auth](https://github.com/honojs/middleware/tree/main/packages/firebase-auth)
* [验证 RSA JWT (JWKS)](https://github.com/wataruoguchi/verify-rsa-jwt-cloudflare-worker)
¥[Verify RSA JWT (JWKS)](https://github.com/wataruoguchi/verify-rsa-jwt-cloudflare-worker)
### 验证器
¥Validators
* [ArkType 验证器](https://github.com/honojs/middleware/tree/main/packages/arktype-validator)
¥[ArkType validator](https://github.com/honojs/middleware/tree/main/packages/arktype-validator)
* [效果模式验证器](https://github.com/honojs/middleware/tree/main/packages/effect-validator)
¥[Effect Schema Validator](https://github.com/honojs/middleware/tree/main/packages/effect-validator)
* [标准架构验证器](https://github.com/honojs/middleware/tree/main/packages/standard-validator)
¥[Standard Schema Validator](https://github.com/honojs/middleware/tree/main/packages/standard-validator)
* [TypeBox 验证器](https://github.com/honojs/middleware/tree/main/packages/typebox-validator)
¥[TypeBox Validator](https://github.com/honojs/middleware/tree/main/packages/typebox-validator)
* [Typia 验证器](https://github.com/honojs/middleware/tree/main/packages/typia-validator)
¥[Typia Validator](https://github.com/honojs/middleware/tree/main/packages/typia-validator)
* [unknownutil Validator](https://github.com/ryoppippi/hono-unknownutil-validator)
* [Valibot 验证器](https://github.com/honojs/middleware/tree/main/packages/valibot-validator)
¥[Valibot Validator](https://github.com/honojs/middleware/tree/main/packages/valibot-validator)
* [Zod Validator](https://github.com/honojs/middleware/tree/main/packages/zod-validator)
### OpenAPI
* [Zod OpenAPI](https://github.com/honojs/middleware/tree/main/packages/zod-openapi)
* [标量 API 参考](https://github.com/scalar/scalar/tree/main/integrations/hono)
¥[Scalar API Reference](https://github.com/scalar/scalar/tree/main/integrations/hono)
* [Swagger UI](https://github.com/honojs/middleware/tree/main/packages/swagger-ui)
* [Hono OpenAPI](https://github.com/rhinobase/hono-openapi)
### 其他
¥Others
* [Bun Transpiler](https://github.com/honojs/middleware/tree/main/packages/bun-transpiler)
* [esbuild Transpiler](https://github.com/honojs/middleware/tree/main/packages/esbuild-transpiler)
* [事件触发器](https://github.com/honojs/middleware/tree/main/packages/event-emitter)
¥[Event Emitter](https://github.com/honojs/middleware/tree/main/packages/event-emitter)
* [GraphQL 服务器](https://github.com/honojs/middleware/tree/main/packages/graphql-server)
¥[GraphQL Server](https://github.com/honojs/middleware/tree/main/packages/graphql-server)
* [Hono 速率限制器](https://github.com/rhinobase/hono-rate-limiter)
¥[Hono Rate Limiter](https://github.com/rhinobase/hono-rate-limiter)
* [Node WebSocket 助手](https://github.com/honojs/middleware/tree/main/packages/node-ws)
¥[Node WebSocket Helper](https://github.com/honojs/middleware/tree/main/packages/node-ws)
* [Prometheus 指标](https://github.com/honojs/middleware/tree/main/packages/prometheus)
¥[Prometheus Metrics](https://github.com/honojs/middleware/tree/main/packages/prometheus)
* [Qwik City](https://github.com/honojs/middleware/tree/main/packages/qwik-city)
* [React 兼容性](https://github.com/honojs/middleware/tree/main/packages/react-compat)
¥[React Compatibility](https://github.com/honojs/middleware/tree/main/packages/react-compat)
* [React Renderer](https://github.com/honojs/middleware/tree/main/packages/react-renderer)
* [RONIN(数据库)](https://github.com/ronin-co/hono-client)
¥[RONIN (Database)](https://github.com/ronin-co/hono-client)
* [Sentry](https://github.com/honojs/middleware/tree/main/packages/sentry)
* [tRPC 服务器](https://github.com/honojs/middleware/tree/main/packages/trpc-server)
¥[tRPC Server](https://github.com/honojs/middleware/tree/main/packages/trpc-server)
* [Geo](https://github.com/ktkongtong/hono-geo-middleware/tree/main/packages/middleware)
* [Hono 简单 DI](https://github.com/maou-shonen/hono-simple-DI)
¥[Hono Simple DI](https://github.com/maou-shonen/hono-simple-DI)
* [Highlight.io](https://www.highlight.io/docs/getting-started/backend-sdk/js/hono)
* [Apitally(API 监控和分析)](https://docs.apitally.io/frameworks/hono)
¥[Apitally (API monitoring & analytics)](https://docs.apitally.io/frameworks/hono)
# 基本身份验证中间件
¥Basic Auth Middleware
此中间件可以将基本身份验证应用于指定路径。使用 Cloudflare Workers 或其他平台实现基本身份验证比看起来要复杂,但有了这个中间件,一切就变得轻而易举了。
¥This middleware can apply Basic authentication to a specified path.
Implementing Basic authentication with Cloudflare Workers or other platforms is more complicated than it seems, but with this middleware, it's a breeze.
有关基本身份验证方案在后台如何工作的更多信息,请参阅 [MDN 文档](https://web.nodejs.cn/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme)。
¥For more information about how the Basic auth scheme works under the hood, see the [MDN docs](https://web.nodejs.cn/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme).
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { basicAuth } from 'hono/basic-auth'
```
## 用法
¥Usage
```ts
const app = new Hono()
app.use(
'/auth/*',
basicAuth({
username: 'hono',
password: 'acoolproject',
})
)
app.get('/auth/page', (c) => {
return c.text('You are authorized')
})
```
要限制到特定路由 + 方法:
¥To restrict to a specific route + method:
```ts
const app = new Hono()
app.get('/auth/page', (c) => {
return c.text('Viewing page')
})
app.delete(
'/auth/page',
basicAuth({ username: 'hono', password: 'acoolproject' }),
(c) => {
return c.text('Page deleted')
}
)
```
如果你想自己验证用户,请指定 `verifyUser` 选项;返回 `true` 表示它被接受。
¥If you want to verify the user by yourself, specify the `verifyUser` option; returning `true` means it is accepted.
```ts
const app = new Hono()
app.use(
basicAuth({
verifyUser: (username, password, c) => {
return (
username === 'dynamic-user' && password === 'hono-password'
)
},
})
)
```
## 选项
¥Options
### <徽章类型="danger" text="required" /> username:`string`
¥ username: `string`
正在进行身份验证的用户的用户名。
¥The username of the user who is authenticating.
### password:`string`
提供的用户名的密码值,用于进行身份验证。
¥The password value for the provided username to authenticate against.
### <徽章类型="info" 文本="optional" /> 字段:`string`
¥ realm: `string`
字段的域名,作为返回的 WWW-Authenticate 质询标头的一部分。默认为 `"Secure Area"`。查看更多:[https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/WWW-Authenticate#directives](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/WWW-Authenticate#directives)
¥The domain name of the realm, as part of the returned WWW-Authenticate challenge header. The default is `"Secure Area"`.\ See more: [https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/WWW-Authenticate#directives](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/WWW-Authenticate#directives)
### hashFunction:`Function`
用于处理哈希函数以安全地比较密码的函数。
¥A function to handle hashing for safe comparison of passwords.
### verifyUser:`(username: string, password: string, c: Context) => boolean | Promise`
验证用户的函数。
¥The function to verify the user.
### invalidUserMessage:`string | object | MessageFunction`
`MessageFunction` 是 `(c: Context) => string | object | Promise`。如果用户无效,则显示自定义消息。
¥`MessageFunction` is `(c: Context) => string | object | Promise`. The custom message if the user is invalid.
## 更多选项
¥More Options
### ...用户:`{ username: string, password: string }[]`
¥ ...users: `{ username: string, password: string }[]`
## 秘诀
¥Recipes
### 定义多个用户
¥Defining Multiple Users
此中间件还允许你传递包含定义更多 `username` 和 `password` 对的对象的任意参数。
¥This middleware also allows you to pass arbitrary parameters containing objects defining more `username` and `password` pairs.
```ts
app.use(
'/auth/*',
basicAuth(
{
username: 'hono',
password: 'acoolproject',
// Define other params in the first object
realm: 'www.example.com',
},
{
username: 'hono-admin',
password: 'super-secure',
// Cannot redefine other params here
},
{
username: 'hono-user-1',
password: 'a-secret',
// Or here
}
)
)
```
或更少的硬编码:
¥Or less hardcoded:
```ts
import { users } from '../config/users'
app.use(
'/auth/*',
basicAuth(
{
realm: 'www.example.com',
...users[0],
},
...users.slice(1)
)
)
```
# Bearer Auth 中间件
¥Bearer Auth Middleware
Bearer Auth Middleware 通过验证 Request 标头中的 API 令牌来提供身份验证。访问端点的 HTTP 客户端将添加 `Authorization` 标头,并以 `Bearer {token}` 作为标头值。
¥The Bearer Auth Middleware provides authentication by verifying an API token in the Request header.
The HTTP clients accessing the endpoint will add the `Authorization` header with `Bearer {token}` as the header value.
从终端使用 `curl`,它看起来像这样:
¥Using `curl` from the terminal, it would look like this:
```sh
curl -H 'Authorization: Bearer honoiscool' http://localhost:8787/auth/page
```
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { bearerAuth } from 'hono/bearer-auth'
```
## 用法
¥Usage
> [!NOTE] 你的 `token` 必须与正则表达式 `/[A-Za-z0-9._~+/-]+=*/` 匹配,否则将返回 400 错误。值得注意的是,此正则表达式同时适用于 URL 安全的 Base64 和标准 Base64 编码的 JWT。此中间件不需要承载令牌是 JWT,只需要它与上述正则表达式匹配。
>
> ¥[!NOTE]
> Your `token` must match the regex `/[A-Za-z0-9._~+/-]+=*/`, otherwise a 400 error will be returned. Notably, this regex acommodates both URL-safe Base64- and standard Base64-encoded JWTs. This middleware does not require the bearer token to be a JWT, just that it matches the above regex.
```ts
const app = new Hono()
const token = 'honoiscool'
app.use('/api/*', bearerAuth({ token }))
app.get('/api/page', (c) => {
return c.json({ message: 'You are authorized' })
})
```
要限制到特定路由 + 方法:
¥To restrict to a specific route + method:
```ts
const app = new Hono()
const token = 'honoiscool'
app.get('/api/page', (c) => {
return c.json({ message: 'Read posts' })
})
app.post('/api/page', bearerAuth({ token }), (c) => {
return c.json({ message: 'Created post!' }, 201)
})
```
要实现多个令牌(例如,任何有效令牌都可以读取,但创建/更新/删除仅限于特权令牌):
¥To implement multiple tokens (E.g., any valid token can read but create/update/delete are restricted to a privileged token):
```ts
const app = new Hono()
const readToken = 'read'
const privilegedToken = 'read+write'
const privilegedMethods = ['POST', 'PUT', 'PATCH', 'DELETE']
app.on('GET', '/api/page/*', async (c, next) => {
// List of valid tokens
const bearer = bearerAuth({ token: [readToken, privilegedToken] })
return bearer(c, next)
})
app.on(privilegedMethods, '/api/page/*', async (c, next) => {
// Single valid privileged token
const bearer = bearerAuth({ token: privilegedToken })
return bearer(c, next)
})
// Define handlers for GET, POST, etc.
```
如果你想自己验证 token 的值,请指定 `verifyToken` 选项;返回 `true` 表示它被接受。
¥If you want to verify the value of the token yourself, specify the `verifyToken` option; returning `true` means it is accepted.
```ts
const app = new Hono()
app.use(
'/auth-verify-token/*',
bearerAuth({
verifyToken: async (token, c) => {
return token === 'dynamic-token'
},
})
)
```
## 选项
¥Options
### <徽章类型="danger" 文本="required" /> 令牌:`string` | `string[]`
¥ token: `string` | `string[]`
用于验证传入 bearer 令牌的字符串。
¥The string to validate the incoming bearer token against.
### <徽章类型="info" 文本="optional" /> 字段:`string`
¥ realm: `string`
字段的域名,作为返回的 WWW-Authenticate 质询标头的一部分。默认为 `""`。查看更多:[https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/WWW-Authenticate#directives](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/WWW-Authenticate#directives)
¥The domain name of the realm, as part of the returned WWW-Authenticate challenge header. The default is `""`.
See more: [https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/WWW-Authenticate#directives](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/WWW-Authenticate#directives)
### prefix:`string`
授权标头值的前缀(或称为 `schema`)。默认为 `"Bearer"`。
¥The prefix (or known as `schema`) for the Authorization header value. The default is `"Bearer"`.
### 标头名称:`string`
¥ headerName: `string`
标头名称。默认值为 `Authorization`。
¥The header name. The default value is `Authorization`.
### hashFunction:`Function`
用于处理哈希函数以安全地比较身份验证令牌的函数。
¥A function to handle hashing for safe comparison of authentication tokens.
### verifyToken:`(token: string, c: Context) => boolean | Promise`
验证令牌的函数。
¥The function to verify the token.
### noAuthenticationHeaderMessage:`string | object | MessageFunction`
`MessageFunction` 是 `(c: Context) => string | object | Promise`。如果没有身份验证标头,则显示自定义消息。
¥`MessageFunction` is `(c: Context) => string | object | Promise`. The custom message if it does not have an authentication header.
### invalidAuthenticationHeaderMessage:`string | object | MessageFunction`
如果身份验证标头无效,则显示自定义消息。
¥The custom message if the authentication header is invalid.
### invalidTokenMessage:`string | object | MessageFunction`
如果令牌无效,则显示自定义消息。
¥The custom message if the token is invalid.
# 主体限制中间件
¥Body Limit Middleware
Body Limit Middleware 可以限制请求正文的文件大小。
¥The Body Limit Middleware can limit the file size of the request body.
此中间件首先使用请求中的 `Content-Length` 标头的值(如果存在)。如果未设置,它将读取流中的主体,并在其大于指定的文件大小时执行错误处理程序。
¥This middleware first uses the value of the `Content-Length` header in the request, if present.
If it is not set, it reads the body in the stream and executes an error handler if it is larger than the specified file size.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { bodyLimit } from 'hono/body-limit'
```
## 用法
¥Usage
```ts
const app = new Hono()
app.post(
'/upload',
bodyLimit({
maxSize: 50 * 1024, // 50kb
onError: (c) => {
return c.text('overflow :(', 413)
},
}),
async (c) => {
const body = await c.req.parseBody()
if (body['file'] instanceof File) {
console.log(`Got file sized: ${body['file'].size}`)
}
return c.text('pass :)')
}
)
```
## 选项
¥Options
### maxSize:`number`
你想要限制的文件的最大文件大小。默认为 `100 * 1024` - `100kb`。
¥The maximum file size of the file you want to limit. The default is `100 * 1024` - `100kb`.
### onError:`OnError`
如果超出指定的文件大小,则调用错误处理程序。
¥The error handler to be invoked if the specified file size is exceeded.
## 与 Bun 一起使用以处理大量请求
¥Usage with Bun for large requests
如果明确使用 Body Limit Middleware 来允许请求主体大于默认值,则可能需要相应地更改 `Bun.serve` 配置。[撰写本文时](https://github.com/oven-sh/bun/blob/f2cfa15e4ef9d730fc6842ad8b79fb7ab4c71cb9/packages/bun-types/bun.d.ts#L2191)、`Bun.serve` 的默认请求正文限制为 128MiB。如果你将 Hono 的 Body Limit Middleware 设置为大于该值,你的请求仍将失败,此外,中间件中指定的 `onError` 处理程序将不会被调用。这是因为 `Bun.serve()` 会在将请求传递给 Hono 之前将状态代码设置为 `413` 并终止连接。
¥If the Body Limit Middleware is used explicitly to allow a request body larger than the default, it might be necessary to make changes to your `Bun.serve` configuration accordingly. [At the time of writing](https://github.com/oven-sh/bun/blob/f2cfa15e4ef9d730fc6842ad8b79fb7ab4c71cb9/packages/bun-types/bun.d.ts#L2191), `Bun.serve`'s default request body limit is 128MiB. If you set Hono's Body Limit Middleware to a value bigger than that, your requests will still fail and, additionally, the `onError` handler specified in the middleware will not be called. This is because `Bun.serve()` will set the status code to `413` and terminate the connection before passing the request to Hono.
如果要使用 Hono 和 Bun 接受大于 128MiB 的请求,则还需要为 Bun 设置限制:
¥If you want to accept requests larger than 128MiB with Hono and Bun, you need to set the limit for Bun as well:
```ts
export default {
port: process.env['PORT'] || 3000,
fetch: app.fetch,
maxRequestBodySize: 1024 * 1024 * 200, // your value here
}
```
或者,取决于你的设置:
¥or, depending on your setup:
```ts
Bun.serve({
fetch(req, server) {
return app.fetch(req, { ip: server.requestIP(req) })
},
maxRequestBodySize: 1024 * 1024 * 200, // your value here
})
```
# 缓存中间件
¥Cache Middleware
Cache 中间件使用 Web 标准的 [缓存 API](https://web.nodejs.cn/en-US/docs/Web/API/Cache)。
¥The Cache middleware uses the Web Standards' [Cache API](https://web.nodejs.cn/en-US/docs/Web/API/Cache).
Cache 中间件目前支持使用自定义域的 Cloudflare Workers 项目和使用 [Deno 1.26+](https://github.com/denoland/deno/releases/tag/v1.26.0) 的 Deno 项目。也可用于 Deno Deploy。
¥The Cache middleware currently supports Cloudflare Workers projects using custom domains and Deno projects using [Deno 1.26+](https://github.com/denoland/deno/releases/tag/v1.26.0). Also available with Deno Deploy.
Cloudflare Workers 尊重 `Cache-Control` 标头并返回缓存的响应。有关详细信息,请参阅 [Cloudflare 文档上的缓存](https://developers.cloudflare.com/workers/runtime-apis/cache/)。Deno 不尊重标头,因此如果你需要更新缓存,则需要实现自己的机制。
¥Cloudflare Workers respects the `Cache-Control` header and return cached responses. For details, refer to [Cache on Cloudflare Docs](https://developers.cloudflare.com/workers/runtime-apis/cache/). Deno does not respect headers, so if you need to update the cache, you will need to implement your own mechanism.
有关每个平台的说明,请参阅下面的 [用法](#usage)。
¥See [Usage](#usage) below for instructions on each platform.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { cache } from 'hono/cache'
```
## 用法
¥Usage
::: code-group
```ts [Cloudflare Workers]
app.get(
'*',
cache({
cacheName: 'my-app',
cacheControl: 'max-age=3600',
})
)
```
```ts [Deno]
// Must use `wait: true` for the Deno runtime
app.get(
'*',
cache({
cacheName: 'my-app',
cacheControl: 'max-age=3600',
wait: true,
})
)
```
:::
## 选项
¥Options
### cacheName:`string` | `(c: Context) => string` | `Promise`
缓存的名称。可用于存储具有不同标识符的多个缓存。
¥The name of the cache. Can be used to store multiple caches with different identifiers.
### wait:`boolean`
一个布尔值,表示 Hono 是否应等待 `cache.put` 函数的 Promise 解析后再继续请求。对于 Deno 环境,必须为真。默认为 `false`。
¥A boolean indicating if Hono should wait for the Promise of the `cache.put` function to resolve before continuing with the request. *Required to be true for the Deno environment*. The default is `false`.
### cacheControl:`string`
`Cache-Control` 标头的指令字符串。有关更多信息,请参阅 [MDN 文档](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Cache-Control)。未提供此选项时,不会将 `Cache-Control` 标头添加到请求中。
¥A string of directives for the `Cache-Control` header. See the [MDN docs](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Cache-Control) for more information. When this option is not provided, no `Cache-Control` header is added to requests.
### evolve:`string` | `string[]`
在响应中设置 `Vary` 标头。如果原始响应标头已包含 `Vary` 标头,则合并值,删除任何重复项。将其设置为 `*` 将导致错误。有关 Vary 标头及其对缓存策略的影响的更多详细信息,请参阅 [MDN 文档](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Vary)。
¥Sets the `Vary` header in the response. If the original response header already contains a `Vary` header, the values are merged, removing any duplicates. Setting this to `*` will result in an error. For more details on the Vary header and its implications for caching strategies, refer to the [MDN docs](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Vary).
### keyGenerator:`(c: Context) => string | Promise`
为 `cacheName` 存储中的每个请求生成密钥。这可用于根据请求参数或上下文参数缓存数据。默认为 `c.req.url`。
¥Generates keys for every request in the `cacheName` store. This can be used to cache data based on request parameters or context parameters. The default is `c.req.url`.
# 合并中间件
¥Combine Middleware
Combine Middleware 将多个中间件函数组合成一个中间件。它提供了三个功能:
¥Combine Middleware combines multiple middleware functions into a single middleware. It provides three functions:
* `some` - 仅运行给定的中间件之一。
¥`some` - Runs only one of the given middleware.
* `every` - 运行所有给定的中间件。
¥`every` - Runs all given middleware.
* `except` - 仅在条件不满足时运行所有给定的中间件。
¥`except` - Runs all given middleware only if a condition is not met.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { some, every, except } from 'hono/combine'
```
## 用法
¥Usage
以下是使用 Combine Middleware 的复杂访问控制规则的示例。
¥Here's an example of complex access control rules using Combine Middleware.
```ts
import { Hono } from 'hono'
import { bearerAuth } from 'hono/bearer-auth'
import { getConnInfo } from 'hono/cloudflare-workers'
import { every, some } from 'hono/combine'
import { ipRestriction } from 'hono/ip-restriction'
import { rateLimit } from '@/my-rate-limit'
const app = new Hono()
app.use(
'*',
some(
every(
ipRestriction(getConnInfo, { allowList: ['192.168.0.2'] }),
bearerAuth({ token })
),
// If both conditions are met, rateLimit will not execute.
rateLimit()
)
)
app.get('/', (c) => c.text('Hello Hono!'))
```
### some
运行第一个返回 true 的中间件。中间件按顺序应用,如果任何一个中间件成功退出,后续的中间件将不会运行。
¥Runs the first middleware that returns true. Middleware is applied in order, and if any middleware exits successfully, subsequent middleware will not run.
```ts
import { some } from 'hono/combine'
import { bearerAuth } from 'hono/bearer-auth'
import { myRateLimit } from '@/rate-limit'
// If client has a valid token, skip rate limiting.
// Otherwise, apply rate limiting.
app.use(
'/api/*',
some(bearerAuth({ token }), myRateLimit({ limit: 100 }))
)
```
### every
运行所有中间件,如果任何一个失败则停止。中间件按顺序应用,如果任何一个中间件抛出错误,后续的中间件将不会运行。
¥Runs all middleware and stops if any of them fail. Middleware is applied in order, and if any middleware throws an error, subsequent middleware will not run.
```ts
import { some, every } from 'hono/combine'
import { bearerAuth } from 'hono/bearer-auth'
import { myCheckLocalNetwork } from '@/check-local-network'
import { myRateLimit } from '@/rate-limit'
// If client is in local network, skip authentication and rate limiting.
// Otherwise, apply authentication and rate limiting.
app.use(
'/api/*',
some(
myCheckLocalNetwork(),
every(bearerAuth({ token }), myRateLimit({ limit: 100 }))
)
)
```
### except
运行所有中间件,除非满足条件。你可以传递字符串或函数作为条件。如果需要匹配多个目标,请将它们作为数组传递。
¥Runs all middleware except when the condition is met. You can pass a string or function as the condition. If multiple targets need to be matched, pass them as an array.
```ts
import { except } from 'hono/combine'
import { bearerAuth } from 'hono/bearer-auth'
// If client is accessing public API, skip authentication.
// Otherwise, require a valid token.
app.use('/api/*', except('/api/public/*', bearerAuth({ token })))
```
# 压缩中间件
¥Compress Middleware
此中间件根据 `Accept-Encoding` 请求标头压缩响应主体。
¥This middleware compresses the response body, according to `Accept-Encoding` request header.
::: info 信息
注意:在 Cloudflare Workers 和 Deno Deploy 上,响应主体将自动压缩,因此无需使用此中间件。
¥**Note**: On Cloudflare Workers and Deno Deploy, the response body will be compressed automatically, so there is no need to use this middleware.
Bun:此中间件使用 bun 中尚不支持的 `CompressionStream`。
¥**Bun**: This middleware uses `CompressionStream` which is not yet supported in bun.
:::
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { compress } from 'hono/compress'
```
## 用法
¥Usage
```ts
const app = new Hono()
app.use(compress())
```
## 选项
¥Options
### 编码:`'gzip'` | `'deflate'`
¥ encoding: `'gzip'` | `'deflate'`
允许响应压缩的压缩方案。`gzip` 或 `deflate`。如果未定义,则两者都允许,并将基于 `Accept-Encoding` 标头使用。如果未提供此选项且客户端在 `Accept-Encoding` 标头中同时提供两者,则优先考虑 `gzip`。
¥The compression scheme to allow for response compression. Either `gzip` or `deflate`. If not defined, both are allowed and will be used based on the `Accept-Encoding` header. `gzip` is prioritized if this option is not provided and the client provides both in the `Accept-Encoding` header.
### <徽章类型="info" 文本="optional" /> 阈值:`number`
¥ threshold: `number`
要压缩的最小大小(以字节为单位)。默认为 1024 字节。
¥The minimum size in bytes to compress. Defaults to 1024 bytes.
# 上下文存储中间件
¥Context Storage Middleware
Context Storage Middleware 将 Hono `Context` 存储在 `AsyncLocalStorage` 中,使其可在全球范围内访问。
¥The Context Storage Middleware stores the Hono `Context` in the `AsyncLocalStorage`, to make it globally accessible.
::: info 信息
注意此中间件使用 `AsyncLocalStorage`。运行时应该支持它。
¥**Note** This middleware uses `AsyncLocalStorage`. The runtime should support it.
Cloudflare Workers:要启用 `AsyncLocalStorage`,请将 [`nodejs_compat` 或 `nodejs_als` 标志](https://developers.cloudflare.com/workers/configuration/compatibility-dates/#nodejs-compatibility-flag) 添加到你的 `wrangler.toml` 文件中。
¥**Cloudflare Workers**: To enable `AsyncLocalStorage`, add the [`nodejs_compat` or `nodejs_als` flag](https://developers.cloudflare.com/workers/configuration/compatibility-dates/#nodejs-compatibility-flag) to your `wrangler.toml` file.
:::
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { contextStorage, getContext } from 'hono/context-storage'
```
## 用法
¥Usage
如果将 `contextStorage()` 作为中间件应用,`getContext()` 将返回当前 Context 对象。
¥The `getContext()` will return the current Context object if the `contextStorage()` is applied as a middleware.
```ts
type Env = {
Variables: {
message: string
}
}
const app = new Hono()
app.use(contextStorage())
app.use(async (c, next) => {
c.set('message', 'Hello!')
await next()
})
// You can access the variable outside the handler.
const getMessage = () => {
return getContext().var.message
}
app.get('/', (c) => {
return c.text(getMessage())
})
```
在 Cloudflare Workers 上,你可以访问处理程序外部的绑定。
¥On Cloudflare Workers, you can access the bindings outside the handler.
```ts
type Env = {
Bindings: {
KV: KVNamespace
}
}
const app = new Hono()
app.use(contextStorage())
const setKV = (value: string) => {
return getContext().env.KV.put('key', value)
}
```
# CORS 中间件
¥CORS Middleware
Cloudflare Workers 作为 Web API 有很多用例,可以从外部前端应用调用它们。对于它们,我们必须实现 CORS,让我们也使用中间件来做到这一点。
¥There are many use cases of Cloudflare Workers as Web APIs and calling them from external front-end application.
For them we have to implement CORS, let's do this with middleware as well.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { cors } from 'hono/cors'
```
## 用法
¥Usage
```ts
const app = new Hono()
// CORS should be called before the route
app.use('/api/*', cors())
app.use(
'/api2/*',
cors({
origin: 'http://example.com',
allowHeaders: ['X-Custom-Header', 'Upgrade-Insecure-Requests'],
allowMethods: ['POST', 'GET', 'OPTIONS'],
exposeHeaders: ['Content-Length', 'X-Kuma-Revision'],
maxAge: 600,
credentials: true,
})
)
app.all('/api/abc', (c) => {
return c.json({ success: true })
})
app.all('/api2/abc', (c) => {
return c.json({ success: true })
})
```
多个来源:
¥Multiple origins:
```ts
app.use(
'/api3/*',
cors({
origin: ['https://example.com', 'https://example.org'],
})
)
// Or you can use "function"
app.use(
'/api4/*',
cors({
// `c` is a `Context` object
origin: (origin, c) => {
return origin.endsWith('.example.com')
? origin
: 'http://example.com'
},
})
)
```
## 选项
¥Options
### origin:`string` | `string[]` | `(origin:string, c:Context) => string`
"Access-Control-Allow-Origin" CORS 标头的值。你还可以传递像 `origin: (origin) => (origin.endsWith('.example.com') ? origin : 'http://example.com')` 这样的回调函数。默认为 `*`。
¥The value of "*Access-Control-Allow-Origin*" CORS header. You can also pass the callback function like `origin: (origin) => (origin.endsWith('.example.com') ? origin : 'http://example.com')`. The default is `*`.
### allowMethods:`string[]`
"Access-Control-Allow-Methods" CORS 标头的值。默认为 `['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH']`。
¥The value of "*Access-Control-Allow-Methods*" CORS header. The default is `['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH']`.
### allowHeaders:`string[]`
"Access-Control-Allow-Headers" CORS 标头的值。默认为 `[]`。
¥The value of "*Access-Control-Allow-Headers*" CORS header. The default is `[]`.
### maxAge:`number`
"Access-Control-Max-Age" CORS 标头的值。
¥The value of "*Access-Control-Max-Age*" CORS header.
### 凭证:`boolean`
¥ credentials: `boolean`
"Access-Control-Allow-Credentials" CORS 标头的值。
¥The value of "*Access-Control-Allow-Credentials*" CORS header.
### 暴露标头:`string[]`
¥ exposeHeaders: `string[]`
"Access-Control-Expose-Headers" CORS 标头的值。默认为 `[]`。
¥The value of "*Access-Control-Expose-Headers*" CORS header. The default is `[]`.
## 依赖于环境的 CORS 配置
¥Environment-dependent CORS configuration
如果要根据执行环境(例如开发或生产)调整 CORS 配置,则从环境变量注入值很方便,因为它无需应用了解其自己的执行环境。有关说明,请参阅下面的示例。
¥If you want to adjust CORS configuration according to the execution environment, such as development or production, injecting values from environment variables is convenient as it eliminates the need for the application to be aware of its own execution environment. See the example below for clarification.
```ts
app.use('*', async (c, next) => {
const corsMiddlewareHandler = cors({
origin: c.env.CORS_ORIGIN,
})
return corsMiddlewareHandler(c, next)
})
```
# CSRF 保护
¥CSRF Protection
CSRF Protection 中间件通过检查请求标头来防止 CSRF 攻击。
¥CSRF Protection Middleware prevents CSRF attacks by checking request headers.
此中间件通过将 `Origin` 标头的值与请求的 URL 进行比较来防止 CSRF 攻击(例如使用表单元素提交)。
¥This middleware protects against CSRF attacks such as submitting with a form element by comparing the value of the `Origin` header with the requested URL.
不发送 `Origin` 标头的旧浏览器或使用反向代理删除 `Origin` 标头的环境可能无法正常工作。在这样的环境中,使用其他 CSRF 令牌方法。
¥Old browsers that do not send `Origin` headers, or environments that use reverse proxies to remove `Origin` headers, may not work well. In such environments, use the other CSRF token methods.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { csrf } from 'hono/csrf'
```
## 用法
¥Usage
```ts
const app = new Hono()
app.use(csrf())
// Specifying origins with using `origin` option
// string
app.use(csrf({ origin: 'myapp.example.com' }))
// string[]
app.use(
csrf({
origin: ['myapp.example.com', 'development.myapp.example.com'],
})
)
// Function
// It is strongly recommended that the protocol be verified to ensure a match to `$`.
// You should *never* do a forward match.
app.use(
'*',
csrf({
origin: (origin) =>
/https:\/\/(\w+\.)?myapp\.example\.com$/.test(origin),
})
)
```
## 选项
¥Options
### origin:`string` | `string[]` | `Function`
指定来源。
¥Specify origins.
# ETag 中间件
¥ETag Middleware
使用此中间件,你可以轻松添加 ETag 标头。
¥Using this middleware, you can add ETag headers easily.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { etag } from 'hono/etag'
```
## 用法
¥Usage
```ts
const app = new Hono()
app.use('/etag/*', etag())
app.get('/etag/abc', (c) => {
return c.text('Hono is cool')
})
```
## 保留的标头
¥The retained headers
304 响应必须包含在等效 200 OK 响应中发送的标头。默认标头为 Cache-Control、Content-Location、Date、ETag、Expires 和 Vary。
¥The 304 Response must include the headers that would have been sent in an equivalent 200 OK response. The default headers are Cache-Control, Content-Location, Date, ETag, Expires, and Vary.
如果要添加发送的标头,可以使用 `retainedHeaders` 选项和包含默认标头的 `RETAINED_304_HEADERS` 字符串数组变量:
¥If you want to add the header that is sent, you can use `retainedHeaders` option and `RETAINED_304_HEADERS` strings array variable that includes the default headers:
```ts
import { etag, RETAINED_304_HEADERS } from 'hono/etag'
// ...
app.use(
'/etag/*',
etag({
retainedHeaders: ['x-message', ...RETAINED_304_HEADERS],
})
)
```
## 选项
¥Options
### strong:`boolean`
定义使用或不使用 [弱验证](https://web.nodejs.cn/en-US/docs/Web/HTTP/Conditional_requests#weak_validation)。如果设置了 `true`,则将 `w/` 添加到值的前缀。默认为 `false`。
¥Define using or not using a [weak validation](https://web.nodejs.cn/en-US/docs/Web/HTTP/Conditional_requests#weak_validation). If `true` is set, then `w/` is added to the prefix of the value. The default is `false`.
### <徽章类型="info" 文本="optional" /> 保留标头:`string[]`
¥ retainedHeaders: `string[]`
你想要在 304 响应中保留的标头。
¥The headers that you want to retain in the 304 Response.
### 生成摘要:`(body: Uint8Array) => ArrayBuffer | Promise`
¥ generateDigest: `(body: Uint8Array) => ArrayBuffer | Promise`
自定义摘要生成函数。默认情况下,它使用 `SHA-1`。此函数以响应主体作为 `Uint8Array` 调用,并应返回哈希作为 `ArrayBuffer` 或 Promise 1。
¥A custom digest generation function. By default, it uses `SHA-1`. This function is called with the response body as a `Uint8Array` and should return a hash as an `ArrayBuffer` or a Promise of one.
# IP 限制中间件
¥IP Restriction Middleware
IP 限制中间件是根据用户的 IP 地址限制对资源的访问的中间件。
¥IP Restriction Middleware is middleware that limits access to resources based on the IP address of the user.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { ipRestriction } from 'hono/ip-restriction'
```
## 用法
¥Usage
对于在 Bun 上运行的应用,如果你只想允许从本地访问,可以按如下方式编写。指定你想要在 `denyList` 中拒绝的规则和你想要在 `allowList` 中允许的规则。
¥For your application running on Bun, if you want to allow access only from local, you can write it as follows. Specify the rules you want to deny in the `denyList` and the rules you want to allow in the `allowList`.
```ts
import { Hono } from 'hono'
import { getConnInfo } from 'hono/bun'
import { ipRestriction } from 'hono/ip-restriction'
const app = new Hono()
app.use(
'*',
ipRestriction(getConnInfo, {
denyList: [],
allowList: ['127.0.0.1', '::1'],
})
)
app.get('/', (c) => c.text('Hello Hono!'))
```
将适合你环境的 [ConnInfo 助手](/docs/helpers/conninfo) 中的 `getConninfo` 作为 `ipRestriction` 的第一个参数传递。例如,对于 Deno,它看起来像这样:
¥Pass the `getConninfo` from the [ConnInfo helper](/docs/helpers/conninfo) appropriate for your environment as the first argument of `ipRestriction`. For example, for Deno, it would look like this:
```ts
import { getConnInfo } from 'hono/deno'
import { ipRestriction } from 'hono/ip-restriction'
//...
app.use(
'*',
ipRestriction(getConnInfo, {
// ...
})
)
```
## 规则
¥Rules
按照以下说明编写规则。
¥Follow the instructions below for writing rules.
### IPv4
* `192.168.2.0` - 静态 IP 地址
¥`192.168.2.0` - Static IP Address
* `192.168.2.0/24` - CIDR 表示法
¥`192.168.2.0/24` - CIDR Notation
* `*` - 所有地址
¥`*` - ALL Addresses
### IPv6
* `::1` - 静态 IP 地址
¥`::1` - Static IP Address
* `::1/10` - CIDR 表示法
¥`::1/10` - CIDR Notation
* `*` - 所有地址
¥`*` - ALL Addresses
## 错误处理
¥Error handling
要自定义错误,请在第三个参数中返回 `Response`。
¥To customize the error, return a `Response` in the third argument.
```ts
app.use(
'*',
ipRestriction(
getConnInfo,
{
denyList: ['192.168.2.0/24'],
},
async (remote, c) => {
return c.text(`Blocking access from ${remote.addr}`, 403)
}
)
)
```
# JSX 渲染器中间件
¥JSX Renderer Middleware
JSX Renderer Middleware 允许你在使用 `c.render()` 函数渲染 JSX 时设置布局,而无需使用 `c.setRenderer()`。此外,它通过使用 `useRequestContext()` 允许访问组件内的 Context 实例。
¥JSX Renderer Middleware allows you to set up the layout when rendering JSX with the `c.render()` function, without the need for using `c.setRenderer()`. Additionally, it enables access to instances of Context within components through the use of `useRequestContext()`.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { jsxRenderer, useRequestContext } from 'hono/jsx-renderer'
```
## 用法
¥Usage
```jsx
const app = new Hono()
app.get(
'/page/*',
jsxRenderer(({ children }) => {
return (
Menu
)
})
```
## 选项
¥Options
### docType:`boolean` | `string`
如果你不想在 HTML 开头添加 DOCTYPE,请将 `docType` 选项设置为 `false`。
¥If you do not want to add a DOCTYPE at the beginning of the HTML, set the `docType` option to `false`.
```tsx
app.use(
'*',
jsxRenderer(
({ children }) => {
return (
{children}
)
},
{ docType: false }
)
)
```
你可以指定 DOCTYPE。
¥And you can specify the DOCTYPE.
```tsx
app.use(
'*',
jsxRenderer(
({ children }) => {
return (
{children}
)
},
{
docType:
'',
}
)
)
```
### <徽章类型="info" 文本="optional" /> 流:`boolean` | `Record`
¥ stream: `boolean` | `Record`
如果你将其设置为 `true` 或提供 Record 值,它将被渲染为流式响应。
¥If you set it to `true` or provide a Record value, it will be rendered as a streaming response.
```tsx
const AsyncComponent = async () => {
await new Promise((r) => setTimeout(r, 1000)) // sleep 1s
return
{children}
)
},
{ stream: true }
)
)
app.get('/', (c) => {
return c.render(
loading...}>
)
})
```
如果设置了 `true`,则添加以下标头:
¥If `true` is set, the following headers are added:
```ts
{
'Transfer-Encoding': 'chunked',
'Content-Type': 'text/html; charset=UTF-8',
'Content-Encoding': 'Identity'
}
```
你可以通过指定记录值来自定义标头值。
¥You can customize the header values by specifying the Record values.
## 嵌套布局
¥Nested Layouts
`Layout` 组件允许嵌套布局。
¥The `Layout` component enables nesting the layouts.
```tsx
app.use(
jsxRenderer(({ children }) => {
return (
{children}
)
})
)
const blog = new Hono()
blog.use(
jsxRenderer(({ children, Layout }) => {
return (
{children}
)
})
)
app.route('/blog', blog)
```
## `useRequestContext()`
`useRequestContext()` 返回 Context 的实例。
¥`useRequestContext()` returns an instance of Context.
```tsx
import { useRequestContext, jsxRenderer } from 'hono/jsx-renderer'
const app = new Hono()
app.use(jsxRenderer())
const RequestUrlBadge: FC = () => {
const c = useRequestContext()
return {c.req.url}
}
app.get('/page/info', (c) => {
return c.render(
You are accessing:
)
})
```
::: warning 警告
你不能将 `useRequestContext()` 与 Deno 的 `precompile` JSX 选项一起使用。使用 `react-jsx`:
¥You can't use `useRequestContext()` with the Deno's `precompile` JSX option. Use the `react-jsx`:
```json
"compilerOptions": {
"jsx": "precompile", // [!code --]
"jsx": "react-jsx", // [!code ++]
"jsxImportSource": "hono/jsx"
}
}
```
:::
## 扩展 `ContextRenderer`
¥Extending `ContextRenderer`
通过定义 `ContextRenderer`(如下所示),你可以将其他内容传递给渲染器。例如,当你想要根据页面更改 head 标签的内容时,这很方便。
¥By defining `ContextRenderer` as shown below, you can pass additional content to the renderer. This is handy, for instance, when you want to change the contents of the head tag depending on the page.
```tsx
declare module 'hono' {
interface ContextRenderer {
(
content: string | Promise,
props: { title: string }
): Response
}
}
const app = new Hono()
app.get(
'/page/*',
jsxRenderer(({ children, title }) => {
return (
{title}Menu
,
{
title: 'My favorites',
}
)
})
```
# JWK Auth 中间件
¥JWK Auth Middleware
JWK Auth 中间件通过使用 JWK(JSON Web Key)验证令牌来验证请求。如果指定,它会检查 `Authorization` 标头和其他配置的源,例如 cookie。具体来说,它使用提供的 `keys` 验证令牌,如果指定,则从 `jwks_uri` 检索密钥,如果设置了 `cookie` 选项,则支持从 cookie 中提取令牌。
¥The JWK Auth Middleware authenticates requests by verifying tokens using JWK (JSON Web Key). It checks for an `Authorization` header and other configured sources, such as cookies, if specified. Specifically, it validates tokens using the provided `keys`, retrieves keys from `jwks_uri` if specified, and supports token extraction from cookies if the `cookie` option is set.
:::info 信息
从客户端发送的 Authorization 标头必须具有指定的方案。
¥The Authorization header sent from the client must have a specified scheme.
示例:`Bearer my.token.value` 或 `Basic my.token.value`
¥Example: `Bearer my.token.value` or `Basic my.token.value`
:::
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { jwk } from 'hono/jwk'
```
## 用法
¥Usage
```ts
const app = new Hono()
app.use(
'/auth/*',
jwk({
jwks_uri: `https://${backendServer}/.well-known/jwks.json`,
})
)
app.get('/auth/page', (c) => {
return c.text('You are authorized')
})
```
获取有效负载:
¥Get payload:
```ts
const app = new Hono()
app.use(
'/auth/*',
jwk({
jwks_uri: `https://${backendServer}/.well-known/jwks.json`,
})
)
app.get('/auth/page', (c) => {
const payload = c.get('jwtPayload')
return c.json(payload) // eg: { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
})
```
## 选项
¥Options
### 键:`HonoJsonWebKey[] | (() => Promise)`
¥ keys: `HonoJsonWebKey[] | (() => Promise)`
你的公钥的值,或返回它们的函数。
¥The values of your public keys, or a function that returns them.
### jwks\_uri:`string`
如果设置了此值,则尝试从此 URI 获取 JWK,期望 JSON 响应带有 `keys`,这些响应将添加到提供的 `keys` 选项中。
¥If this value is set, attempt to fetch JWKs from this URI, expecting a JSON response with `keys`, which are added to the provided `keys` option.
### cookie:`string`
如果设置了此值,则使用该值作为密钥从 cookie 标头中检索该值,然后将其验证为令牌。
¥If this value is set, then the value is retrieved from the cookie header using that value as a key, which is then validated as a token.
# JWT Auth 中间件
¥JWT Auth Middleware
JWT Auth 中间件通过使用 JWT 验证令牌来提供身份验证。如果未设置 `cookie` 选项,中间件将检查 `Authorization` 标头。
¥The JWT Auth Middleware provides authentication by verifying the token with JWT.
The middleware will check for an `Authorization` header if the `cookie` option is not set.
:::info 信息
从客户端发送的 Authorization 标头必须具有指定的方案。
¥The Authorization header sent from the client must have a specified scheme.
示例:`Bearer my.token.value` 或 `Basic my.token.value`
¥Example: `Bearer my.token.value` or `Basic my.token.value`
:::
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { jwt } from 'hono/jwt'
import type { JwtVariables } from 'hono/jwt'
```
## 用法
¥Usage
```ts
// Specify the variable types to infer the `c.get('jwtPayload')`:
type Variables = JwtVariables
const app = new Hono<{ Variables: Variables }>()
app.use(
'/auth/*',
jwt({
secret: 'it-is-very-secret',
})
)
app.get('/auth/page', (c) => {
return c.text('You are authorized')
})
```
获取有效负载:
¥Get payload:
```ts
const app = new Hono()
app.use(
'/auth/*',
jwt({
secret: 'it-is-very-secret',
})
)
app.get('/auth/page', (c) => {
const payload = c.get('jwtPayload')
return c.json(payload) // eg: { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
})
```
::: tip 提示
`jwt()` 只是一个中间件函数。如果要使用环境变量(例如:`c.env.JWT_SECRET`),可以按如下方式使用:
¥`jwt()` is just a middleware function. If you want to use an environment variable (eg: `c.env.JWT_SECRET`), you can use it as follows:
```js
app.use('/auth/*', (c, next) => {
const jwtMiddleware = jwt({
secret: c.env.JWT_SECRET,
})
return jwtMiddleware(c, next)
})
```
:::
## 选项
¥Options
### <徽章类型="danger" 文本="required" /> 秘密:`string`
¥ secret: `string`
你的密钥值。
¥A value of your secret key.
### cookie:`string`
如果设置了此值,则使用该值作为密钥从 cookie 标头中检索该值,然后将其验证为令牌。
¥If this value is set, then the value is retrieved from the cookie header using that value as a key, which is then validated as a token.
### alg:`string`
用于验证的算法类型。默认为 `HS256`。
¥An algorithm type that is used for verifying.\ The default is `HS256`.
可用的类型有 `HS256` | `HS384` | `HS512` | `RS256` | `RS384` | `RS512` | `PS256` | `PS384` | `PS512` | `ES256` | `ES384` | `ES512` | `EdDSA`。
¥Available types are `HS256` | `HS384` | `HS512` | `RS256` | `RS384` | `RS512` | `PS256` | `PS384` | `PS512` | `ES256` | `ES384` | `ES512` | `EdDSA`.
# 语言中间件
¥Language Middleware
语言检测器中间件会自动从各种来源确定用户的首选语言(语言环境),并通过 `c.get('language')` 提供该语言。检测策略包括查询参数、cookie、标头和 URL 路径段。非常适合国际化 (i18n) 和特定于语言环境的内容。
¥The Language Detector middleware automatically determines a user's preferred language (locale) from various sources and makes it available via `c.get('language')`. Detection strategies include query parameters, cookies, headers, and URL path segments. Perfect for internationalization (i18n) and locale-specific content.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { languageDetector } from 'hono/language'
```
## 基本用法
¥Basic Usage
从查询字符串、cookie 和标头(默认顺序)检测语言,并回退到英语:
¥Detect language from query string, cookie, and header (default order), with fallback to English:
```ts
const app = new Hono()
app.use(
languageDetector({
supportedLanguages: ['en', 'ar', 'ja'], // Must include fallback
fallbackLanguage: 'en', // Required
})
)
app.get('/', (c) => {
const lang = c.get('language')
return c.text(`Hello! Your language is ${lang}`)
})
```
### 客户端示例
¥Client Examples
```sh
# Via path
curl http://localhost:8787/ar/home
# Via query parameter
curl http://localhost:8787/?lang=ar
# Via cookie
curl -H 'Cookie: language=ja' http://localhost:8787/
# Via header
curl -H 'Accept-Language: ar,en;q=0.9' http://localhost:8787/
```
## 默认配置
¥Default Configuration
```ts
export const DEFAULT_OPTIONS: DetectorOptions = {
order: ['querystring', 'cookie', 'header'],
lookupQueryString: 'lang',
lookupCookie: 'language',
lookupFromHeaderKey: 'accept-language',
lookupFromPathIndex: 0,
caches: ['cookie'],
ignoreCase: true,
fallbackLanguage: 'en',
supportedLanguages: ['en'],
cookieOptions: {
sameSite: 'Strict',
secure: true,
maxAge: 365 * 24 * 60 * 60,
httpOnly: true,
},
debug: false,
}
```
## 关键行为
¥Key Behaviors
### 检测工作流程
¥Detection Workflow
1. 顺序:默认按此顺序检查源:
¥**Order**: Checks sources in this sequence by default:
* 查询参数 (?lang=ar)
¥Query parameter (?lang=ar)
* Cookie(语言=ar)
¥Cookie (language=ar)
* Accept-Language 标头
¥Accept-Language header
2. 缓存:将检测到的语言存储在 cookie 中(默认为 1 年)
¥**Caching**: Stores detected language in a cookie (1 year by default)
3. 后备:如果没有有效检测则使用 `fallbackLanguage`(必须在 `supportedLanguages` 中)
¥**Fallback**: Uses `fallbackLanguage` if no valid detection (must be in `supportedLanguages`)
## 高级配置
¥Advanced Configuration
### 自定义检测顺序
¥Custom Detection Order
优先考虑 URL 路径检测(例如,/en/about):
¥Prioritize URL path detection (e.g., /en/about):
```ts
app.use(
languageDetector({
order: ['path', 'cookie', 'querystring', 'header'],
lookupFromPathIndex: 0, // /en/profile → index 0 = 'en'
supportedLanguages: ['en', 'ar'],
fallbackLanguage: 'en',
})
)
```
### 语言代码转换
¥Language Code Transformation
规范化复杂代码(例如,en-US → en):
¥Normalize complex codes (e.g., en-US → en):
```ts
app.use(
languageDetector({
convertDetectedLanguage: (lang) => lang.split('-')[0],
supportedLanguages: ['en', 'ja'],
fallbackLanguage: 'en',
})
)
```
### Cookie 配置
¥Cookie Configuration
```ts
app.use(
languageDetector({
lookupCookie: 'app_lang',
caches: ['cookie'],
cookieOptions: {
path: '/', // Cookie path
sameSite: 'Lax', // Cookie same-site policy
secure: true, // Only send over HTTPS
maxAge: 86400 * 365, // 1 year expiration
httpOnly: true, // Not accessible via JavaScript
domain: '.example.com', // Optional: specific domain
},
})
)
```
要禁用 cookie 缓存:
¥To disable cookie caching:
```ts
languageDetector({
caches: false,
})
```
### 调试
¥Debugging
日志检测步骤:
¥Log detection steps:
```ts
languageDetector({
debug: true, // Shows: "Detected from querystring: ar"
})
```
## 选项参考
¥Options Reference
### 基本选项
¥Basic Options
| 选项 | 类型 | 默认 | 必需 | 描述 |
| :------------------- | :--------------- | :------------------------------------ | :- | :------ |
| `supportedLanguages` | `string[]` | `['en']` | 是 | 允许的语言代码 |
| `fallbackLanguage` | `string` | `'en'` | 是 | 默认语言 |
| `order` | `DetectorType[]` | `['querystring', 'cookie', 'header']` | 否 | 检测序列 |
| `debug` | `boolean` | `false` | 否 | 启用日志记录 |
### 检测选项
¥Detection Options
| 选项 | 类型 | 默认 | 描述 |
| :-------------------- | :------- | :------------------ | :-------- |
| `lookupQueryString` | `string` | `'lang'` | 查询参数名称 |
| `lookupCookie` | `string` | `'language'` | Cookie 名称 |
| `lookupFromHeaderKey` | `string` | `'accept-language'` | 标头名称 |
| `lookupFromPathIndex` | `number` | `0` | 路径段索引 |
### Cookie 选项
¥Cookie Options
| 选项 | 类型 | 默认 | 描述 |
| :----------------------- | :---------------------------- | :----------- | :---------- |
| `caches` | `CacheType[] \| false` | `['cookie']` | 缓存设置 |
| `cookieOptions.path` | `string` | `'/'` | Cookie 路径 |
| `cookieOptions.sameSite` | `'Strict' \| 'Lax' \| 'None'` | `'Strict'` | SameSite 策略 |
| `cookieOptions.secure` | `boolean` | `true` | 仅 HTTPS |
| `cookieOptions.maxAge` | `number` | `31536000` | 到期时间(秒) |
| `cookieOptions.httpOnly` | `boolean` | `true` | JS 可访问性 |
| `cookieOptions.domain` | `string` | `undefined` | Cookie 域 |
### 高级选项
¥Advanced Options
| 选项 | 类型 | 默认 | 描述 |
| :------------------------ | :------------------------- | :---------- | :-------- |
| `ignoreCase` | `boolean` | `true` | 不区分大小写的匹配 |
| `convertDetectedLanguage` | `(lang: string) => string` | `undefined` | 语言代码转换器 |
## 验证和错误处理
¥Validation & Error Handling
* `fallbackLanguage` 必须在 `supportedLanguages` 中(设置期间会抛出错误)
¥`fallbackLanguage` must be in `supportedLanguages` (throws error during setup)
* `lookupFromPathIndex` 必须≥0
¥`lookupFromPathIndex` must be ≥ 0
* 无效配置在中间件初始化期间抛出错误
¥Invalid configurations throw errors during middleware initialization
* 失败的检测会默默使用 `fallbackLanguage`
¥Failed detections silently use `fallbackLanguage`
## 常用秘诀
¥Common Recipes
### 基于路径的路由
¥Path-Based Routing
```ts
app.get('/:lang/home', (c) => {
const lang = c.get('language') // 'en', 'ar', etc.
return c.json({ message: getLocalizedContent(lang) })
})
```
### 支持多种语言
¥Multiple Supported Languages
```ts
languageDetector({
supportedLanguages: ['en', 'en-GB', 'ar', 'ar-EG'],
convertDetectedLanguage: (lang) => lang.replace('_', '-'), // Normalize
})
```
# 日志器中间件
¥Logger Middleware
这是一个简单的日志器。
¥It's a simple logger.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { logger } from 'hono/logger'
```
## 用法
¥Usage
```ts
const app = new Hono()
app.use(logger())
app.get('/', (c) => c.text('Hello Hono!'))
```
## 日志详细信息
¥Logging Details
Logger 中间件记录每个请求的以下详细信息:
¥The Logger Middleware logs the following details for each request:
* 传入请求:记录 HTTP 方法、请求路径和传入请求。
¥**Incoming Request**: Logs the HTTP method, request path, and incoming request.
* 传出响应:记录 HTTP 方法、请求路径、响应状态代码和请求/响应时间。
¥**Outgoing Response**: Logs the HTTP method, request path, response status code, and request/response times.
* 状态代码着色:响应状态代码采用颜色编码,以便更好地查看和快速识别状态类别。不同的状态代码类别由不同的颜色表示。
¥**Status Code Coloring**: Response status codes are color-coded for better visibility and quick identification of status categories. Different status code categories are represented by different colors.
* 已用时间:请求/响应周期所花费的时间以人性化的形式记录,以毫秒 (ms) 或秒 (s) 为单位。
¥**Elapsed Time**: The time taken for the request/response cycle is logged in a human-readable format, either in milliseconds (ms) or seconds (s).
通过使用 Logger 中间件,你可以轻松监控 Hono 应用中的请求和响应流,并快速识别任何问题或性能瓶颈。
¥By using the Logger Middleware, you can easily monitor the flow of requests and responses in your Hono application and quickly identify any issues or performance bottlenecks.
你还可以通过提供自己的 `PrintFunc` 函数来进一步扩展中间件,以实现定制的日志记录行为。
¥You can also extend the middleware further by providing your own `PrintFunc` function for tailored logging behavior.
## PrintFunc
Logger 中间件接受可选的 `PrintFunc` 函数作为参数。此函数允许你自定义日志器并添加其他日志。
¥The Logger Middleware accepts an optional `PrintFunc` function as a parameter. This function allows you to customize the logger and add additional logs.
## 选项
¥Options
### fn:`PrintFunc(str: string, ...rest: string[])`
* `str`:由日志器传递。
¥`str`: Passed by the logger.
* `...rest`:要打印到控制台的其他字符串属性。
¥`...rest`: Additional string props to be printed to console.
### 示例
¥Example
为 Logger 中间件设置自定义 `PrintFunc` 函数:
¥Setting up a custom `PrintFunc` function to the Logger Middleware:
```ts
export const customLogger = (message: string, ...rest: string[]) => {
console.log(message, ...rest)
}
app.use(logger(customLogger))
```
在路由中设置自定义日志器:
¥Setting up the custom logger in a route:
```ts
app.post('/blog', (c) => {
// Routing logic
customLogger('Blog saved:', `Path: ${blog.url},`, `ID: ${blog.id}`)
// Output
// <-- POST /blog
// Blog saved: Path: /blog/example, ID: 1
// --> POST /blog 201 93ms
// Return Context
})
```
# 方法覆盖中间件
¥Method Override Middleware
此中间件根据表单、标头或查询的值执行指定方法的处理程序(该方法与请求的实际方法不同),并返回其响应。
¥This middleware executes the handler of the specified method, which is different from the actual method of the request, depending on the value of the form, header, or query, and returns its response.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { methodOverride } from 'hono/method-override'
```
## 用法
¥Usage
```ts
const app = new Hono()
// If no options are specified, the value of `_method` in the form,
// e.g. DELETE, is used as the method.
app.use('/posts', methodOverride({ app }))
app.delete('/posts', (c) => {
// ....
})
```
## 例如
¥For example
由于 HTML 表单无法发送 DELETE 方法,你可以将值 `DELETE` 放在名为 `_method` 的属性中并发送它。`app.delete()` 的处理程序将被执行。
¥Since HTML forms cannot send a DELETE method, you can put the value `DELETE` in the property named `_method` and send it. And the handler for `app.delete()` will be executed.
HTML 表单:
¥The HTML form:
```html
```
应用:
¥The application:
```ts
import { methodOverride } from 'hono/method-override'
const app = new Hono()
app.use('/posts', methodOverride({ app }))
app.delete('/posts', () => {
// ...
})
```
你可以更改默认值或使用标头值和查询值:
¥You can change the default values or use the header value and query value:
```ts
app.use('/posts', methodOverride({ app, form: '_custom_name' }))
app.use(
'/posts',
methodOverride({ app, header: 'X-METHOD-OVERRIDE' })
)
app.use('/posts', methodOverride({ app, query: '_method' }))
```
## 选项
¥Options
### app:`Hono`
你的应用中使用了 `Hono` 的实例。
¥The instance of `Hono` is used in your application.
### 表格:`string`
¥ form: `string`
表单键,其值包含方法名称。默认为 `_method`。
¥Form key with a value containing the method name.
The default is `_method`.
### 标头:`boolean`
¥ header: `boolean`
标头名称,其值包含方法名称。
¥Header name with a value containing the method name.
### 查询:`boolean`
¥ query: `boolean`
查询参数键,其值包含方法名称。
¥Query parameter key with a value containing the method name.
# 漂亮的 JSON 中间件
¥Pretty JSON Middleware
Pretty JSON 中间件为 JSON 响应主体启用 "JSON 漂亮打印"。将 `?pretty` 添加到 url 查询参数,JSON 字符串将被美化。
¥Pretty JSON middleware enables "*JSON pretty print*" for JSON response body.
Adding `?pretty` to url query param, the JSON strings are prettified.
```js
// GET /
{"project":{"name":"Hono","repository":"https://github.com/honojs/hono"}}
```
将是:
¥will be:
```js
// GET /?pretty
{
"project": {
"name": "Hono",
"repository": "https://github.com/honojs/hono"
}
}
```
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { prettyJSON } from 'hono/pretty-json'
```
## 用法
¥Usage
```ts
const app = new Hono()
app.use(prettyJSON()) // With options: prettyJSON({ space: 4 })
app.get('/', (c) => {
return c.json({ message: 'Hono!' })
})
```
## 选项
¥Options
### <徽章类型="info" 文本="optional" /> 空间:`number`
¥ space: `number`
缩进的空格数。默认为 `2`。
¥Number of spaces for indentation. The default is `2`.
### 查询:`string`
¥ query: `string`
用于应用的查询字符串的名称。默认为 `pretty`。
¥The name of the query string for applying. The default is `pretty`.
# 请求 ID 中间件
¥Request ID Middleware
请求 ID 中间件为每个请求生成一个唯一的 ID,你可以在处理程序中使用该 ID。
¥Request ID Middleware generates a unique ID for each request, which you can use in your handlers.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { requestId } from 'hono/request-id'
```
## 用法
¥Usage
你可以通过应用请求 ID 中间件的处理程序和中间件中的 `requestId` 变量访问请求 ID。
¥You can access the Request ID through the `requestId` variable in the handlers and middleware to which the Request ID Middleware is applied.
```ts
const app = new Hono()
app.use('*', requestId())
app.get('/', (c) => {
return c.text(`Your request id is ${c.get('requestId')}`)
})
```
如果你想要明确指定类型,请导入 `RequestIdVariables` 并将其传入 `new Hono()` 的泛型中。
¥If you want to explicitly specify the type, import `RequestIdVariables` and pass it in the generics of `new Hono()`.
```ts
import type { RequestIdVariables } from 'hono/request-id'
const app = new Hono<{
Variables: RequestIdVariables
}>()
```
## 选项
¥Options
### limitLength:`number`
请求 ID 的最大长度。默认为 `255`。
¥The maximum length of the request ID. The default is `255`.
### 标头名称:`string`
¥ headerName: `string`
用于请求 ID 的标头名称。默认为 `X-Request-Id`。
¥The header name used for the request ID. The default is `X-Request-Id`.
### 生成器:`(c: Context) => string`
¥ generator: `(c: Context) => string`
请求 ID 生成函数。默认情况下,它使用 `crypto.randomUUID()`。
¥The request ID generation function. By default, it uses `crypto.randomUUID()`.
# 安全标头中间件
¥Secure Headers Middleware
安全标头中间件简化了安全标头的设置。部分受 Helmet 功能的启发,它允许你控制特定安全标头的激活和停用。
¥Secure Headers Middleware simplifies the setup of security headers. Inspired in part by the capabilities of Helmet, it allows you to control the activation and deactivation of specific security headers.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { secureHeaders } from 'hono/secure-headers'
```
## 用法
¥Usage
你可以默认使用最佳设置。
¥You can use the optimal settings by default.
```ts
const app = new Hono()
app.use(secureHeaders())
```
你可以通过将不必要的标头设置为 false 来抑制它们。
¥You can suppress unnecessary headers by setting them to false.
```ts
const app = new Hono()
app.use(
'*',
secureHeaders({
xFrameOptions: false,
xXssProtection: false,
})
)
```
你可以使用字符串覆盖默认标头值。
¥You can override default header values using a string.
```ts
const app = new Hono()
app.use(
'*',
secureHeaders({
strictTransportSecurity:
'max-age=63072000; includeSubDomains; preload',
xFrameOptions: 'DENY',
xXssProtection: '1',
})
)
```
## 支持的选项
¥Supported Options
每个选项对应以下 Header 键值对。
¥Each option corresponds to the following Header Key-Value pairs.
| 选项 | 标头 | 值 | 默认 |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------- | ----- |
| * | X-Powered-By | (删除标头) | True |
| contentSecurityPolicy | [Content-Security-Policy](https://web.nodejs.cn/en-US/docs/Web/HTTP/CSP) | 用法:[设置内容安全策略](#setting-content-security-policy) | 无设置 |
| contentSecurityPolicyReportOnly | [Content-Security-Policy-Report-Only](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only) | 用法:[设置内容安全策略](#setting-content-security-policy) | 无设置 |
| crossOriginEmbedderPolicy | [Cross-Origin-Embedder-Policy](https://web.nodejs.cn/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy) | require-corp | **假** |
| crossOriginResourcePolicy | [Cross-Origin-Resource-Policy](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy) | same-origin | True |
| crossOriginOpenerPolicy | [Cross-Origin-Opener-Policy](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy) | same-origin | True |
| originAgentCluster | [Origin-Agent-Cluster](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Origin-Agent-Cluster) | ?1 | True |
| referrerPolicy | [Referrer-Policy](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Referrer-Policy) | no-referrer | True |
| reportingEndpoints | [Reporting-Endpoints](https://www.w3.org/TR/reporting-1/#header) | 用法:[设置内容安全策略](#setting-content-security-policy) | 无设置 |
| reportTo | [Report-To](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to) | 用法:[设置内容安全策略](#setting-content-security-policy) | 无设置 |
| strictTransportSecurity | [Strict-Transport-Security](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security) | max-age=15552000;includeSubDomains | True |
| xContentTypeOptions | [X-Content-Type-Options](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options) | nosniff | True |
| xDnsPrefetchControl | [X-DNS-Prefetch-Control](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control) | off | True |
| xDownloadOptions | [X-Download-Options](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/X-Download-Options) | noopen | True |
| xFrameOptions | [X-Frame-Options](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/X-Frame-Options) | SAMEORIGIN | True |
| xPermittedCrossDomainPolicies | [X-Permitted-Cross-Domain-Policies](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/X-Permitted-Cross-Domain-Policies) | none | True |
| xXssProtection | [X-XSS-Protection](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/X-XSS-Protection) | 0 | True |
| permissionPolicy | [权限策略](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Permissions-Policy) | 用法:[设置权限策略](#setting-permission-policy) | 无设置 |
## 中间件冲突
¥Middleware Conflict
处理操纵相同标头的中间件时,请谨慎规范的顺序。
¥Please be cautious about the order of specification when dealing with middleware that manipulates the same header.
在这种情况下,Secure-headers 运行并删除 `x-powered-by`:
¥In this case, Secure-headers operates and the `x-powered-by` is removed:
```ts
const app = new Hono()
app.use(secureHeaders())
app.use(poweredBy())
```
在这种情况下,Powered-By 运行并添加 `x-powered-by`:
¥In this case, Powered-By operates and the `x-powered-by` is added:
```ts
const app = new Hono()
app.use(poweredBy())
app.use(secureHeaders())
```
## 设置内容安全策略
¥Setting Content-Security-Policy
```ts
const app = new Hono()
app.use(
'/test',
secureHeaders({
reportingEndpoints: [
{
name: 'endpoint-1',
url: 'https://example.com/reports',
},
],
// -- or alternatively
// reportTo: [
// {
// group: 'endpoint-1',
// max_age: 10886400,
// endpoints: [{ url: 'https://example.com/reports' }],
// },
// ],
contentSecurityPolicy: {
defaultSrc: ["'self'"],
baseUri: ["'self'"],
childSrc: ["'self'"],
connectSrc: ["'self'"],
fontSrc: ["'self'", 'https:', 'data:'],
formAction: ["'self'"],
frameAncestors: ["'self'"],
frameSrc: ["'self'"],
imgSrc: ["'self'", 'data:'],
manifestSrc: ["'self'"],
mediaSrc: ["'self'"],
objectSrc: ["'none'"],
reportTo: 'endpoint-1',
sandbox: ['allow-same-origin', 'allow-scripts'],
scriptSrc: ["'self'"],
scriptSrcAttr: ["'none'"],
scriptSrcElem: ["'self'"],
styleSrc: ["'self'", 'https:', "'unsafe-inline'"],
styleSrcAttr: ['none'],
styleSrcElem: ["'self'", 'https:', "'unsafe-inline'"],
upgradeInsecureRequests: [],
workerSrc: ["'self'"],
},
})
)
```
### `nonce` 属性
¥`nonce` attribute
你可以通过将从 `hono/secure-headers` 导入的 `NONCE` 添加到 `scriptSrc` 或 `styleSrc` 来将 [`nonce` 属性](https://web.nodejs.cn/en-US/docs/Web/HTML/Global_attributes/nonce) 添加到 `script` 或 `style` 元素:
¥You can add a [`nonce` attribute](https://web.nodejs.cn/en-US/docs/Web/HTML/Global_attributes/nonce) to a `script` or `style` element by adding the `NONCE` imported from `hono/secure-headers` to a `scriptSrc` or `styleSrc`:
```tsx
import { secureHeaders, NONCE } from 'hono/secure-headers'
import type { SecureHeadersVariables } from 'hono/secure-headers'
// Specify the variable types to infer the `c.get('secureHeadersNonce')`:
type Variables = SecureHeadersVariables
const app = new Hono<{ Variables: Variables }>()
// Set the pre-defined nonce value to `scriptSrc`:
app.get(
'*',
secureHeaders({
contentSecurityPolicy: {
scriptSrc: [NONCE, 'https://allowed1.example.com'],
},
})
)
// Get the value from `c.get('secureHeadersNonce')`:
app.get('/', (c) => {
return c.html(
{/** contents */}
)
})
```
如果你想要自己生成 nonce 值,也可以像下面这样指定一个函数:
¥If you want to generate the nonce value yourself, you can also specify a function as the following:
```tsx
const app = new Hono<{
Variables: { myNonce: string }
}>()
const myNonceGenerator: ContentSecurityPolicyOptionHandler = (c) => {
// This function is called on every request.
const nonce = Math.random().toString(36).slice(2)
c.set('myNonce', nonce)
return `'nonce-${nonce}'`
}
app.get(
'*',
secureHeaders({
contentSecurityPolicy: {
scriptSrc: [myNonceGenerator, 'https://allowed1.example.com'],
},
})
)
app.get('/', (c) => {
return c.html(
{/** contents */}
)
})
```
## 设置权限策略
¥Setting Permission-Policy
Permission-Policy 标头允许你控制可以在浏览器中使用哪些功能和 API。以下是设置方法的示例:
¥The Permission-Policy header allows you to control which features and APIs can be used in the browser. Here's an example of how to set it:
```ts
const app = new Hono()
app.use(
'*',
secureHeaders({
permissionsPolicy: {
fullscreen: ['self'], // fullscreen=(self)
bluetooth: ['none'], // bluetooth=(none)
payment: ['self', 'https://example.com'], // payment=(self "https://example.com")
syncXhr: [], // sync-xhr=()
camera: false, // camera=none
microphone: true, // microphone=*
geolocation: ['*'], // geolocation=*
usb: ['self', 'https://a.example.com', 'https://b.example.com'], // usb=(self "https://a.example.com" "https://b.example.com")
accelerometer: ['https://*.example.com'], // accelerometer=("https://*.example.com")
gyroscope: ['src'], // gyroscope=(src)
magnetometer: [
'https://a.example.com',
'https://b.example.com',
], // magnetometer=("https://a.example.com" "https://b.example.com")
},
})
)
```
# 超时中间件
¥Timeout Middleware
Timeout 中间件使你能够轻松管理应用中的请求超时。它允许你设置请求的最大持续时间,并在超过指定超时时可选地定义自定义错误响应。
¥The Timeout Middleware enables you to easily manage request timeouts in your application. It allows you to set a maximum duration for requests and optionally define custom error responses if the specified timeout is exceeded.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { timeout } from 'hono/timeout'
```
## 用法
¥Usage
以下是使用 Timeout Middleware 的默认和自定义设置的方法:
¥Here's how to use the Timeout Middleware with both default and custom settings:
默认设置:
¥Default Settings:
```ts
const app = new Hono()
// Applying a 5-second timeout
app.use('/api', timeout(5000))
// Handling a route
app.get('/api/data', async (c) => {
// Your route handler logic
return c.json({ data: 'Your data here' })
})
```
自定义设置:
¥Custom settings:
```ts
import { HTTPException } from 'hono/http-exception'
// Custom exception factory function
const customTimeoutException = (context) =>
new HTTPException(408, {
message: `Request timeout after waiting ${context.req.headers.get(
'Duration'
)} seconds. Please try again later.`,
})
// for Static Exception Message
// const customTimeoutException = new HTTPException(408, {
// message: 'Operation timed out. Please try again later.'
// });
// Applying a 1-minute timeout with a custom exception
app.use('/api/long-process', timeout(60000, customTimeoutException))
app.get('/api/long-process', async (c) => {
// Simulate a long process
await new Promise((resolve) => setTimeout(resolve, 61000))
return c.json({ data: 'This usually takes longer' })
})
```
## 注释
¥Notes
* 可以以毫秒为单位指定超时持续时间。如果超过指定的持续时间,中间件将自动拒绝 promise 并可能抛出错误。
¥The duration for the timeout can be specified in milliseconds. The middleware will automatically reject the promise and potentially throw an error if the specified duration is exceeded.
* 超时中间件不能与流一起使用,因此,请同时使用 `stream.close` 和 `setTimeout`。
¥The timeout middleware cannot be used with stream Thus, use `stream.close` and `setTimeout` together.
```ts
app.get('/sse', async (c) => {
let id = 0
let running = true
let timer: number | undefined
return streamSSE(c, async (stream) => {
timer = setTimeout(() => {
console.log('Stream timeout reached, closing stream')
stream.close()
}, 3000) as unknown as number
stream.onAbort(async () => {
console.log('Client closed connection')
running = false
clearTimeout(timer)
})
while (running) {
const message = `It is ${new Date().toISOString()}`
await stream.writeSSE({
data: message,
event: 'time-update',
id: String(id++),
})
await stream.sleep(1000)
}
})
})
```
## 中间件冲突
¥Middleware Conflicts
请注意中间件的顺序,特别是在使用错误处理或其他与时间相关的中间件时,因为它可能会影响此超时中间件的行为。
¥Be cautious about the order of middleware, especially when using error-handling or other timing-related middleware, as it might affect the behavior of this timeout middleware.
# 服务器计时中间件
¥Server-Timing Middleware
[Server-Timing](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Server-Timing) 中间件在响应标头中提供性能指标。
¥The [Server-Timing](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Server-Timing) Middleware provides
performance metrics in the response headers.
::: info 信息
注意:在 Cloudflare Workers 上,由于 [计时器仅显示上次 I/O 的时间](https://developers.cloudflare.com/workers/learning/security-model/#step-1-disallow-timers-and-multi-threading),计时器指标可能不准确。
¥Note: On Cloudflare Workers, the timer metrics may not be accurate,
since [timers only show the time of last I/O](https://developers.cloudflare.com/workers/learning/security-model/#step-1-disallow-timers-and-multi-threading).
:::
## 导入
¥Import
```ts [npm]
import { Hono } from 'hono'
import { timing, setMetric, startTime, endTime } from 'hono/timing'
import type { TimingVariables } from 'hono/timing'
```
## 用法
¥Usage
```js
// Specify the variable types to infer the `c.get('metric')`:
type Variables = TimingVariables
const app = new Hono<{ Variables: Variables }>()
// add the middleware to your router
app.use(timing());
app.get('/', async (c) => {
// add custom metrics
setMetric(c, 'region', 'europe-west3')
// add custom metrics with timing, must be in milliseconds
setMetric(c, 'custom', 23.8, 'My custom Metric')
// start a new timer
startTime(c, 'db');
const data = await db.findMany(...);
// end the timer
endTime(c, 'db');
return c.json({ response: data });
});
```
### 有条件启用
¥Conditionally enabled
```ts
const app = new Hono()
app.use(
'*',
timing({
// c: Context of the request
enabled: (c) => c.req.method === 'POST',
})
)
```
## 结果
¥Result

## 选项
¥Options
### <徽章类型="info" 文本="optional" /> 总计:`boolean`
¥ total: `boolean`
显示总响应时间。默认为 `true`。
¥Show the total response time. The default is `true`.
### 已启用:`boolean` | `(c: Context) => boolean`
¥ enabled: `boolean` | `(c: Context) => boolean`
是否应将时间添加到标头中。默认为 `true`。
¥Whether timings should be added to the headers or not. The default is `true`.
### <徽章类型="info" 文本="optional" /> 总描述:`boolean`
¥ totalDescription: `boolean`
总响应时间的描述。默认为 `Total Response Time`。
¥Description for the total response time. The default is `Total Response Time`.
### autoEnd:`boolean`
如果 `startTime()` 应该在请求结束时自动结束。如果禁用,未手动结束的计时器将不会显示。
¥If `startTime()` should end automatically at the end of the request.
If disabled, not manually ended timers will not be shown.
### crossOrigin:`boolean` | `string` | `(c: Context) => boolean | string`
此计时标头的来源应可读。
¥The origin this timings header should be readable.
* 如果为 false,则仅来自当前来源。
¥If false, only from current origin.
* 如果为真,则来自所有来源。
¥If true, from all origin.
* 如果是字符串,则来自此域。多个域必须用逗号分隔。
¥If string, from this domain(s). Multiple domains must be separated with a comma.
默认为 `false`。查看更多 [docs](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin)。
¥The default is `false`. See more [docs](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin).
# 尾部斜杠中间件
¥Trailing Slash Middleware
此中间件处理 GET 请求中 URL 中的尾部斜杠。
¥This middleware handles Trailing Slash in the URL on a GET request.
如果未找到内容,`appendTrailingSlash` 会将 URL 重定向到添加了尾部斜杠的位置。此外,`trimTrailingSlash` 将删除尾部斜杠。
¥`appendTrailingSlash` redirects the URL to which it added the Trailing Slash if the content was not found. Also, `trimTrailingSlash` will remove the Trailing Slash.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import {
appendTrailingSlash,
trimTrailingSlash,
} from 'hono/trailing-slash'
```
## 用法
¥Usage
将 `/about/me` 的 GET 请求重定向到 `/about/me/` 的示例。
¥Example of redirecting a GET request of `/about/me` to `/about/me/`.
```ts
import { Hono } from 'hono'
import { appendTrailingSlash } from 'hono/trailing-slash'
const app = new Hono({ strict: true })
app.use(appendTrailingSlash())
app.get('/about/me/', (c) => c.text('With Trailing Slash'))
```
将 `/about/me/` 的 GET 请求重定向到 `/about/me` 的示例。
¥Example of redirecting a GET request of `/about/me/` to `/about/me`.
```ts
import { Hono } from 'hono'
import { trimTrailingSlash } from 'hono/trailing-slash'
const app = new Hono({ strict: true })
app.use(trimTrailingSlash())
app.get('/about/me', (c) => c.text('Without Trailing Slash'))
```
## 注意
¥Note
当请求方法是 `GET` 且响应状态是 `404` 时,它将被启用。
¥It will be enabled when the request method is `GET` and the response status is `404`.
# 接受助手
¥Accepts Helper
Accepts Helper 有助于处理请求中的 Accept 标头。
¥Accepts Helper helps to handle Accept headers in the Requests.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { accepts } from 'hono/accepts'
```
## `accepts()`
`accepts()` 函数查看 Accept 标头,例如 Accept-Encoding 和 Accept-Language,并返回正确的值。
¥The `accepts()` function looks at the Accept header, such as Accept-Encoding and Accept-Language, and returns the proper value.
```ts
import { accepts } from 'hono/accepts'
app.get('/', (c) => {
const accept = accepts(c, {
header: 'Accept-Language',
supports: ['en', 'ja', 'zh'],
default: 'en',
})
return c.json({ lang: accept })
})
```
### `AcceptHeader` 类型
¥`AcceptHeader` type
`AcceptHeader` 类型的定义如下。
¥The definition of the `AcceptHeader` type is as follows.
```ts
export type AcceptHeader =
| 'Accept'
| 'Accept-Charset'
| 'Accept-Encoding'
| 'Accept-Language'
| 'Accept-Patch'
| 'Accept-Post'
| 'Accept-Ranges'
```
## 选项
¥Options
### 标头:`AcceptHeader`
¥ header: `AcceptHeader`
目标接受标头。
¥The target accept header.
### <徽章类型="danger" 文本="required" /> 支持:`string[]`
¥ supports: `string[]`
你的应用支持的标头值。
¥The header values which your application supports.
### 默认:`string`
¥ default: `string`
默认值。
¥The default values.
### match:`(accepts: Accept[], config: acceptsConfig) => string`
自定义匹配函数。
¥The custom match function.
# 适配器助手
¥Adapter Helper
Adapter Helper 通过统一界面提供了一种与各种平台无缝交互的方式。
¥The Adapter Helper provides a seamless way to interact with various platforms through a unified interface.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { env, getRuntimeKey } from 'hono/adapter'
```
## `env()`
`env()` 函数有助于在不同的运行时检索环境变量,而不仅仅是 Cloudflare Workers 的绑定。使用 `env(c)` 可以检索的值可能因每个运行时而异。
¥The `env()` function facilitates retrieving environment variables across different runtimes, extending beyond just Cloudflare Workers' Bindings. The value that can be retrieved with `env(c)` may be different for each runtimes.
```ts
import { env } from 'hono/adapter'
app.get('/env', (c) => {
// NAME is process.env.NAME on Node.js or Bun
// NAME is the value written in `wrangler.toml` on Cloudflare
const { NAME } = env<{ NAME: string }>(c)
return c.text(NAME)
})
```
支持的运行时、无服务器平台和云服务:
¥Supported Runtimes, Serverless Platforms and Cloud Services:
* Cloudflare Workers
* `wrangler.toml`
* `wrangler.jsonc`
* Deno
* [`Deno.env`](https://docs.deno.com/runtime/manual/basics/env_variables)
* `.env` 文件
¥`.env` file
* Bun
* [`Bun.env`](https://bun.sh/guides/runtime/set-env)
* `process.env`
* Node.js
* `process.env`
* Vercel
* [Vercel 上的环境变量](https://vercel.com/docs/projects/environment-variables)
¥[Environment Variables on Vercel](https://vercel.com/docs/projects/environment-variables)
* AWS Lambda
* [AWS 上的环境变量 Lambda](https://docs.aws.amazon.com/lambda/latest/dg/samples-blank.html#samples-blank-architecture)
¥[Environment Variables on AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/samples-blank.html#samples-blank-architecture)
* Lambda 上的 Lambda@Edge 环境变量是 Lambda@Edge 的 [不支持](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/add-origin-custom-headers.html),你需要使用 [Lamdba@Edge 事件](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html) 作为替代方案。
¥Lambda@Edge\ Environment Variables on Lambda are [not supported](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/add-origin-custom-headers.html) by Lambda@Edge, you need to use [Lamdba@Edge event](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html) as an alternative.
* 快速计算在 Fastly Compute 上,你可以使用 ConfigStore 来管理用户定义的数据。
¥Fastly Compute\ On Fastly Compute, you can use the ConfigStore to manage user-defined data.
* Netlify 在 Netlify 上,你可以使用 [Netlify 上下文](https://docs.netlify.com/site-deploys/overview/#deploy-contexts) 来管理用户定义的数据。
¥Netlify\ On Netlify, you can use the [Netlify Contexts](https://docs.netlify.com/site-deploys/overview/#deploy-contexts) to manage user-defined data.
### 指定运行时
¥Specify the runtime
你可以通过将运行时键作为第二个参数传递来指定运行时以获取环境变量。
¥You can specify the runtime to get environment variables by passing the runtime key as the second argument.
```ts
app.get('/env', (c) => {
const { NAME } = env<{ NAME: string }>(c, 'workerd')
return c.text(NAME)
})
```
## `getRuntimeKey()`
`getRuntimeKey()` 函数返回当前运行时的标识符。
¥The `getRuntimeKey()` function returns the identifier of the current runtime.
```ts
app.get('/', (c) => {
if (getRuntimeKey() === 'workerd') {
return c.text('You are on Cloudflare')
} else if (getRuntimeKey() === 'bun') {
return c.text('You are on Bun')
}
...
})
```
### 可用运行时密钥
¥Available Runtimes Keys
以下是可用的运行时键,不可用的运行时键运行时可能受支持并标记为 `other`,其中一些受 [WinterCG 的运行时密钥](https://runtime-keys.proposal.wintercg.org/) 启发:
¥Here are the available runtimes keys, unavailable runtime key runtimes may be supported and labeled as `other`, with some being inspired by [WinterCG's Runtime Keys](https://runtime-keys.proposal.wintercg.org/):
* `workerd` - Cloudflare Workers
* `deno`
* `bun`
* `node`
* `edge-light` - Vercel Edge 函数
¥`edge-light` - Vercel Edge Functions
* `fastly` - Fastly 计算
¥`fastly` - Fastly Compute
* `other` - 其他未知运行时键
¥`other` - Other unknown runtimes keys
# ConnInfo 助手
¥ConnInfo Helper
ConnInfo Helper 可帮助你获取连接信息。例如,你可以轻松获取客户端的远程地址。
¥The ConnInfo Helper helps you to get the connection information. For example, you can get the client's remote address easily.
## 导入
¥Import
::: code-group
```ts [Cloudflare Workers]
import { Hono } from 'hono'
import { getConnInfo } from 'hono/cloudflare-workers'
```
```ts [Deno]
import { Hono } from 'hono'
import { getConnInfo } from 'hono/deno'
```
```ts [Bun]
import { Hono } from 'hono'
import { getConnInfo } from 'hono/bun'
```
```ts [Vercel]
import { Hono } from 'hono'
import { getConnInfo } from 'hono/vercel'
```
```ts [Lambda@Edge]
import { Hono } from 'hono'
import { getConnInfo } from 'hono/lambda-edge'
```
```ts [Node.js]
import { Hono } from 'hono'
import { getConnInfo } from '@hono/node-server/conninfo'
```
:::
## 用法
¥Usage
```ts
const app = new Hono()
app.get('/', (c) => {
const info = getConnInfo(c) // info is `ConnInfo`
return c.text(`Your remote address is ${info.remote.address}`)
})
```
## 类型定义
¥Type Definitions
你可以从 `getConnInfo()` 获取的值的类型定义如下:
¥The type definitions of the values that you can get from `getConnInfo()` are the following:
```ts
type AddressType = 'IPv6' | 'IPv4' | undefined
type NetAddrInfo = {
/**
* Transport protocol type
*/
transport?: 'tcp' | 'udp'
/**
* Transport port number
*/
port?: number
address?: string
addressType?: AddressType
} & (
| {
/**
* Host name such as IP Addr
*/
address: string
/**
* Host name type
*/
addressType: AddressType
}
| {}
)
/**
* HTTP Connection information
*/
interface ConnInfo {
/**
* Remote information
*/
remote: NetAddrInfo
}
```
# Cookie 助手
¥Cookie Helper
Cookie Helper 提供了一个简单的界面来管理 cookie,使开发者能够无缝地设置、解析和删除 cookie。
¥The Cookie Helper provides an easy interface to manage cookies, enabling developers to set, parse, and delete cookies seamlessly.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import {
getCookie,
getSignedCookie,
setCookie,
setSignedCookie,
deleteCookie,
} from 'hono/cookie'
```
## 用法
¥Usage
注意:由于 WebCrypto API 的异步特性,设置和检索签名的 cookie 会返回 Promise,该 API 用于创建 HMAC SHA-256 签名。
¥**NOTE**: Setting and retrieving signed cookies returns a Promise due to the async nature of the WebCrypto API, which is used to create HMAC SHA-256 signatures.
```ts
const app = new Hono()
app.get('/cookie', (c) => {
const allCookies = getCookie(c)
const yummyCookie = getCookie(c, 'yummy_cookie')
// ...
setCookie(c, 'delicious_cookie', 'macha')
deleteCookie(c, 'delicious_cookie')
//
})
app.get('/signed-cookie', async (c) => {
const secret = 'secret ingredient'
// `getSignedCookie` will return `false` for a specified cookie if the signature was tampered with or is invalid
const allSignedCookies = await getSignedCookie(c, secret)
const fortuneCookie = await getSignedCookie(
c,
secret,
'fortune_cookie'
)
// ...
const anotherSecret = 'secret chocolate chips'
await setSignedCookie(c, 'great_cookie', 'blueberry', anotherSecret)
deleteCookie(c, 'great_cookie')
//
})
```
## 选项
¥Options
### `setCookie` & `setSignedCookie`
* domain:`string`
* expires:`Date`
* httpOnly:`boolean`
* maxAge:`number`
* 路径:`string`
¥path: `string`
* 安全:`boolean`
¥secure: `boolean`
* sameSite:`'Strict'` | `'Lax'` | `'None'`
* 优先级:`'Low' | 'Medium' | 'High'`
¥priority: `'Low' | 'Medium' | 'High'`
* 前缀:`secure` | `'host'`
¥prefix: `secure` | `'host'`
* 分区:`boolean`
¥partitioned: `boolean`
示例:
¥Example:
```ts
// Regular cookies
setCookie(c, 'great_cookie', 'banana', {
path: '/',
secure: true,
domain: 'example.com',
httpOnly: true,
maxAge: 1000,
expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)),
sameSite: 'Strict',
})
// Signed cookies
await setSignedCookie(
c,
'fortune_cookie',
'lots-of-money',
'secret ingredient',
{
path: '/',
secure: true,
domain: 'example.com',
httpOnly: true,
maxAge: 1000,
expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)),
sameSite: 'Strict',
}
)
```
### `deleteCookie`
* 路径:`string`
¥path: `string`
* 安全:`boolean`
¥secure: `boolean`
* domain:`string`
示例:
¥Example:
```ts
deleteCookie(c, 'banana', {
path: '/',
secure: true,
domain: 'example.com',
})
```
`deleteCookie` 返回已删除的值:
¥`deleteCookie` returns the deleted value:
```ts
const deletedCookie = deleteCookie(c, 'delicious_cookie')
```
## `__Secure-` 和 `__Host-` 前缀
¥`__Secure-` and `__Host-` prefix
Cookie 助手支持 `__Secure-` 和 `__Host-` 作为 cookie 名称的前缀。
¥The Cookie helper supports `__Secure-` and `__Host-` prefix for cookies names.
如果要验证 cookie 名称是否有前缀,请指定前缀选项。
¥If you want to verify if the cookie name has a prefix, specify the prefix option.
```ts
const securePrefixCookie = getCookie(c, 'yummy_cookie', 'secure')
const hostPrefixCookie = getCookie(c, 'yummy_cookie', 'host')
const securePrefixSignedCookie = await getSignedCookie(
c,
secret,
'fortune_cookie',
'secure'
)
const hostPrefixSignedCookie = await getSignedCookie(
c,
secret,
'fortune_cookie',
'host'
)
```
此外,如果你希望在设置 cookie 时指定前缀,请为前缀选项指定一个值。
¥Also, if you wish to specify a prefix when setting the cookie, specify a value for the prefix option.
```ts
setCookie(c, 'delicious_cookie', 'macha', {
prefix: 'secure', // or `host`
})
await setSignedCookie(
c,
'delicious_cookie',
'macha',
'secret choco chips',
{
prefix: 'secure', // or `host`
}
)
```
## 遵循最佳实践
¥Following the best practices
新的 Cookie RFC(又名 cookie-bis)和 CHIPS 包含一些开发者应遵循的 Cookie 设置最佳实践。
¥A New Cookie RFC (a.k.a cookie-bis) and CHIPS include some best practices for Cookie settings that developers should follow.
* [RFC6265bis-13](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-13)
* `Max-Age`/`Expires` 限制
¥`Max-Age`/`Expires` limitation
* `__Host-`/`__Secure-` 前缀限制
¥`__Host-`/`__Secure-` prefix limitation
* [CHIPS-01](https://www.ietf.org/archive/id/draft-cutler-httpbis-partitioned-cookies-01.html)
* `Partitioned` 限制
¥`Partitioned` limitation
Hono 遵循最佳实践。在以下条件下解析 cookie 时,cookie 助手将抛出 `Error`:
¥Hono is following the best practices.
The cookie helper will throw an `Error` when parsing cookies under the following conditions:
* cookie 名称以 `__Secure-` 开头,但未设置 `secure` 选项。
¥The cookie name starts with `__Secure-`, but `secure` option is not set.
* cookie 名称以 `__Host-` 开头,但未设置 `secure` 选项。
¥The cookie name starts with `__Host-`, but `secure` option is not set.
* cookie 名称以 `__Host-` 开头,但 `path` 不是 `/`。
¥The cookie name starts with `__Host-`, but `path` is not `/`.
* cookie 名称以 `__Host-` 开头,但设置了 `domain`。
¥The cookie name starts with `__Host-`, but `domain` is set.
* `maxAge` 选项值大于 400 天。
¥The `maxAge` option value is greater than 400 days.
* `expires` 选项值比当前时间晚 400 天。
¥The `expires` option value is 400 days later than the current time.
# css 助手
¥css Helper
css 助手 - `hono/css` - 是 Hono 在 JS(X) 中内置的 CSS。
¥The css helper - `hono/css` - is Hono's built-in CSS in JS(X).
你可以在名为 `css` 的 JavaScript 模板字面量中以 JSX 形式编写 CSS。`css` 的返回值将是类名,它设置为类属性的值。然后 `` 组件将包含 CSS 的值。
¥You can write CSS in JSX in a JavaScript template literal named `css`. The return value of `css` will be the class name, which is set to the value of the class attribute. The `` component will then contain the value of the CSS.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { css, cx, keyframes, Style } from 'hono/css'
```
## `css`
¥`css`
你可以在 `css` 模板字面量中编写 CSS。在这种情况下,它使用 `headerClass` 作为 `class` 属性的值。不要忘记添加 ``,因为它包含 CSS 内容。
¥You can write CSS in the `css` template literal. In this case, it uses `headerClass` as a value of the `class` attribute. Don't forget to add `` as it contains the CSS content.
```ts{10,13}
app.get('/', (c) => {
const headerClass = css`
background-color: orange;
color: white;
padding: 1rem;
`
return c.html(
Hello!
)
})
```
你可以使用 [嵌套选择器](https://web.nodejs.cn/en-US/docs/Web/CSS/Nesting_selector)、`&` 来设置伪类(如 `:hover`)的样式:
¥You can style pseudo-classes like `:hover` by using the [nesting selector](https://web.nodejs.cn/en-US/docs/Web/CSS/Nesting_selector), `&`:
```ts
const buttonClass = css`
background-color: #fff;
&:hover {
background-color: red;
}
`
```
### 扩展
¥Extending
你可以通过嵌入类名来扩展 CSS 定义。
¥You can extend the CSS definition by embedding the class name.
```tsx
const baseClass = css`
color: white;
background-color: blue;
`
const header1Class = css`
${baseClass}
font-size: 3rem;
`
const header2Class = css`
${baseClass}
font-size: 2rem;
`
```
此外,`${baseClass} {}` 的语法支持嵌套类。
¥In addition, the syntax of `${baseClass} {}` enables nesting classes.
```tsx
const headerClass = css`
color: white;
background-color: blue;
`
const containerClass = css`
${headerClass} {
h1 {
font-size: 3rem;
}
}
`
return c.render(
Hello!
)
```
### 全局样式
¥Global styles
名为 `:-hono-global` 的伪选择器允许你定义全局样式。
¥A pseudo-selector called `:-hono-global` allows you to define global styles.
```tsx
const globalClass = css`
:-hono-global {
html {
font-family: Arial, Helvetica, sans-serif;
}
}
`
return c.render(
Hello!
Today is a good day.
)
```
或者你可以在 `` 组件中使用 `css` 字面量编写 CSS。
¥Or you can write CSS in the `` component with the `css` literal.
```tsx
export const renderer = jsxRenderer(({ children, title }) => {
return (
{title}
{children}
)
})
```
## `keyframes`
¥`keyframes`
你可以使用 `keyframes` 写入 `@keyframes` 的内容。在这种情况下,`fadeInAnimation` 将是动画的名称
¥You can use `keyframes` to write the contents of `@keyframes`. In this case, `fadeInAnimation` will be the name of the animation
```tsx
const fadeInAnimation = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`
const headerClass = css`
animation-name: ${fadeInAnimation};
animation-duration: 2s;
`
const Header = () => Hello!
```
## `cx`
¥`cx`
`cx` 组合了两个类名。
¥The `cx` composites the two class names.
```tsx
const buttonClass = css`
border-radius: 10px;
`
const primaryClass = css`
background: orange;
`
const Button = () => (
Click!
)
```
它还可以编写简单的字符串。
¥It can also compose simple strings.
```tsx
const Header = () => Hi
```
## 与 [安全标头](/docs/middleware/builtin/secure-headers) 中间件结合使用
¥Usage in combination with [Secure Headers](/docs/middleware/builtin/secure-headers) middleware
如果要将 css 助手与 [安全标头](/docs/middleware/builtin/secure-headers) 中间件结合使用,可以将 [`nonce` 属性](https://web.nodejs.cn/en-US/docs/Web/HTML/Global_attributes/nonce) 添加到 `` 中,以避免 css 助手导致的 Content-Security-Policy。
¥If you want to use the css helpers in combination with the [Secure Headers](/docs/middleware/builtin/secure-headers) middleware, you can add the [`nonce` attribute](https://web.nodejs.cn/en-US/docs/Web/HTML/Global_attributes/nonce) to the `` to avoid Content-Security-Policy caused by the css helpers.
```tsx{8,23}
import { secureHeaders, NONCE } from 'hono/secure-headers'
app.get(
'*',
secureHeaders({
contentSecurityPolicy: {
// Set the pre-defined nonce value to `styleSrc`:
styleSrc: [NONCE],
},
})
)
app.get('/', (c) => {
const headerClass = css`
background-color: orange;
color: white;
padding: 1rem;
`
return c.html(
{/* Set the `nonce` attribute on the css helpers `style` and `script` elements */}
Hello!
)
})
```
## 提示
¥Tips
如果你使用 VS Code,则可以使用 [vscode-styled-components](https://marketplace.visualstudio.com/items?itemName=styled-components.vscode-styled-components) 进行语法高亮,并使用 IntelliSense 进行 css 标记字面量。
¥If you use VS Code, you can use [vscode-styled-components](https://marketplace.visualstudio.com/items?itemName=styled-components.vscode-styled-components) for Syntax highlighting and IntelliSense for css tagged literals.

# 开发助手
¥Dev Helper
Dev Helper 提供了你可以在开发中使用的有用方法。
¥Dev Helper provides useful methods you can use in development.
```ts
import { Hono } from 'hono'
import { getRouterName, showRoutes } from 'hono/dev'
```
## `getRouterName()`
你可以使用 `getRouterName()` 获取当前使用的路由的名称。
¥You can get the name of the currently used router with `getRouterName()`.
```ts
const app = new Hono()
// ...
console.log(getRouterName(app))
```
## `showRoutes()`
`showRoutes()` 函数在你的控制台中显示已注册的路由。
¥`showRoutes()` function displays the registered routes in your console.
考虑如下应用:
¥Consider an application like the following:
```ts
const app = new Hono().basePath('/v1')
app.get('/posts', (c) => {
// ...
})
app.get('/posts/:id', (c) => {
// ...
})
app.post('/posts', (c) => {
// ...
})
showRoutes(app, {
verbose: true,
})
```
当此应用开始运行时,路由将显示在你的控制台中,如下所示:
¥When this application starts running, the routes will be shown in your console as follows:
```txt
GET /v1/posts
GET /v1/posts/:id
POST /v1/posts
```
## 选项
¥Options
### verbose:`boolean`
设置为 `true` 时,显示详细信息。
¥When set to `true`, it displays verbose information.
### colorize:`boolean`
设置为 `false` 时,输出不会带颜色。
¥When set to `false`, the output will not be colored.
# 工厂助手
¥Factory Helper
Factory Helper 提供了创建 Hono 组件(如中间件)的有用功能。有时很难设置正确的 TypeScript 类型,但此辅助程序可以简化此操作。
¥The Factory Helper provides useful functions for creating Hono's components such as Middleware. Sometimes it's difficult to set the proper TypeScript types, but this helper facilitates that.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { createFactory, createMiddleware } from 'hono/factory'
```
## `createFactory()`
`createFactory()` 将创建 Factory 类的实例。
¥`createFactory()` will create an instance of the Factory class.
```ts
import { createFactory } from 'hono/factory'
const factory = createFactory()
```
你可以将 Env 类型作为泛型传递:
¥You can pass your Env types as Generics:
```ts
type Env = {
Variables: {
foo: string
}
}
const factory = createFactory()
```
### 选项
¥Options
### defaultAppOptions:`HonoOptions`
传递给 `createApp()` 创建的 Hono 应用的默认选项。
¥The default options to pass to the Hono application created by `createApp()`.
```ts
const factory = createFactory({
defaultAppOptions: { strict: false },
})
const app = factory.createApp() // `strict: false` is applied
```
## `createMiddleware()`
`createMiddleware()` 是 `factory.createMiddleware()` 的快捷方式。此函数将创建你的自定义中间件。
¥`createMiddleware()` is shortcut of `factory.createMiddleware()`.
This function will create your custom middleware.
```ts
const messageMiddleware = createMiddleware(async (c, next) => {
await next()
c.res.headers.set('X-Message', 'Good morning!')
})
```
提示:如果你想要获取像 `message` 这样的参数,你可以像下面这样将其创建为一个函数。
¥Tip: If you want to get an argument like `message`, you can create it as a function like the following.
```ts
const messageMiddleware = (message: string) => {
return createMiddleware(async (c, next) => {
await next()
c.res.headers.set('X-Message', message)
})
}
app.use(messageMiddleware('Good evening!'))
```
## `factory.createHandlers()`
`createHandlers()` 有助于在与 `app.get('/')` 不同的地方定义处理程序。
¥`createHandlers()` helps to define handlers in a different place than `app.get('/')`.
```ts
import { createFactory } from 'hono/factory'
import { logger } from 'hono/logger'
// ...
const factory = createFactory()
const middleware = factory.createMiddleware(async (c, next) => {
c.set('foo', 'bar')
await next()
})
const handlers = factory.createHandlers(logger(), middleware, (c) => {
return c.json(c.var.foo)
})
app.get('/api', ...handlers)
```
## `factory.createApp()`
`createApp()` 帮助创建具有适当类型的 Hono 实例。如果你将此方法与 `createFactory()` 一起使用,则可以避免 `Env` 类型的定义中的冗余。
¥`createApp()` helps to create an instance of Hono with the proper types. If you use this method with `createFactory()`, you can avoid redundancy in the definition of the `Env` type.
如果你的应用是这样的,你必须在两个地方设置 `Env`:
¥If your application is like this, you have to set the `Env` in two places:
```ts
import { createMiddleware } from 'hono/factory'
type Env = {
Variables: {
myVar: string
}
}
// 1. Set the `Env` to `new Hono()`
const app = new Hono()
// 2. Set the `Env` to `createMiddleware()`
const mw = createMiddleware(async (c, next) => {
await next()
})
app.use(mw)
```
通过使用 `createFactory()` 和 `createApp()`,你只能在一个地方设置 `Env`。
¥By using `createFactory()` and `createApp()`, you can set the `Env` only in one place.
```ts
import { createFactory } from 'hono/factory'
// ...
// Set the `Env` to `createFactory()`
const factory = createFactory()
const app = factory.createApp()
// factory also has `createMiddleware()`
const mw = factory.createMiddleware(async (c, next) => {
await next()
})
```
`createFactory()` 可以接收 `initApp` 选项来初始化由 `createApp()` 创建的 `app`。以下是使用该选项的示例。
¥`createFactory()` can receive the `initApp` option to initialize an `app` created by `createApp()`. The following is an example that uses the option.
```ts
// factory-with-db.ts
type Env = {
Bindings: {
MY_DB: D1Database
}
Variables: {
db: DrizzleD1Database
}
}
export default createFactory({
initApp: (app) => {
app.use(async (c, next) => {
const db = drizzle(c.env.MY_DB)
c.set('db', db)
await next()
})
},
})
```
```ts
// crud.ts
import factoryWithDB from './factory-with-db'
const app = factoryWithDB.createApp()
app.post('/posts', (c) => {
c.var.db.insert()
// ...
})
```
# html 助手
¥html Helper
html Helper 允许你使用名为 `html` 的标记在 JavaScript 模板字面量中编写 HTML。使用 `raw()`,内容将按原样渲染。你必须自己转义这些字符串。
¥The html Helper lets you write HTML in JavaScript template literal with a tag named `html`. Using `raw()`, the content will be rendered as is. You have to escape these strings by yourself.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { html, raw } from 'hono/html'
```
## `html`
```ts
const app = new Hono()
app.get('/:username', (c) => {
const { username } = c.req.param()
return c.html(
html`
Hello! ${username}!
`
)
})
```
### 将代码片段插入 JSX
¥Insert snippets into JSX
将内联脚本插入 JSX:
¥Insert the inline script into JSX:
```tsx
app.get('/', (c) => {
return c.html(
Test Site
{html`
`}
Hello!
)
})
```
### 充当功能组件
¥Act as functional component
由于 `html` 返回 HtmlEscapedString,因此它可以充当功能齐全的组件而无需使用 JSX。
¥Since `html` returns an HtmlEscapedString, it can act as a fully functional component without using JSX.
#### 使用 `html` 代替 `memo` 来加速进程
¥Use `html` to speed up the process instead of `memo`
```typescript
const Footer = () => html`
`
```
### 接收 props 并嵌入值
¥Receives props and embeds values
```typescript
interface SiteData {
title: string
description: string
image: string
children?: any
}
const Layout = (props: SiteData) => html`
${props.title}
${props.children}
`
const Content = (props: { siteData: SiteData; name: string }) => (
`)
})
```
## 提示
¥Tips
得益于这些库,Visual Studio Code 和 vim 还将模板字面量解释为 HTML,从而允许应用语法高亮和格式化。
¥Thanks to these libraries, Visual Studio Code and vim also interprets template literals as HTML, allowing syntax highlighting and formatting to be applied.
* [https://marketplace.visualstudio.com/items?itemName=bierner.lit-html](https://marketplace.visualstudio.com/items?itemName=bierner.lit-html)
* [https://github.com/MaxMEllon/vim-jsx-pretty](https://github.com/MaxMEllon/vim-jsx-pretty)
# JWT 身份验证助手
¥JWT Authentication Helper
此辅助程序提供用于编码、解码、签名和验证 JSON Web Tokens (JWT) 的函数。JWT 通常用于 Web 应用中的身份验证和授权目的。此辅助程序提供强大的 JWT 功能,支持各种加密算法。
¥This helper provides functions for encoding, decoding, signing, and verifying JSON Web Tokens (JWTs). JWTs are commonly used for authentication and authorization purposes in web applications. This helper offers robust JWT functionality with support for various cryptographic algorithms.
## 导入
¥Import
要使用此辅助程序,你可以按如下方式导入它:
¥To use this helper, you can import it as follows:
```ts
import { decode, sign, verify } from 'hono/jwt'
```
::: info 信息
[JWT 中间件](/docs/middleware/builtin/jwt) 还从 `hono/jwt` 导入 `jwt` 函数。
¥[JWT Middleware](/docs/middleware/builtin/jwt) also import the `jwt` function from the `hono/jwt`.
:::
## `sign()`
此函数通过编码有效负载并使用指定的算法和密钥对其进行签名来生成 JWT 令牌。
¥This function generates a JWT token by encoding a payload and signing it using the specified algorithm and secret.
```ts
sign(
payload: unknown,
secret: string,
alg?: 'HS256';
): Promise;
```
### 示例
¥Example
```ts
import { sign } from 'hono/jwt'
const payload = {
sub: 'user123',
role: 'admin',
exp: Math.floor(Date.now() / 1000) + 60 * 5, // Token expires in 5 minutes
}
const secret = 'mySecretKey'
const token = await sign(payload, secret)
```
### 选项
¥Options
#### payload:`unknown`
要签名的 JWT 有效负载。你可以像在 [有效负载验证](#payload-validation) 中一样包含其他声明。
¥The JWT payload to be signed. You can include other claims like in [Payload Validation](#payload-validation).
#### <徽章类型="danger" 文本="required" /> 秘密:`string`
¥ secret: `string`
用于 JWT 验证或签名的密钥。
¥The secret key used for JWT verification or signing.
#### alg:[AlgorithmTypes](#supported-algorithmtypes)
用于 JWT 签名或验证的算法。默认为 HS256。
¥The algorithm used for JWT signing or verification. The default is HS256.
## `verify()`
此函数检查 JWT 令牌是否真实且仍然有效。它确保令牌未被更改,并且仅在你添加 [有效负载验证](#payload-validation) 时才检查有效性。
¥This function checks if a JWT token is genuine and still valid. It ensures the token hasn't been altered and checks validity only if you added [Payload Validation](#payload-validation).
```ts
verify(
token: string,
secret: string,
alg?: 'HS256';
): Promise;
```
### 示例
¥Example
```ts
import { verify } from 'hono/jwt'
const tokenToVerify = 'token'
const secretKey = 'mySecretKey'
const decodedPayload = await verify(tokenToVerify, secretKey)
console.log(decodedPayload)
```
### 选项
¥Options
#### <徽章类型="danger" 文本="required" /> 令牌:`string`
¥ token: `string`
要验证的 JWT 令牌。
¥The JWT token to be verified.
#### <徽章类型="danger" 文本="required" /> 秘密:`string`
¥ secret: `string`
用于 JWT 验证或签名的密钥。
¥The secret key used for JWT verification or signing.
#### alg:[AlgorithmTypes](#supported-algorithmtypes)
用于 JWT 签名或验证的算法。默认为 HS256。
¥The algorithm used for JWT signing or verification. The default is HS256.
## `decode()`
此函数解码 JWT 令牌而不执行签名验证。它从令牌中提取并返回标头和有效负载。
¥This function decodes a JWT token without performing signature verification. It extracts and returns the header and payload from the token.
```ts
decode(token: string): { header: any; payload: any };
```
### 示例
¥Example
```ts
import { decode } from 'hono/jwt'
// Decode the JWT token
const tokenToDecode =
'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOiAidXNlcjEyMyIsICJyb2xlIjogImFkbWluIn0.JxUwx6Ua1B0D1B0FtCrj72ok5cm1Pkmr_hL82sd7ELA'
const { header, payload } = decode(tokenToDecode)
console.log('Decoded Header:', header)
console.log('Decoded Payload:', payload)
```
### 选项
¥Options
#### <徽章类型="danger" 文本="required" /> 令牌:`string`
¥ token: `string`
要解码的 JWT 令牌。
¥The JWT token to be decoded.
> `decode` 函数允许你检查 JWT 令牌的标头和有效负载而无需执行验证。这对于调试或从 JWT 令牌中提取信息很有用。
>
> ¥The `decode` function allows you to inspect the header and payload of a JWT token ***without*** performing verification. This can be useful for debugging or extracting information from JWT tokens.
## 有效负载验证
¥Payload Validation
验证 JWT 令牌时,将执行以下有效负载验证:
¥When verifying a JWT token, the following payload validations are performed:
* `exp`:检查令牌以确保其未过期。
¥`exp`: The token is checked to ensure it has not expired.
* `nbf`:检查令牌以确保其未在指定时间之前被使用。
¥`nbf`: The token is checked to ensure it is not being used before a specified time.
* `iat`:检查令牌以确保其不会在将来发出。
¥`iat`: The token is checked to ensure it is not issued in the future.
如果你打算在验证期间执行这些检查,请确保你的 JWT 有效负载包含这些字段作为对象。
¥Please ensure that your JWT payload includes these fields, as an object, if you intend to perform these checks during verification.
## 自定义错误类型
¥Custom Error Types
该模块还定义了自定义错误类型来处理与 JWT 相关的错误。
¥The module also defines custom error types to handle JWT-related errors.
* `JwtAlgorithmNotImplemented`:表示请求的 JWT 算法未实现。
¥`JwtAlgorithmNotImplemented`: Indicates that the requested JWT algorithm is not implemented.
* `JwtTokenInvalid`:表示 JWT 令牌无效。
¥`JwtTokenInvalid`: Indicates that the JWT token is invalid.
* `JwtTokenNotBefore`:表示令牌在有效期之前使用。
¥`JwtTokenNotBefore`: Indicates that the token is being used before its valid date.
* `JwtTokenExpired`:表示令牌已过期。
¥`JwtTokenExpired`: Indicates that the token has expired.
* `JwtTokenIssuedAt`:表示令牌中的 "iat" 声明不正确。
¥`JwtTokenIssuedAt`: Indicates that the "iat" claim in the token is incorrect.
* `JwtTokenSignatureMismatched`:表示令牌中的签名不匹配。
¥`JwtTokenSignatureMismatched`: Indicates a signature mismatch in the token.
## 支持的算法类型
¥Supported AlgorithmTypes
该模块支持以下 JWT 加密算法:
¥The module supports the following JWT cryptographic algorithms:
* `HS256`:使用 SHA-256 的 HMAC
¥`HS256`: HMAC using SHA-256
* `HS384`:使用 SHA-384 的 HMAC
¥`HS384`: HMAC using SHA-384
* `HS512`:使用 SHA-512 的 HMAC
¥`HS512`: HMAC using SHA-512
* `RS256`:使用 SHA-256 的 RSASSA-PKCS1-v1_5
¥`RS256`: RSASSA-PKCS1-v1_5 using SHA-256
* `RS384`:使用 SHA-384 的 RSASSA-PKCS1-v1_5
¥`RS384`: RSASSA-PKCS1-v1_5 using SHA-384
* `RS512`:使用 SHA-512 的 RSASSA-PKCS1-v1_5
¥`RS512`: RSASSA-PKCS1-v1_5 using SHA-512
* `PS256`:RSASSA-PSS 使用 SHA-256 和 MGF1 使用 SHA-256
¥`PS256`: RSASSA-PSS using SHA-256 and MGF1 with SHA-256
* `PS384`:RSASSA-PSS 使用 SHA-386 和 MGF1 使用 SHA-386
¥`PS384`: RSASSA-PSS using SHA-386 and MGF1 with SHA-386
* `PS512`:RSASSA-PSS 使用 SHA-512 和 MGF1 使用 SHA-512
¥`PS512`: RSASSA-PSS using SHA-512 and MGF1 with SHA-512
* `ES256`:使用 P-256 和 SHA-256 的 ECDSA
¥`ES256`: ECDSA using P-256 and SHA-256
* `ES384`:使用 P-384 和 SHA-384 的 ECDSA
¥`ES384`: ECDSA using P-384 and SHA-384
* `ES512`:使用 P-521 和 SHA-512 的 ECDSA
¥`ES512`: ECDSA using P-521 and SHA-512
* `EdDSA`:使用 Ed25519 的 EdDSA
¥`EdDSA`: EdDSA using Ed25519
# 代理助手
¥Proxy Helper
当使用 Hono 应用作为(反向)代理时,Proxy Helper 提供了有用的功能。
¥Proxy Helper provides useful functions when using Hono application as a (reverse) proxy.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { proxy } from 'hono/proxy'
```
## `proxy()`
`proxy()` 是用于代理的 `fetch()` API 封装器。参数和返回值与 `fetch()` 相同(代理特定选项除外)。
¥`proxy()` is a `fetch()` API wrapper for proxy. The parameters and return value are the same as for `fetch()` (except for the proxy-specific options).
`Accept-Encoding` 标头被替换为当前运行时可以处理的编码。删除不必要的响应标头,并返回一个 `Response` 对象,你可以将其作为处理程序的响应返回。
¥The `Accept-Encoding` header is replaced with an encoding that the current runtime can handle. Unnecessary response headers are deleted, and a `Response` object is returned that you can return as a response from the handler.
### 示例
¥Examples
简单用法:
¥Simple usage:
```ts
app.get('/proxy/:path', (c) => {
return proxy(`http://${originServer}/${c.req.param('path')}`)
})
```
复杂用法:
¥Complicated usage:
```ts
app.get('/proxy/:path', async (c) => {
const res = await proxy(
`http://${originServer}/${c.req.param('path')}`,
{
headers: {
...c.req.header(), // optional, specify only when forwarding all the request data (including credentials) is necessary.
'X-Forwarded-For': '127.0.0.1',
'X-Forwarded-Host': c.req.header('host'),
Authorization: undefined, // do not propagate request headers contained in c.req.header('Authorization')
},
}
)
res.headers.delete('Set-Cookie')
return res
})
```
或者你可以将 `c.req` 作为参数传递。
¥Or you can pass the `c.req` as a parameter.
```ts
app.all('/proxy/:path', (c) => {
return proxy(`http://${originServer}/${c.req.param('path')}`, {
...c.req, // optional, specify only when forwarding all the request data (including credentials) is necessary.
headers: {
...c.req.header(),
'X-Forwarded-For': '127.0.0.1',
'X-Forwarded-Host': c.req.header('host'),
Authorization: undefined, // do not propagate request headers contained in c.req.header('Authorization')
},
})
})
```
### `ProxyFetch`
`proxy()` 的类型定义为 `ProxyFetch`,如下所示
¥The type of `proxy()` is defined as `ProxyFetch` and is as follows
```ts
interface ProxyRequestInit extends Omit {
raw?: Request
headers?:
| HeadersInit
| [string, string][]
| Record
| Record
}
interface ProxyFetch {
(
input: string | URL | Request,
init?: ProxyRequestInit
): Promise
}
```
# SSG 助手
¥SSG Helper
SSG Helper 从你的 Hono 应用生成静态站点。它将检索已注册路由的内容并将其保存为静态文件。
¥SSG Helper generates a static site from your Hono application. It will retrieve the contents of registered routes and save them as static files.
## 用法
¥Usage
### 手动
¥Manual
如果你有一个简单的 Hono 应用,如下所示:
¥If you have a simple Hono application like the following:
```tsx
// index.tsx
const app = new Hono()
app.get('/', (c) => c.html('Hello, World!'))
app.use('/about', async (c, next) => {
c.setRenderer((content, head) => {
return c.html(
{head.title ?? ''}
{content}
)
})
await next()
})
app.get('/about', (c) => {
return c.render('Hello!', { title: 'Hono SSG Page' })
})
export default app
```
对于 Node.js,创建如下构建脚本:
¥For Node.js, create a build script like this:
```ts
// build.ts
import app from './index'
import { toSSG } from 'hono/ssg'
import fs from 'fs/promises'
toSSG(app, fs)
```
通过执行脚本,文件将输出如下:
¥By executing the script, the files will be output as follows:
```bash
ls ./static
about.html index.html
```
### Vite 插件
¥Vite Plugin
使用 `@hono/vite-ssg` Vite 插件,你可以轻松处理该过程。
¥Using the `@hono/vite-ssg` Vite Plugin, you can easily handle the process.
有关更多详细信息,请参见此处:
¥For more details, see here:
[https://github.com/honojs/vite-plugins/tree/main/packages/ssg](https://github.com/honojs/vite-plugins/tree/main/packages/ssg)
## toSSG
`toSSG` 是生成静态站点的主要函数,以应用和文件系统模块作为参数。它基于以下内容:
¥`toSSG` is the main function for generating static sites, taking an application and a filesystem module as arguments. It is based on the following:
### 输入
¥Input
toSSG 的参数在 ToSSGInterface 中指定。
¥The arguments for toSSG are specified in ToSSGInterface.
```ts
export interface ToSSGInterface {
(
app: Hono,
fsModule: FileSystemModule,
options?: ToSSGOptions
): Promise
}
```
* `app` 指定已注册路由的 `new Hono()`。
¥`app` specifies `new Hono()` with registered routes.
* `fs` 指定以下对象,假设 `node:fs/promise`。
¥`fs` specifies the following object, assuming `node:fs/promise`.
```ts
export interface FileSystemModule {
writeFile(path: string, data: string | Uint8Array): Promise
mkdir(
path: string,
options: { recursive: boolean }
): Promise
}
```
### 使用 Deno 和 Bun 的适配器
¥Using adapters for Deno and Bun
如果要在 Deno 或 Bun 上使用 SSG,则每个文件系统都提供了一个 `toSSG` 函数。
¥If you want to use SSG on Deno or Bun, a `toSSG` function is provided for each file system.
对于 Deno:
¥For Deno:
```ts
import { toSSG } from 'hono/deno'
toSSG(app) // The second argument is an option typed `ToSSGOptions`.
```
对于 Bun:
¥For Bun:
```ts
import { toSSG } from 'hono/bun'
toSSG(app) // The second argument is an option typed `ToSSGOptions`.
```
### 选项
¥Options
选项在 ToSSGOptions 接口中指定。
¥Options are specified in the ToSSGOptions interface.
```ts
export interface ToSSGOptions {
dir?: string
concurrency?: number
beforeRequestHook?: BeforeRequestHook
afterResponseHook?: AfterResponseHook
afterGenerateHook?: AfterGenerateHook
extensionMap?: Record
}
```
* `dir` 是静态文件的输出目标。默认值为 `./static`。
¥`dir` is the output destination for Static files. The default value is `./static`.
* `concurrency` 是同时生成的文件数。默认值为 `2`。
¥`concurrency` is the concurrent number of files to be generated at the same time. The default value is `2`.
* `extensionMap` 是一个映射,其中包含 `Content-Type` 作为键和扩展字符串作为值。这用于确定输出文件的文件扩展名。
¥`extensionMap` is a map containing the `Content-Type` as a key and the string of the extension as a value. This is used to determine the file extension of the output file.
稍后将描述每个 Hook。
¥Each Hook will be described later.
### 输出
¥Output
`toSSG` 以以下 Result 类型返回结果。
¥`toSSG` returns the result in the following Result type.
```ts
export interface ToSSGResult {
success: boolean
files: string[]
error?: Error
}
```
## Hook
你可以通过在选项中指定以下自定义钩子来自定义 `toSSG` 的流程。
¥You can customize the process of `toSSG` by specifying the following custom hooks in options.
```ts
export type BeforeRequestHook = (req: Request) => Request | false
export type AfterResponseHook = (res: Response) => Response | false
export type AfterGenerateHook = (
result: ToSSGResult
) => void | Promise
```
### BeforeRequestHook/AfterResponseHook
`toSSG` 针对应用中注册的所有路由,但如果你想要排除某些路由,则可以通过指定 Hook 来过滤它们。
¥`toSSG` targets all routes registered in app, but if there are routes you want to exclude, you can filter them by specifying a Hook.
例如,如果你只想输出 GET 请求,请在 `beforeRequestHook` 中过滤 `req.method`。
¥For example, if you want to output only GET requests, filter `req.method` in `beforeRequestHook`.
```ts
toSSG(app, fs, {
beforeRequestHook: (req) => {
if (req.method === 'GET') {
return req
}
return false
},
})
```
例如,如果你只想在 StatusCode 为 200 或 500 时输出,请在 `afterResponseHook` 中过滤 `res.status`。
¥For example, if you want to output only when StatusCode is 200 or 500, filter `res.status` in `afterResponseHook`.
```ts
toSSG(app, fs, {
afterResponseHook: (res) => {
if (res.status === 200 || res.status === 500) {
return res
}
return false
},
})
```
### AfterGenerateHook
如果你想钩子 `toSSG` 的结果,请使用 `afterGenerateHook`。
¥Use `afterGenerateHook` if you want to hook the result of `toSSG`.
```ts
toSSG(app, fs, {
afterGenerateHook: (result) => {
if (result.files) {
result.files.forEach((file) => console.log(file))
}
})
})
```
## 生成文件
¥Generate File
### 路由和文件名
¥Route and Filename
以下规则适用于注册的路由信息和生成的文件名。默认 `./static` 的行为如下:
¥The following rules apply to the registered route information and the generated file name. The default `./static` behaves as follows:
* `/` -> `./static/index.html`
* `/path` -> `./static/path.html`
* `/path/` -> `./static/path/index.html`
### 文件扩展名
¥File Extension
文件扩展名取决于每个路由返回的 `Content-Type`。例如,来自 `c.html` 的响应保存为 `.html`。
¥The file extension depends on the `Content-Type` returned by each route. For example, responses from `c.html` are saved as `.html`.
如果你想要自定义文件扩展名,请设置 `extensionMap` 选项。
¥If you want to customize the file extensions, set the `extensionMap` option.
```ts
import { toSSG, defaultExtensionMap } from 'hono/ssg'
// Save `application/x-html` content with `.html`
toSSG(app, fs, {
extensionMap: {
'application/x-html': 'html',
...defaultExtensionMap,
},
})
```
请注意,无论扩展名是什么,以斜杠结尾的路径都会保存为 index.ext。
¥Note that paths ending with a slash are saved as index.ext regardless of the extension.
```ts
// save to ./static/html/index.html
app.get('/html/', (c) => c.html('html'))
// save to ./static/text/index.txt
app.get('/text/', (c) => c.text('text'))
```
## 中间件
¥Middleware
引入支持 SSG 的内置中间件。
¥Introducing built-in middleware that supports SSG.
### ssgParams
你可以使用 Next.js 的 `generateStaticParams` 之类的 API。
¥You can use an API like `generateStaticParams` of Next.js.
示例:
¥Example:
```ts
app.get(
'/shops/:id',
ssgParams(async () => {
const shops = await getShops()
return shops.map((shop) => ({ id: shop.id }))
}),
async (c) => {
const shop = await getShop(c.req.param('id'))
if (!shop) {
return c.notFound()
}
return c.render(
{shop.name}
)
}
)
```
### disableSSG
具有 `disableSSG` 中间件集的路由被 `toSSG` 排除在静态文件生成之外。
¥Routes with the `disableSSG` middleware set are excluded from static file generation by `toSSG`.
```ts
app.get('/api', disableSSG(), (c) => c.text('an-api'))
```
### onlySSG
具有 `onlySSG` 中间件集的路由将在 `toSSG` 执行后被 `c.notFound()` 覆盖。
¥Routes with the `onlySSG` middleware set will be overridden by `c.notFound()` after `toSSG` execution.
```ts
app.get('/static-page', onlySSG(), (c) => c.html(
Welcome to my site
))
```
# 流式助手
¥Streaming Helper
Streaming Helper 提供用于流式响应的方法。
¥The Streaming Helper provides methods for streaming responses.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { stream, streamText, streamSSE } from 'hono/streaming'
```
## `stream()`
它返回一个简单的流响应作为 `Response` 对象。
¥It returns a simple streaming response as `Response` object.
```ts
app.get('/stream', (c) => {
return stream(c, async (stream) => {
// Write a process to be executed when aborted.
stream.onAbort(() => {
console.log('Aborted!')
})
// Write a Uint8Array.
await stream.write(new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]))
// Pipe a readable stream.
await stream.pipe(anotherReadableStream)
})
})
```
## `streamText()`
它返回带有 `Content-Type:text/plain`、`Transfer-Encoding:chunked` 和 `X-Content-Type-Options:nosniff` 标头的流响应。
¥It returns a streaming response with `Content-Type:text/plain`, `Transfer-Encoding:chunked`, and `X-Content-Type-Options:nosniff` headers.
```ts
app.get('/streamText', (c) => {
return streamText(c, async (stream) => {
// Write a text with a new line ('\n').
await stream.writeln('Hello')
// Wait 1 second.
await stream.sleep(1000)
// Write a text without a new line.
await stream.write(`Hono!`)
})
})
```
::: warning 警告
如果你正在为 Cloudflare Workers 开发应用,流媒体可能无法在 Wrangler 上正常工作。如果是这样,请为 `Content-Encoding` 标头添加 `Identity`。
¥If you are developing an application for Cloudflare Workers, a streaming may not work well on Wrangler. If so, add `Identity` for `Content-Encoding` header.
```ts
app.get('/streamText', (c) => {
c.header('Content-Encoding', 'Identity')
return streamText(c, async (stream) => {
// ...
})
})
```
:::
## `streamSSE()`
它允许你无缝地流式传输服务器发送事件 (SSE)。
¥It allows you to stream Server-Sent Events (SSE) seamlessly.
```ts
const app = new Hono()
let id = 0
app.get('/sse', async (c) => {
return streamSSE(c, async (stream) => {
while (true) {
const message = `It is ${new Date().toISOString()}`
await stream.writeSSE({
data: message,
event: 'time-update',
id: String(id++),
})
await stream.sleep(1000)
}
})
})
```
## 错误处理
¥Error Handling
流助手的第三个参数是错误处理程序。此参数是可选的,如果不指定,错误将作为控制台错误输出。
¥The third argument of the streaming helper is an error handler.
This argument is optional, if you don't specify it, the error will be output as a console error.
```ts
app.get('/stream', (c) => {
return stream(
c,
async (stream) => {
// Write a process to be executed when aborted.
stream.onAbort(() => {
console.log('Aborted!')
})
// Write a Uint8Array.
await stream.write(
new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f])
)
// Pipe a readable stream.
await stream.pipe(anotherReadableStream)
},
(err, stream) => {
stream.writeln('An error occurred!')
console.error(err)
}
)
})
```
执行回调后,流将自动关闭。
¥The stream will be automatically closed after the callbacks are executed.
::: warning 警告
如果流式助手的回调函数抛出错误,则不会触发 Hono 的 `onError` 事件。
¥If the callback function of the streaming helper throws an error, the `onError` event of Hono will not be triggered.
`onError` 是一个钩子,用于在发送响应之前处理错误并覆盖响应。但是,当执行回调函数时,流已经开始,因此无法覆盖它。
¥`onError` is a hook to handle errors before the response is sent and overwrite the response. However, when the callback function is executed, the stream has already started, so it cannot be overwritten.
:::
# 测试助手
¥Testing Helper
Testing Helper 提供使 Hono 应用测试更容易的功能。
¥The Testing Helper provides functions to make testing of Hono applications easier.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
```
## `testClient()`
`testClient()` 将 Hono 的实例作为其第一个参数并返回 [Hono 客户端](/docs/guides/rpc#client) 的对象。通过使用它,你可以使用编辑器完成定义你的请求。
¥The `testClient()` takes an instance of Hono as its first argument and returns an object of the [Hono Client](/docs/guides/rpc#client). By using this, you can define your request with the editor completion.
```ts
import { testClient } from 'hono/testing'
it('test', async () => {
const app = new Hono().get('/search', (c) =>
c.json({ hello: 'world' })
)
const res = await testClient(app).search.$get()
expect(await res.json()).toEqual({ hello: 'world' })
})
```
# WebSocket 助手
¥WebSocket Helper
WebSocket Helper 是 Hono 应用中服务器端 WebSockets 的助手。目前 Cloudflare Workers / Pages、Deno 和 Bun 适配器可用。
¥WebSocket Helper is a helper for server-side WebSockets in Hono applications.
Currently Cloudflare Workers / Pages, Deno, and Bun adapters are available.
## 导入
¥Import
::: code-group
```ts [Cloudflare Workers]
import { Hono } from 'hono'
import { upgradeWebSocket } from 'hono/cloudflare-workers'
```
```ts [Deno]
import { Hono } from 'hono'
import { upgradeWebSocket } from 'hono/deno'
```
```ts [Bun]
import { Hono } from 'hono'
import { createBunWebSocket } from 'hono/bun'
import type { ServerWebSocket } from 'bun'
const { upgradeWebSocket, websocket } =
createBunWebSocket()
// ...
export default {
fetch: app.fetch,
websocket,
}
```
:::
如果你使用 Node.js,则可以使用 [@hono/node-ws](https://github.com/honojs/middleware/tree/main/packages/node-ws)。
¥If you use Node.js, you can use [@hono/node-ws](https://github.com/honojs/middleware/tree/main/packages/node-ws).
## `upgradeWebSocket()`
`upgradeWebSocket()` 返回用于处理 WebSocket 的处理程序。
¥`upgradeWebSocket()` returns a handler for handling WebSocket.
```ts
const app = new Hono()
app.get(
'/ws',
upgradeWebSocket((c) => {
return {
onMessage(event, ws) {
console.log(`Message from client: ${event.data}`)
ws.send('Hello from server!')
},
onClose: () => {
console.log('Connection closed')
},
}
})
)
```
可用事件:
¥Available events:
* `onOpen` - 目前,Cloudflare Workers 不支持它。
¥`onOpen` - Currently, Cloudflare Workers does not support it.
* `onMessage`
* `onClose`
* `onError`
::: warning 警告
如果你在使用 WebSocket Helper 的路由上使用修改标头的中间件(例如,应用 CORS),则可能会遇到错误,提示你无法修改不可变标头。这是因为 `upgradeWebSocket()` 也在内部更改标头。
¥If you use middleware that modifies headers (e.g., applying CORS) on a route that uses WebSocket Helper, you may encounter an error saying you can't modify immutable headers. This is because `upgradeWebSocket()` also changes headers internally.
因此,如果你同时使用 WebSocket Helper 和中间件,请谨慎使用。
¥Therefore, please be cautious if you are using WebSocket Helper and middleware at the same time.
:::
## RPC 模式
¥RPC-mode
使用 WebSocket Helper 定义的处理程序支持 RPC 模式。
¥Handlers defined with WebSocket Helper support RPC mode.
```ts
// server.ts
const wsApp = app.get(
'/ws',
upgradeWebSocket((c) => {
//...
})
)
export type WebSocketApp = typeof wsApp
// client.ts
const client = hc('http://localhost:8787')
const socket = client.ws.$ws() // A WebSocket object for a client
```
## 示例
¥Examples
查看使用 WebSocket Helper 的示例。
¥See the examples using WebSocket Helper.
### 服务器和客户端
¥Server and Client
```ts
// server.ts
import { Hono } from 'hono'
import { upgradeWebSocket } from 'hono/cloudflare-workers'
const app = new Hono().get(
'/ws',
upgradeWebSocket(() => {
return {
onMessage: (event) => {
console.log(event.data)
},
}
})
)
export default app
```
```ts
// client.ts
import { hc } from 'hono/client'
import type app from './server'
const client = hc('http://localhost:8787')
const ws = client.ws.$ws(0)
ws.addEventListener('open', () => {
setInterval(() => {
ws.send(new Date().toString())
}, 1000)
})
```
### 带有 JSX 的 Bun
¥Bun with JSX
```tsx
import { Hono } from 'hono'
import { createBunWebSocket } from 'hono/bun'
const { upgradeWebSocket, websocket } = createBunWebSocket()
const app = new Hono()
app.get('/', (c) => {
return c.html(
)
})
const ws = app.get(
'/ws',
upgradeWebSocket((c) => {
let intervalId
return {
onOpen(_event, ws) {
intervalId = setInterval(() => {
ws.send(new Date().toString())
}, 200)
},
onClose() {
clearInterval(intervalId)
},
}
})
)
export default {
fetch: app.fetch,
websocket,
}
```
# 最佳实践
¥Best Practices
Hono 非常灵活。你可以根据需要编写应用。但是,有更好的最佳实践可以遵循。
¥Hono is very flexible. You can write your app as you like.
However, there are best practices that are better to follow.
## 尽可能不要制作 "控制器"
¥Don't make "Controllers" when possible
如果可能,你不应创建 "类似 Ruby on Rails 的控制器"。
¥When possible, you should not create "Ruby on Rails-like Controllers".
```ts
// 🙁
// A RoR-like Controller
const booksList = (c: Context) => {
return c.json('list books')
}
app.get('/books', booksList)
```
问题与类型有关。例如,如果不编写复杂的泛型,则无法在 Controller 中推断路径参数。
¥The issue is related to types. For example, the path parameter cannot be inferred in the Controller without writing complex generics.
```ts
// 🙁
// A RoR-like Controller
const bookPermalink = (c: Context) => {
const id = c.req.param('id') // Can't infer the path param
return c.json(`get ${id}`)
}
```
因此,你不需要创建类似 RoR 的控制器,而应该在路径定义后直接编写处理程序。
¥Therefore, you don't need to create RoR-like controllers and should write handlers directly after path definitions.
```ts
// 😃
app.get('/books/:id', (c) => {
const id = c.req.param('id') // Can infer the path param
return c.json(`get ${id}`)
})
```
## `hono/factory` 中的 `factory.createHandlers()`
¥`factory.createHandlers()` in `hono/factory`
如果你仍想创建类似 RoR 的控制器,请在 [`hono/factory`](/docs/helpers/factory) 中使用 `factory.createHandlers()`。如果你使用这种方法,类型推断将正常工作。
¥If you still want to create a RoR-like Controller, use `factory.createHandlers()` in [`hono/factory`](/docs/helpers/factory). If you use this, type inference will work correctly.
```ts
import { createFactory } from 'hono/factory'
import { logger } from 'hono/logger'
// ...
// 😃
const factory = createFactory()
const middleware = factory.createMiddleware(async (c, next) => {
c.set('foo', 'bar')
await next()
})
const handlers = factory.createHandlers(logger(), middleware, (c) => {
return c.json(c.var.foo)
})
app.get('/api', ...handlers)
```
## 构建更大的应用
¥Building a larger application
使用 `app.route()` 构建更大的应用而无需创建 "类似 Ruby on Rails 的控制器"。
¥Use `app.route()` to build a larger application without creating "Ruby on Rails-like Controllers".
如果你的应用有 `/authors` 和 `/books` 端点,并且你希望将文件与 `index.ts` 分开,请创建 `authors.ts` 和 `books.ts`。
¥If your application has `/authors` and `/books` endpoints and you wish to separate files from `index.ts`, create `authors.ts` and `books.ts`.
```ts
// authors.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.json('list authors'))
app.post('/', (c) => c.json('create an author', 201))
app.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default app
```
```ts
// books.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.json('list books'))
app.post('/', (c) => c.json('create a book', 201))
app.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default app
```
然后,导入它们并将其与 `app.route()` 一起挂载在路径 `/authors` 和 `/books` 上。
¥Then, import them and mount on the paths `/authors` and `/books` with `app.route()`.
```ts
// index.ts
import { Hono } from 'hono'
import authors from './authors'
import books from './books'
const app = new Hono()
// 😃
app.route('/authors', authors)
app.route('/books', books)
export default app
```
### 如果要使用 RPC 功能
¥If you want to use RPC features
上面的代码对于正常用例来说效果很好。但是,如果你想使用 `RPC` 功能,可以通过如下链接获取正确的类型。
¥The code above works well for normal use cases.
However, if you want to use the `RPC` feature, you can get the correct type by chaining as follows.
```ts
// authors.ts
import { Hono } from 'hono'
const app = new Hono()
.get('/', (c) => c.json('list authors'))
.post('/', (c) => c.json('create an author', 201))
.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default app
```
如果你将 `app` 的类型传递给 `hc`,它将获得正确的类型。
¥If you pass the type of the `app` to `hc`, it will get the correct type.
```ts
import app from './authors'
import { hc } from 'hono/client'
// 😃
const client = hc('http://localhost') // Typed correctly
```
有关更多详细信息,请参阅 [RPC 页面](/docs/guides/rpc#using-rpc-with-larger-applications)。
¥For more detailed information, please see [the RPC page](/docs/guides/rpc#using-rpc-with-larger-applications).
# 示例
¥Examples
查看 [示例部分](/examples/)。
¥See the [Examples section](/examples/).
# 常见问题
¥Frequently Asked Questions
本指南收集了有关 Hono 的常见问题 (FAQ) 及其解决方法。
¥This guide is a collection of frequently asked questions (FAQ) about Hono and how to resolve them.
## Hono 有官方的 Renovate 配置吗?
¥Is there an official Renovate config for Hono?
Hono 团队目前不维护 [Renovate](https://github.com/renovatebot/renovate) 配置。因此,请按如下方式使用第三方 renovate-config。
¥The Hono teams does not currently maintain [Renovate](https://github.com/renovatebot/renovate) Configuration.
Therefore, please use third-party renovate-config as follows.
在你的 `renovate.json` 中:
¥In your `renovate.json` :
```json
// renovate.json
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"github>shinGangan/renovate-config-hono" // [!code ++]
]
}
```
有关更多详细信息,请参阅 [renovate-config-hono](https://github.com/shinGangan/renovate-config-hono) 存储库。
¥see [renovate-config-hono](https://github.com/shinGangan/renovate-config-hono) repository for more details.
# 助手工具
¥Helpers
辅助程序可用于协助开发你的应用。与中间件不同,它们不充当处理程序,而是提供有用的功能。
¥Helpers are available to assist in developing your application. Unlike middleware, they don't act as handlers, but rather provide useful functions.
例如,以下是如何使用 [Cookie 助手](/docs/helpers/cookie):
¥For instance, here's how to use the [Cookie helper](/docs/helpers/cookie):
```ts
import { getCookie, setCookie } from 'hono/cookie'
const app = new Hono()
app.get('/cookie', (c) => {
const yummyCookie = getCookie(c, 'yummy_cookie')
// ...
setCookie(c, 'delicious_cookie', 'macha')
//
})
```
## 可用助手
¥Available Helpers
* [接受](/docs/helpers/accepts)
¥[Accepts](/docs/helpers/accepts)
* [适配器](/docs/helpers/adapter)
¥[Adapter](/docs/helpers/adapter)
* [Cookie](/docs/helpers/cookie)
* [css](/docs/helpers/css)
* [开发](/docs/helpers/dev)
¥[Dev](/docs/helpers/dev)
* [工厂](/docs/helpers/factory)
¥[Factory](/docs/helpers/factory)
* [html](/docs/helpers/html)
* [JWT](/docs/helpers/jwt)
* [SSG](/docs/helpers/ssg)
* [流式传输](/docs/helpers/streaming)
¥[Streaming](/docs/helpers/streaming)
* [测试](/docs/helpers/testing)
¥[Testing](/docs/helpers/testing)
* [WebSocket](/docs/helpers/websocket)
# 客户端组件
¥Client Components
`hono/jsx` 不仅支持服务器端,还支持客户端。这意味着可以创建一个在浏览器中运行的交互式 UI。我们称之为客户端组件或 `hono/jsx/dom`。
¥`hono/jsx` supports not only server side but also client side. This means that it is possible to create an interactive UI that runs in the browser. We call it Client Components or `hono/jsx/dom`.
它速度快而且非常小。`hono/jsx/dom` 中的计数器程序使用 Brotli 压缩后只有 2.8KB。但是,React 占用 47.8KB。
¥It is fast and very small. The counter program in `hono/jsx/dom` is only 2.8KB with Brotli compression. But, 47.8KB for React.
本节介绍客户端组件特有的功能。
¥This section introduces Client Components-specific features.
## 反例
¥Counter example
以下是简单计数器的示例,相同的代码与 React 中的工作方式相同。
¥Here is an example of a simple counter, the same code works as in React.
```tsx
import { useState } from 'hono/jsx'
import { render } from 'hono/jsx/dom'
function Counter() {
const [count, setCount] = useState(0)
return (
Count: {count}
)
}
function App() {
return (
)
}
const root = document.getElementById('root')
render(, root)
```
## `render()`
你可以使用 `render()` 在指定的 HTML 元素内插入 JSX 组件。
¥You can use `render()` to insert JSX components within a specified HTML element.
```tsx
render(, container)
```
## 与 React 兼容的 Hooks
¥Hooks compatible with React
hono/jsx/dom 具有与 React 兼容或部分兼容的 Hooks。你可以通过查看 [React 文档](https://react.nodejs.cn/reference/react/hooks) 来了解这些 API。
¥hono/jsx/dom has Hooks that are compatible or partially compatible with React. You can learn about these APIs by looking at [the React documentation](https://react.nodejs.cn/reference/react/hooks).
* `useState()`
* `useEffect()`
* `useRef()`
* `useCallback()`
* `use()`
* `startTransition()`
* `useTransition()`
* `useDeferredValue()`
* `useMemo()`
* `useLayoutEffect()`
* `useReducer()`
* `useDebugValue()`
* `createElement()`
* `memo()`
* `isValidElement()`
* `useId()`
* `createRef()`
* `forwardRef()`
* `useImperativeHandle()`
* `useSyncExternalStore()`
* `useInsertionEffect()`
* `useFormStatus()`
* `useActionState()`
* `useOptimistic()`
## `startViewTransition()` 系列
¥`startViewTransition()` family
`startViewTransition()` 系列包含原始钩子和函数,可轻松处理 [查看转换 API](https://web.nodejs.cn/en-US/docs/Web/API/View_Transitions_API)。以下是如何使用它们的示例。
¥The `startViewTransition()` family contains original hooks and functions to handle [View Transitions API](https://web.nodejs.cn/en-US/docs/Web/API/View_Transitions_API) easily. The followings are examples of how to use them.
### 1. 最简单的例子
¥ An easiest example
你可以使用 `document.startViewTransition` 或 `startViewTransition()` 语法编写过渡。
¥You can write a transition using the `document.startViewTransition` shortly with the `startViewTransition()`.
```tsx
import { useState, startViewTransition } from 'hono/jsx'
import { css, Style } from 'hono/css'
export default function App() {
const [showLargeImage, setShowLargeImage] = useState(false)
return (
<>
{!showLargeImage ? (
) : (
)}
>
)
}
```
### 2. 将 `viewTransition()` 与 `keyframes()` 一起使用
¥ Using `viewTransition()` with `keyframes()`
`viewTransition()` 函数允许你获取唯一的 `view-transition-name`。
¥The `viewTransition()` function allows you to get the unique `view-transition-name`.
你可以将它与 `keyframes()` 一起使用,`::view-transition-old()` 转换为 `::view-transition-old(${uniqueName))`。
¥You can use it with the `keyframes()`, The `::view-transition-old()` is converted to `::view-transition-old(${uniqueName))`.
```tsx
import { useState, startViewTransition } from 'hono/jsx'
import { viewTransition } from 'hono/jsx/dom/css'
import { css, keyframes, Style } from 'hono/css'
const rotate = keyframes`
from {
rotate: 0deg;
}
to {
rotate: 360deg;
}
`
export default function App() {
const [showLargeImage, setShowLargeImage] = useState(false)
const [transitionNameClass] = useState(() =>
viewTransition(css`
::view-transition-old() {
animation-name: ${rotate};
}
::view-transition-new() {
animation-name: ${rotate};
}
`)
)
return (
<>
{!showLargeImage ? (
) : (
)}
>
)
}
```
### 3. 使用 `useViewTransition`
¥ Using `useViewTransition`
如果只想在动画期间更改样式。你可以使用 `useViewTransition()`。此钩子返回 `[boolean, (callback: () => void) => void]`,它们是 `isUpdating` 标志和 `startViewTransition()` 函数。
¥If you want to change the style only during the animation. You can use `useViewTransition()`. This hook returns the `[boolean, (callback: () => void) => void]`, and they are the `isUpdating` flag and the `startViewTransition()` function.
使用此钩子时,组件将在以下两次进行评估。
¥When this hook is used, the Component is evaluated at the following two times.
* 在对 `startViewTransition()` 的调用的回调中。
¥Inside the callback of a call to `startViewTransition()`.
* 当 [`finish` promise 实现](https://web.nodejs.cn/en-US/docs/Web/API/ViewTransition/finished) 时
¥When [the `finish` promise becomes fulfilled](https://web.nodejs.cn/en-US/docs/Web/API/ViewTransition/finished)
```tsx
import { useState, useViewTransition } from 'hono/jsx'
import { viewTransition } from 'hono/jsx/dom/css'
import { css, keyframes, Style } from 'hono/css'
const rotate = keyframes`
from {
rotate: 0deg;
}
to {
rotate: 360deg;
}
`
export default function App() {
const [isUpdating, startViewTransition] = useViewTransition()
const [showLargeImage, setShowLargeImage] = useState(false)
const [transitionNameClass] = useState(() =>
viewTransition(css`
::view-transition-old() {
animation-name: ${rotate};
}
::view-transition-new() {
animation-name: ${rotate};
}
`)
)
return (
<>
{!showLargeImage ? (
) : (
)}
>
)
}
```
## `hono/jsx/dom` 运行时
¥The `hono/jsx/dom` runtime
有一个用于客户端组件的小型 JSX 运行时。使用它将产生比使用 `hono/jsx` 更小的打包结果。在 `tsconfig.json` 中指定 `hono/jsx/dom`。对于 Deno,修改 deno.json。
¥There is a small JSX Runtime for Client Components. Using this will result in smaller bundled results than using `hono/jsx`. Specify `hono/jsx/dom` in `tsconfig.json`. For Deno, modify the deno.json.
```json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx/dom"
}
}
```
# JSX
你可以使用 `hono/jsx` 和 JSX 语法编写 HTML。
¥You can write HTML with JSX syntax with `hono/jsx`.
虽然 `hono/jsx` 在客户端上运行,但你可能最常在服务器端渲染内容时使用它。以下是与 JSX 相关的一些内容,这些内容对服务器和客户端都很常见。
¥Although `hono/jsx` works on the client, you will probably use it most often when rendering content on the server side. Here are some things related to JSX that are common to both server and client.
## 设置
¥Settings
要使用 JSX,请修改 `tsconfig.json`:
¥To use JSX, modify the `tsconfig.json`:
`tsconfig.json`:
```json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}
```
或者,使用 pragma 指令:
¥Alternatively, use the pragma directives:
```ts
/** @jsx jsx */
/** @jsxImportSource hono/jsx */
```
对于 Deno,你必须修改 `deno.json` 而不是 `tsconfig.json`:
¥For Deno, you have to modify the `deno.json` instead of the `tsconfig.json`:
```json
{
"compilerOptions": {
"jsx": "precompile",
"jsxImportSource": "hono/jsx"
}
}
```
## 用法
¥Usage
`index.tsx`:
```tsx
import { Hono } from 'hono'
import type { FC } from 'hono/jsx'
const app = new Hono()
const Layout: FC = (props) => {
return (
{props.children}
)
}
const Top: FC<{ messages: string[] }> = (props: {
messages: string[]
}) => {
return (
Hello Hono!
{props.messages.map((message) => {
return
{message}!!
})}
)
}
app.get('/', (c) => {
const messages = ['Good Morning', 'Good Evening', 'Good Night']
return c.html()
})
export default app
```
## 片段
¥Fragment
使用 Fragment 对多个元素进行分组,而无需添加额外的节点:
¥Use Fragment to group multiple elements without adding extra nodes:
```tsx
import { Fragment } from 'hono/jsx'
const List = () => (
first child
second child
third child
)
```
或者,如果设置正确,你可以使用 `<>>` 编写它。
¥Or you can write it with `<>>` if it set up properly.
```tsx
const List = () => (
<>
first child
second child
third child
>
)
```
## `PropsWithChildren`
你可以使用 `PropsWithChildren` 正确地推断函数组件中的子元素。
¥You can use `PropsWithChildren` to correctly infer a child element in a function component.
```tsx
import { PropsWithChildren } from 'hono/jsx'
type Post = {
id: number
title: string
}
function Component({ title, children }: PropsWithChildren) {
return (
{title}
{children}
)
}
```
## 插入原始 HTML
¥Inserting Raw HTML
要直接插入 HTML,请使用 `dangerouslySetInnerHTML`:
¥To directly insert HTML, use `dangerouslySetInnerHTML`:
```tsx
app.get('/foo', (c) => {
const inner = { __html: 'JSX · SSR' }
const Div =
})
```
## 记忆化
¥Memoization
通过使用 `memo` 记忆计算字符串来优化你的组件:
¥Optimize your components by memoizing computed strings using `memo`:
```tsx
import { memo } from 'hono/jsx'
const Header = memo(() => Welcome to Hono)
const Footer = memo(() => )
const Layout = (
Hono is cool!
)
```
## 上下文
¥Context
通过使用 `useContext`,你可以在组件树的任何级别上全局共享数据,而无需通过 props 传递值。
¥By using `useContext`, you can share data globally across any level of the Component tree without passing values through props.
```tsx
import type { FC } from 'hono/jsx'
import { createContext, useContext } from 'hono/jsx'
const themes = {
light: {
color: '#000000',
background: '#eeeeee',
},
dark: {
color: '#ffffff',
background: '#222222',
},
}
const ThemeContext = createContext(themes.light)
const Button: FC = () => {
const theme = useContext(ThemeContext)
return
}
const Toolbar: FC = () => {
return (
)
}
// ...
app.get('/', (c) => {
return c.html(
)
})
```
## 异步组件
¥Async Component
`hono/jsx` 支持异步组件,因此你可以在组件中使用 `async`/`await`。如果你使用 `c.html()` 渲染它,它将自动等待。
¥`hono/jsx` supports an Async Component, so you can use `async`/`await` in your component.
If you render it with `c.html()`, it will await automatically.
```tsx
const AsyncComponent = async () => {
await new Promise((r) => setTimeout(r, 1000)) // sleep 1s
return
Done!
}
app.get('/', (c) => {
return c.html(
)
})
```
## Suspense
¥Suspense
类似 React 的 `Suspense` 功能可用。如果你用 `Suspense` 封装异步组件,则 fallback 中的内容将首先渲染,一旦 Promise 被解析,将显示等待的内容。你可以将它与 `renderToReadableStream()` 一起使用。
¥The React-like `Suspense` feature is available.
If you wrap the async component with `Suspense`, the content in the fallback will be rendered first, and once the Promise is resolved, the awaited content will be displayed.
You can use it with `renderToReadableStream()`.
```tsx
import { renderToReadableStream, Suspense } from 'hono/jsx/streaming'
//...
app.get('/', (c) => {
const stream = renderToReadableStream(
loading...}>
)
return c.body(stream, {
headers: {
'Content-Type': 'text/html; charset=UTF-8',
'Transfer-Encoding': 'chunked',
},
})
})
```
## ErrorBoundary
¥ErrorBoundary
你可以使用 `ErrorBoundary` 捕获子组件中的错误。
¥You can catch errors in child components using `ErrorBoundary`.
在下面的示例中,如果发生错误,它将显示 `fallback` 中指定的内容。
¥In the example below, it will show the content specified in `fallback` if an error occurs.
```tsx
function SyncComponent() {
throw new Error('Error')
return
Hello
}
app.get('/sync', async (c) => {
return c.html(
Out of Service}>
)
})
```
`ErrorBoundary` 还可以与异步组件和 `Suspense` 一起使用。
¥`ErrorBoundary` can also be used with async components and `Suspense`.
```tsx
async function AsyncComponent() {
await new Promise((resolve) => setTimeout(resolve, 2000))
throw new Error('Error')
return
Hello
}
app.get('/with-suspense', async (c) => {
return c.html(
Out of Service}>
Loading...}>
)
})
```
## 与 html 集成中间件
¥Integration with html Middleware
结合 JSX 和 html 中间件,实现强大的模板。有关详细信息,请参阅 [html 中间件文档](/docs/helpers/html)。
¥Combine the JSX and html middlewares for powerful templating.
For in-depth details, consult the [html middleware documentation](/docs/helpers/html).
```tsx
import { Hono } from 'hono'
import { html } from 'hono/html'
const app = new Hono()
interface SiteData {
title: string
children?: any
}
const Layout = (props: SiteData) =>
html`
${props.title}
${props.children}
`
const Content = (props: { siteData: SiteData; name: string }) => (
Hello {props.name}
)
app.get('/:name', (c) => {
const { name } = c.req.param()
const props = {
name: name,
siteData: {
title: 'JSX with html sample',
},
}
return c.html()
})
export default app
```
## 使用 JSX 渲染器中间件
¥With JSX Renderer Middleware
[JSX 渲染器中间件](/docs/middleware/builtin/jsx-renderer) 允许你更轻松地使用 JSX 创建 HTML 页面。
¥The [JSX Renderer Middleware](/docs/middleware/builtin/jsx-renderer) allows you to create HTML pages more easily with the JSX.
## 覆盖类型定义
¥Override type definitions
你可以覆盖类型定义以添加自定义元素和属性。
¥You can override the type definition to add your custom elements and attributes.
```ts
declare module 'hono/jsx' {
namespace JSX {
interface IntrinsicElements {
'my-custom-element': HTMLAttributes & {
'x-event'?: 'click' | 'scroll'
}
}
}
}
```
# 中间件
¥Middleware
中间件在 Handler 之后/之前工作。我们可以在调度之前获取 `Request`,或者在调度之后操作 `Response`。
¥Middleware works after/before Handler. We can get `Request` before dispatching or manipulate `Response` after dispatching.
## 中间件定义
¥Definition of Middleware
* 处理程序 - 应该返回 `Response` 对象。只会调用一个处理程序。
¥Handler - should return `Response` object. Only one handler will be called.
* 中间件 - 应该不返回任何内容,将继续使用 `await next()` 进行下一个中间件
¥Middleware - should return nothing, will be proceeded to next middleware with `await next()`
用户可以使用 `app.use` 或使用 `app.HTTP_METHOD` 以及处理程序来注册中间件。对于此功能,可以轻松指定路径和方法。
¥The user can register middleware using `app.use` or using `app.HTTP_METHOD` as well as the handlers. For this feature, it's easy to specify the path and the method.
```ts
// match any method, all routes
app.use(logger())
// specify path
app.use('/posts/*', cors())
// specify method and path
app.post('/posts/*', basicAuth())
```
如果处理程序返回 `Response`,它将用于终端用户并停止处理。
¥If the handler returns `Response`, it will be used for the end-user, and stopping the processing.
```ts
app.post('/posts', (c) => c.text('Created!', 201))
```
在这种情况下,在调度之前会处理四个中间件,如下所示:
¥In this case, four middleware are processed before dispatching like this:
```ts
logger() -> cors() -> basicAuth() -> *handler*
```
## 执行顺序
¥Execution order
中间件的执行顺序由其注册的顺序决定。第一个注册的中间件的 `next` 之前的进程首先执行,`next` 之后的进程最后执行。见下文。
¥The order in which Middleware is executed is determined by the order in which it is registered.
The process before the `next` of the first registered Middleware is executed first,
and the process after the `next` is executed last.
See below.
```ts
app.use(async (_, next) => {
console.log('middleware 1 start')
await next()
console.log('middleware 1 end')
})
app.use(async (_, next) => {
console.log('middleware 2 start')
await next()
console.log('middleware 2 end')
})
app.use(async (_, next) => {
console.log('middleware 3 start')
await next()
console.log('middleware 3 end')
})
app.get('/', (c) => {
console.log('handler')
return c.text('Hello!')
})
```
结果如下。
¥Result is the following.
```
middleware 1 start
middleware 2 start
middleware 3 start
handler
middleware 3 end
middleware 2 end
middleware 1 end
```
## 内置中间件
¥Built-in Middleware
Hono 有内置中间件。
¥Hono has built-in middleware.
```ts
import { Hono } from 'hono'
import { poweredBy } from 'hono/powered-by'
import { logger } from 'hono/logger'
import { basicAuth } from 'hono/basic-auth'
const app = new Hono()
app.use(poweredBy())
app.use(logger())
app.use(
'/auth/*',
basicAuth({
username: 'hono',
password: 'acoolproject',
})
)
```
::: warning 警告
在 Deno 中,可以使用与 Hono 版本不同的中间件版本,但这可能会导致错误。例如,由于版本不同,此代码无法正常工作。
¥In Deno, it is possible to use a different version of middleware than the Hono version, but this can lead to bugs.
For example, this code is not working because the version is different.
```ts
import { Hono } from 'jsr:@hono/hono@4.4.0'
import { upgradeWebSocket } from 'jsr:@hono/hono@4.4.5/deno'
const app = new Hono()
app.get(
'/ws',
upgradeWebSocket(() => ({
// ...
}))
)
```
:::
## 自定义中间件
¥Custom Middleware
你可以在 `app.use()` 中直接编写自己的中间件:
¥You can write your own middleware directly inside `app.use()`:
```ts
// Custom logger
app.use(async (c, next) => {
console.log(`[${c.req.method}] ${c.req.url}`)
await next()
})
// Add a custom header
app.use('/message/*', async (c, next) => {
await next()
c.header('x-message', 'This is middleware!')
})
app.get('/message/hello', (c) => c.text('Hello Middleware!'))
```
但是,将中间件直接嵌入 `app.use()` 会限制其可重用性。因此,我们可以将中间件分成不同的文件。
¥However, embedding middleware directly within `app.use()` can limit its reusability. Therefore, we can separate our
middleware into different files.
为了确保我们不会丢失 `context` 和 `next` 的类型定义,在分离中间件时,我们可以使用 Hono 工厂中的 [`createMiddleware()`](/docs/helpers/factory#createmiddleware)。这也允许我们从下游处理程序安全地键入 [访问我们在 `Context` 中的 `set` 的数据](https://hono.nodejs.cn/docs/api/context#set-get)。
¥To ensure we don't lose type definitions for `context` and `next`, when separating middleware, we can use
[`createMiddleware()`](/docs/helpers/factory#createmiddleware) from Hono's factory. This also allows us to type-safely [access data we've `set` in `Context`](https://hono.nodejs.cn/docs/api/context#set-get) from downstream handlers.
```ts
import { createMiddleware } from 'hono/factory'
const logger = createMiddleware(async (c, next) => {
console.log(`[${c.req.method}] ${c.req.url}`)
await next()
})
```
:::info 信息
类型泛型可以与 `createMiddleware` 一起使用:
¥Type generics can be used with `createMiddleware`:
```ts
createMiddleware<{Bindings: Bindings}>(async (c, next) =>
```
:::
### 下一步后修改响应
¥Modify the Response After Next
此外,中间件可以设计为在必要时修改响应:
¥Additionally, middleware can be designed to modify responses if necessary:
```ts
const stripRes = createMiddleware(async (c, next) => {
await next()
c.res = undefined
c.res = new Response('New Response')
})
```
## 中间件参数内的上下文访问
¥Context access inside Middleware arguments
要访问中间件参数中的上下文,直接使用 `app.use` 提供的 context 参数即可。有关说明,请参阅下面的示例。
¥To access the context inside middleware arguments, directly use the context parameter provided by `app.use`. See the example below for clarification.
```ts
import { cors } from 'hono/cors'
app.use('*', async (c, next) => {
const middleware = cors({
origin: c.env.CORS_ORIGIN,
})
return middleware(c, next)
})
```
### 扩展中间件中的上下文
¥Extending the Context in Middleware
要扩展中间件内部的上下文,请使用 `c.set`。你可以通过将 `{ Variables: { yourVariable: YourVariableType } }` 泛型参数传递给 `createMiddleware` 函数来使其类型安全。
¥To extend the context inside middleware, use `c.set`. You can make this type-safe by passing a `{ Variables: { yourVariable: YourVariableType } }` generic argument to the `createMiddleware` function.
```ts
import { createMiddleware } from 'hono/factory'
const echoMiddleware = createMiddleware<{
Variables: {
echo: (str: string) => string
}
}>(async (c, next) => {
c.set('echo', (str) => str)
await next()
})
app.get('/echo', echoMiddleware, (c) => {
return c.text(c.var.echo('Hello!'))
})
```
## 第三方中间件
¥Third-party Middleware
内置中间件不依赖于外部模块,但第三方中间件可以依赖于第三方库。因此,有了它们,我们可以制作更复杂的应用。
¥Built-in middleware does not depend on external modules, but third-party middleware can depend on third-party libraries.
So with them, we may make a more complex application.
例如,我们有 GraphQL Server Middleware、Sentry Middleware、Firebase Auth Middleware 等。
¥For example, we have GraphQL Server Middleware, Sentry Middleware, Firebase Auth Middleware, and others.
# 杂项
¥Miscellaneous
## 贡献
¥Contributing
欢迎贡献!你可以通过以下方式做出贡献。
¥Contributions Welcome! You can contribute in the following ways.
* 创建问题 - 提出一个新功能。报告错误。
¥Create an Issue - Propose a new feature. Report a bug.
* 拉取请求 - 修复错误和拼写错误。重构代码。
¥Pull Request - Fix a bug and typo. Refactor the code.
* 创建第三方中间件 - 说明如下。
¥Create third-party middleware - Instruct below.
* 分享 - 在博客、X(Twitter)等上分享你的想法。
¥Share - Share your thoughts on the Blog, X(Twitter), and others.
* 制作你的应用 - 请尝试使用 Hono。
¥Make your application - Please try to use Hono.
有关更多详细信息,请参阅 [贡献指南](https://github.com/honojs/hono/blob/main/docs/CONTRIBUTING.md)。
¥For more details, see [Contribution Guide](https://github.com/honojs/hono/blob/main/docs/CONTRIBUTING.md).
## 赞助
¥Sponsoring
你可以通过 GitHub 赞助计划赞助 Hono 作者。
¥You can sponsor Hono authors via the GitHub sponsor program.
* [GitHub 赞助商 @yusukebe](https://github.com/sponsors/yusukebe)
¥[Sponsor @yusukebe on GitHub Sponsors](https://github.com/sponsors/yusukebe)
* [GitHub 赞助商 @usualoma](https://github.com/sponsors/usualoma)
¥[Sponsor @usualoma on GitHub Sponsors](https://github.com/sponsors/usualoma)
## 其他资源
¥Other Resources
* GitHub 存储库:[https://github.com/honojs](https://github.com/honojs)
¥GitHub repository: [https://github.com/honojs](https://github.com/honojs)
* npm 注册表:[https://www.npmjs.com/package/hono](https://www.npmjs.com/package/hono)
¥npm registry: [https://www.npmjs.com/package/hono](https://www.npmjs.com/package/hono)
* JSR:[https://jsr.io/@hono/hono](https://jsr.io/@hono/hono)
# RPC
RPC 功能允许在服务器和客户端之间共享 API 规范。
¥The RPC feature allows sharing of the API specifications between the server and the client.
首先,从你的服务器代码中导出 `typeof` 你的 Hono 应用(通常称为 `AppType`)— 或者只是你希望客户端可用的路由。
¥First, export the `typeof` your Hono app (commonly called `AppType`)—or just the routes you want available to the client—from your server code.
通过接受 `AppType` 作为通用参数,Hono 客户端可以推断验证器指定的输入类型和返回 `c.json()` 的处理程序发出的输出类型。
¥By accepting `AppType` as a generic parameter, the Hono Client can infer both the input type(s) specified by the Validator, and the output type(s) emitted by handlers returning `c.json()`.
> [!NOTE] 此时,从中间件返回的响应是 [客户端无法推断。](https://github.com/honojs/hono/issues/2719)
>
> ¥[!NOTE]
> At this time, responses returned from middleware are [not inferred by the client.](https://github.com/honojs/hono/issues/2719)
> [!NOTE] 为了使 RPC 类型在 monorepo 中正常工作,在客户端和服务器的 tsconfig.json 文件中,在 `compilerOptions` 中设置 `"strict": true`。[阅读更多。](https://github.com/honojs/hono/issues/2270#issuecomment-2143745118)
>
> ¥[!NOTE]\ For the RPC types to work properly in a monorepo, in both the Client's and Server's tsconfig.json files, set `"strict": true` in `compilerOptions`. [Read more.](https://github.com/honojs/hono/issues/2270#issuecomment-2143745118)
## 服务器
¥Server
你需要在服务器端做的就是编写一个验证器并创建一个变量 `route`。以下示例使用 [Zod Validator](https://github.com/honojs/middleware/tree/main/packages/zod-validator)。
¥All you need to do on the server side is to write a validator and create a variable `route`. The following example uses [Zod Validator](https://github.com/honojs/middleware/tree/main/packages/zod-validator).
```ts{1}
const route = app.post(
'/posts',
zValidator(
'form',
z.object({
title: z.string(),
body: z.string(),
})
),
(c) => {
// ...
return c.json(
{
ok: true,
message: 'Created!',
},
201
)
}
)
```
然后,导出类型以与客户端共享 API 规范。
¥Then, export the type to share the API spec with the Client.
```ts
export type AppType = typeof route
```
## 客户端
¥Client
在客户端,首先导入 `hc` 和 `AppType`。
¥On the Client side, import `hc` and `AppType` first.
```ts
import { AppType } from '.'
import { hc } from 'hono/client'
```
`hc` 是一个用于创建客户端的函数。将 `AppType` 作为泛型传递,并将服务器 URL 指定为参数。
¥`hc` is a function to create a client. Pass `AppType` as Generics and specify the server URL as an argument.
```ts
const client = hc('http://localhost:8787/')
```
调用 `client.{path}.{method}` 并将你希望发送到服务器的数据作为参数传递。
¥Call `client.{path}.{method}` and pass the data you wish to send to the server as an argument.
```ts
const res = await client.posts.$post({
form: {
title: 'Hello',
body: 'Hono is a cool project',
},
})
```
`res` 与 "fetch" 响应兼容。你可以使用 `res.json()` 从服务器检索数据。
¥The `res` is compatible with the "fetch" Response. You can retrieve data from the server with `res.json()`.
```ts
if (res.ok) {
const data = await res.json()
console.log(data.message)
}
```
::: warning 文件上传
目前,客户端不支持文件上传。
¥Currently, the client does not support file uploading.
:::
## 状态代码
¥Status code
如果你在 `c.json()` 中明确指定了状态代码,比如 `200` 或 `404`。它将被添加为传递给客户端的类型。
¥If you explicitly specify the status code, such as `200` or `404`, in `c.json()`. It will be added as a type for passing to the client.
```ts
// server.ts
const app = new Hono().get(
'/posts',
zValidator(
'query',
z.object({
id: z.string(),
})
),
async (c) => {
const { id } = c.req.valid('query')
const post: Post | undefined = await getPost(id)
if (post === undefined) {
return c.json({ error: 'not found' }, 404) // Specify 404
}
return c.json({ post }, 200) // Specify 200
}
)
export type AppType = typeof app
```
你可以通过状态代码获取数据。
¥You can get the data by the status code.
```ts
// client.ts
const client = hc('http://localhost:8787/')
const res = await client.posts.$get({
query: {
id: '123',
},
})
if (res.status === 404) {
const data: { error: string } = await res.json()
console.log(data.error)
}
if (res.ok) {
const data: { post: Post } = await res.json()
console.log(data.post)
}
// { post: Post } | { error: string }
type ResponseType = InferResponseType
// { post: Post }
type ResponseType200 = InferResponseType<
typeof client.posts.$get,
200
>
```
## 未找到
¥Not Found
如果要使用客户端,则不应将 `c.notFound()` 用于未找到响应。无法正确推断客户端从服务器获取的数据。
¥If you want to use a client, you should not use `c.notFound()` for the Not Found response. The data that the client gets from the server cannot be inferred correctly.
```ts
// server.ts
export const routes = new Hono().get(
'/posts',
zValidator(
'query',
z.object({
id: z.string(),
})
),
async (c) => {
const { id } = c.req.valid('query')
const post: Post | undefined = await getPost(id)
if (post === undefined) {
return c.notFound() // ❌️
}
return c.json({ post })
}
)
// client.ts
import { hc } from 'hono/client'
const client = hc('/')
const res = await client.posts[':id'].$get({
param: {
id: '123',
},
})
const data = await res.json() // 🙁 data is unknown
```
请使用 `c.json()` 并指定未找到响应的状态代码。
¥Please use `c.json()` and specify the status code for the Not Found Response.
```ts
export const routes = new Hono().get(
'/posts',
zValidator(
'query',
z.object({
id: z.string(),
})
),
async (c) => {
const { id } = c.req.valid('query')
const post: Post | undefined = await getPost(id)
if (post === undefined) {
return c.json({ error: 'not found' }, 404) // Specify 404
}
return c.json({ post }, 200) // Specify 200
}
)
```
## 路径参数
¥Path parameters
你还可以处理包含路径参数的路由。
¥You can also handle routes that include path parameters.
```ts
const route = app.get(
'/posts/:id',
zValidator(
'query',
z.object({
page: z.string().optional(),
})
),
(c) => {
// ...
return c.json({
title: 'Night',
body: 'Time to sleep',
})
}
)
```
使用 `param` 指定你想要包含在路径中的字符串。
¥Specify the string you want to include in the path with `param`.
```ts
const res = await client.posts[':id'].$get({
param: {
id: '123',
},
query: {},
})
```
## Headers
你可以将标头附加到请求中。
¥You can append the headers to the request.
```ts
const res = await client.search.$get(
{
//...
},
{
headers: {
'X-Custom-Header': 'Here is Hono Client',
'X-User-Agent': 'hc',
},
}
)
```
要为所有请求添加通用标头,请将其作为参数指定给 `hc` 函数。
¥To add a common header to all requests, specify it as an argument to the `hc` function.
```ts
const client = hc('/api', {
headers: {
Authorization: 'Bearer TOKEN',
},
})
```
## `init` 选项
¥`init` option
你可以将 fetch 的 `RequestInit` 对象作为 `init` 选项传递给请求。下面是中止请求的示例。
¥You can pass the fetch's `RequestInit` object to the request as the `init` option. Below is an example of aborting a Request.
```ts
import { hc } from 'hono/client'
const client = hc('http://localhost:8787/')
const abortController = new AbortController()
const res = await client.api.posts.$post(
{
json: {
// Request body
},
},
{
// RequestInit object
init: {
signal: abortController.signal,
},
}
)
// ...
abortController.abort()
```
::: info 信息
由 `init` 定义的 `RequestInit` 对象具有最高优先级。它可用于覆盖由其他选项(如 `body | method | headers`)设置的内容。
¥A `RequestInit` object defined by `init` takes the highest priority. It could be used to overwrite things set by other options like `body | method | headers`.
:::
## `$url()`
你可以使用 `$url()` 获取用于访问端点的 `URL` 对象。
¥You can get a `URL` object for accessing the endpoint by using `$url()`.
::: warning 警告
你必须传入绝对 URL 才能使其工作。传入相对 URL `/` 将导致以下错误。
¥You have to pass in an absolute URL for this to work. Passing in a relative URL `/` will result in the following error.
`Uncaught TypeError: Failed to construct 'URL': Invalid URL`
```ts
// ❌ Will throw error
const client = hc('/')
client.api.post.$url()
// ✅ Will work as expected
const client = hc('http://localhost:8787/')
client.api.post.$url()
```
:::
```ts
const route = app
.get('/api/posts', (c) => c.json({ posts }))
.get('/api/posts/:id', (c) => c.json({ post }))
const client = hc('http://localhost:8787/')
let url = client.api.posts.$url()
console.log(url.pathname) // `/api/posts`
url = client.api.posts[':id'].$url({
param: {
id: '123',
},
})
console.log(url.pathname) // `/api/posts/123`
```
## 自定义 `fetch` 方法
¥Custom `fetch` method
你可以设置自定义 `fetch` 方法。
¥You can set the custom `fetch` method.
在下面的 Cloudflare Worker 示例脚本中,使用了 Service Bindings 的 `fetch` 方法,而不是默认的 `fetch`。
¥In the following example script for Cloudflare Worker, the Service Bindings' `fetch` method is used instead of the default `fetch`.
```toml
# wrangler.toml
services = [
{ binding = "AUTH", service = "auth-service" },
]
```
```ts
// src/client.ts
const client = hc('/', {
fetch: c.env.AUTH.fetch.bind(c.env.AUTH),
})
```
## 推断
¥Infer
使用 `InferRequestType` 和 `InferResponseType` 来了解要请求的对象类型和要返回的对象类型。
¥Use `InferRequestType` and `InferResponseType` to know the type of object to be requested and the type of object to be returned.
```ts
import type { InferRequestType, InferResponseType } from 'hono/client'
// InferRequestType
const $post = client.todo.$post
type ReqType = InferRequestType['form']
// InferResponseType
type ResType = InferResponseType
```
## 使用 SWR
¥Using SWR
你还可以使用 React Hook 库,例如 [SWR](https://swr.nodejs.cn)。
¥You can also use a React Hook library such as [SWR](https://swr.nodejs.cn).
```tsx
import useSWR from 'swr'
import { hc } from 'hono/client'
import type { InferRequestType } from 'hono/client'
import { AppType } from '../functions/api/[[route]]'
const App = () => {
const client = hc('/api')
const $get = client.hello.$get
const fetcher =
(arg: InferRequestType) => async () => {
const res = await $get(arg)
return await res.json()
}
const { data, error, isLoading } = useSWR(
'api-hello',
fetcher({
query: {
name: 'SWR',
},
})
)
if (error) return
failed to load
if (isLoading) return
loading...
return
{data?.message}
}
export default App
```
## 在更大的应用中使用 RPC
¥Using RPC with larger applications
对于较大的应用,例如 [构建更大的应用](/docs/guides/best-practices#building-a-larger-application) 中提到的示例,需要注意推断的类型。一种简单的方法是链接处理程序,以便始终推断类型。
¥In the case of a larger application, such as the example mentioned in [Building a larger application](/docs/guides/best-practices#building-a-larger-application), you need to be careful about the type of inference.
A simple way to do this is to chain the handlers so that the types are always inferred.
```ts
// authors.ts
import { Hono } from 'hono'
const app = new Hono()
.get('/', (c) => c.json('list authors'))
.post('/', (c) => c.json('create an author', 201))
.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default app
```
```ts
// books.ts
import { Hono } from 'hono'
const app = new Hono()
.get('/', (c) => c.json('list books'))
.post('/', (c) => c.json('create a book', 201))
.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default app
```
然后,你可以像平常一样导入子路由,并确保也链接它们的处理程序,因为这是应用的顶层(在本例中),这是我们想要导出的类型。
¥You can then import the sub-routers as you usually would, and make sure you chain their handlers as well, since this is the top level of the app in this case, this is the type we'll want to export.
```ts
// index.ts
import { Hono } from 'hono'
import authors from './authors'
import books from './books'
const app = new Hono()
const routes = app.route('/authors', authors).route('/books', books)
export default app
export type AppType = typeof routes
```
你现在可以使用已注册的 AppType 创建新客户端并像平常一样使用它。
¥You can now create a new client using the registered AppType and use it as you would normally.
## 已知问题
¥Known issues
### IDE 性能
¥IDE performance
使用 RPC 时,路由越多,IDE 就会变得越慢。其中一个主要原因是执行了大量类型实例化来推断应用的类型。
¥When using RPC, the more routes you have, the slower your IDE will become. One of the main reasons for this is that massive amounts of type instantiations are executed to infer the type of your app.
例如,假设你的应用有这样的路由:
¥For example, suppose your app has a route like this:
```ts
// app.ts
export const app = new Hono().get('foo/:id', (c) =>
c.json({ ok: true }, 200)
)
```
Hono 将推断类型如下:
¥Hono will infer the type as follows:
```ts
export const app = Hono().get<
'foo/:id',
'foo/:id',
JSONRespondReturn<{ ok: boolean }, 200>,
BlankInput,
BlankEnv
>('foo/:id', (c) => c.json({ ok: true }, 200))
```
这是单个路由的类型实例化。虽然用户不需要手动编写这些类型参数,这是一件好事,但众所周知类型实例化需要花费很多时间。每次使用应用时,IDE 中使用的 `tsserver` 都会执行这项耗时的任务。如果你有很多路由,这会显著降低你的 IDE 速度。
¥This is a type instantiation for a single route. While the user doesn't need to write these type arguments manually, which is a good thing, it's known that type instantiation takes much time. `tsserver` used in your IDE does this time consuming task every time you use the app. If you have a lot of routes, this can slow down your IDE significantly.
但是,我们有一些技巧可以缓解这个问题。
¥However, we have some tips to mitigate this issue.
#### Hono 版本不匹配
¥Hono version mismatch
如果你的后端与前端分开并且位于不同的目录中,则需要确保 Hono 版本匹配。如果你在后端使用一个 Hono 版本,在前端使用另一个版本,则会遇到诸如 "类型实例化过深且可能无限" 之类的问题。
¥If your backend is separate from the frontend and lives in a different directory, you need to ensure that the Hono versions match. If you use one Hono version on the backend and another on the frontend, you'll run into issues such as "*Type instantiation is excessively deep and possibly infinite*".

#### TypeScript 项目参考
¥TypeScript project references
与 [Hono 版本不匹配](#hono-version-mismatch) 的情况一样,如果你的后端和前端是分开的,你会遇到问题。如果要在前端从后端(例如 `AppType`)访问代码,则需要使用 [项目参考](https://ts.nodejs.cn/docs/handbook/project-references.html)。TypeScript 的项目引用允许一个 TypeScript 代码库访问和使用来自另一个 TypeScript 代码库的代码。(来源:[Hono RPC 和 TypeScript 项目参考](https://catalins.tech/hono-rpc-in-monorepos/))。
¥Like in the case of [Hono version mismatch](#hono-version-mismatch), you'll run into issues if your backend and frontend are separate. If you want to access code from the backend (`AppType`, for example) on the frontend, you need to use [project references](https://ts.nodejs.cn/docs/handbook/project-references.html). TypeScript's project references allow one TypeScript codebase to access and use code from another TypeScript codebase. *(source: [Hono RPC And TypeScript Project References](https://catalins.tech/hono-rpc-in-monorepos/))*.
#### 使用代码前先编译(推荐)
¥Compile your code before using it (recommended)
`tsc` 可以在编译时执行类型实例化等繁重任务!然后,每次使用 `tsserver` 时,它不需要实例化所有类型参数。它将使你的 IDE 更快!
¥`tsc` can do heavy tasks like type instantiation at compile time! Then, `tsserver` doesn't need to instantiate all the type arguments every time you use it. It will make your IDE a lot faster!
编译包括服务器应用在内的客户端可为你提供最佳性能。将以下代码放入你的项目中:
¥Compiling your client including the server app gives you the best performance. Put the following code in your project:
```ts
import { app } from './app'
import { hc } from 'hono/client'
// this is a trick to calculate the type when compiling
const client = hc('')
export type Client = typeof client
export const hcWithType = (...args: Parameters): Client =>
hc(...args)
```
编译后,你可以使用 `hcWithType` 而不是 `hc` 来获取已计算类型的客户端。
¥After compiling, you can use `hcWithType` instead of `hc` to get the client with the type already calculated.
```ts
const client = hcWithType('http://localhost:8787/')
const res = await client.posts.$post({
form: {
title: 'Hello',
body: 'Hono is a cool project',
},
})
```
如果你的项目是 monorepo,则此解决方案确实很合适。使用 [`turborepo`](https://turbo.build/repo/docs) 这样的工具,你可以轻松地分离服务器项目和客户端项目,并通过管理它们之间的依赖获得更好的集成。这是 [工作示例](https://github.com/m-shaka/hono-rpc-perf-tips-example)。
¥If your project is a monorepo, this solution does fit well. Using a tool like [`turborepo`](https://turbo.build/repo/docs), you can easily separate the server project and the client project and get better integration managing dependencies between them. Here is [a working example](https://github.com/m-shaka/hono-rpc-perf-tips-example).
你还可以使用 `concurrently` 或 `npm-run-all` 等工具手动协调构建过程。
¥You can also coordinate your build process manually with tools like `concurrently` or `npm-run-all`.
#### 手动指定类型参数
¥Specify type arguments manually
这有点麻烦,但你可以手动指定类型参数以避免类型实例化。
¥This is a bit cumbersome, but you can specify type arguments manually to avoid type instantiation.
```ts
const app = new Hono().get<'foo/:id'>('foo/:id', (c) =>
c.json({ ok: true }, 200)
)
```
仅指定单一类型参数会对性能产生影响,而如果你有很多路由,则可能会花费大量时间和精力。
¥Specifying just single type argument make a difference in performance, while it may take you a lot of time and effort if you have a lot of routes.
#### 将你的应用和客户端拆分为多个文件
¥Split your app and client into multiple files
如 [在更大的应用中使用 RPC](#using-rpc-with-larger-applications) 中所述,你可以将应用拆分为多个应用。你还可以为每个应用创建一个客户端:
¥As described in [Using RPC with larger applications](#using-rpc-with-larger-applications), you can split your app into multiple apps. You can also create a client for each app:
```ts
// authors-cli.ts
import { app as authorsApp } from './authors'
import { hc } from 'hono/client'
const authorsClient = hc('/authors')
// books-cli.ts
import { app as booksApp } from './books'
import { hc } from 'hono/client'
const booksClient = hc('/books')
```
这样,`tsserver` 就不需要一次实例化所有路由的类型了。
¥This way, `tsserver` doesn't need to instantiate types for all routes at once.
# 测试
¥Testing
测试很重要。实际上,测试 Hono 的应用很容易。创建测试环境的方式因每个运行时而异,但基本步骤相同。在本节中,让我们使用 Cloudflare Workers 和 Jest 进行测试。
¥Testing is important.
In actuality, it is easy to test Hono's applications.
The way to create a test environment differs from each runtime, but the basic steps are the same.
In this section, let's test with Cloudflare Workers and Jest.
## 请求和响应
¥Request and Response
你所要做的就是创建一个请求并将其传递给 Hono 应用以验证响应。你可以使用 `app.request` 的有用方法。
¥All you do is create a Request and pass it to the Hono application to validate the Response.
And, you can use `app.request` the useful method.
::: tip 提示
对于类型测试客户端,请参阅 [测试助手](/docs/helpers/testing)。
¥For a typed test client see the [testing helper](/docs/helpers/testing).
:::
例如,考虑一个提供以下 REST API 的应用。
¥For example, consider an application that provides the following REST API.
```ts
app.get('/posts', (c) => {
return c.text('Many posts')
})
app.post('/posts', (c) => {
return c.json(
{
message: 'Created',
},
201,
{
'X-Custom': 'Thank you',
}
)
})
```
向 `GET /posts` 发出请求并测试响应。
¥Make a request to `GET /posts` and test the response.
```ts
describe('Example', () => {
test('GET /posts', async () => {
const res = await app.request('/posts')
expect(res.status).toBe(200)
expect(await res.text()).toBe('Many posts')
})
})
```
要向 `POST /posts` 发出请求,请执行以下操作。
¥To make a request to `POST /posts`, do the following.
```ts
test('POST /posts', async () => {
const res = await app.request('/posts', {
method: 'POST',
})
expect(res.status).toBe(201)
expect(res.headers.get('X-Custom')).toBe('Thank you')
expect(await res.json()).toEqual({
message: 'Created',
})
})
```
要使用 `JSON` 数据向 `POST /posts` 发出请求,请执行以下操作。
¥To make a request to `POST /posts` with `JSON` data, do the following.
```ts
test('POST /posts', async () => {
const res = await app.request('/posts', {
method: 'POST',
body: JSON.stringify({ message: 'hello hono' }),
headers: new Headers({ 'Content-Type': 'application/json' }),
})
expect(res.status).toBe(201)
expect(res.headers.get('X-Custom')).toBe('Thank you')
expect(await res.json()).toEqual({
message: 'Created',
})
})
```
要使用 `multipart/form-data` 数据向 `POST /posts` 发出请求,请执行以下操作。
¥To make a request to `POST /posts` with `multipart/form-data` data, do the following.
```ts
test('POST /posts', async () => {
const formData = new FormData()
formData.append('message', 'hello')
const res = await app.request('/posts', {
method: 'POST',
body: formData,
})
expect(res.status).toBe(201)
expect(res.headers.get('X-Custom')).toBe('Thank you')
expect(await res.json()).toEqual({
message: 'Created',
})
})
```
你还可以传递 Request 类的实例。
¥You can also pass an instance of the Request class.
```ts
test('POST /posts', async () => {
const req = new Request('http://localhost/posts', {
method: 'POST',
})
const res = await app.request(req)
expect(res.status).toBe(201)
expect(res.headers.get('X-Custom')).toBe('Thank you')
expect(await res.json()).toEqual({
message: 'Created',
})
})
```
通过这种方式,你可以像端到端一样对其进行测试。
¥In this way, you can test it as like an End-to-End.
## Env
要设置 `c.env` 进行测试,你可以将其作为第 3 个参数传递给 `app.request`。这对于模拟 [Cloudflare Workers Bindings](https://hono.nodejs.cn/getting-started/cloudflare-workers#bindings) 之类的值很有用:
¥To set `c.env` for testing, you can pass it as the 3rd parameter to `app.request`. This is useful for mocking values like [Cloudflare Workers Bindings](https://hono.nodejs.cn/getting-started/cloudflare-workers#bindings):
```ts
const MOCK_ENV = {
API_HOST: 'example.com',
DB: {
prepare: () => {
/* mocked D1 */
},
},
}
test('GET /posts', async () => {
const res = await app.request('/posts', {}, MOCK_ENV)
})
```
# 验证
¥Validation
Hono 只提供了一个非常薄的验证器。但是,当与第三方验证器结合使用时,它会非常强大。此外,RPC 功能允许你通过类型与客户端共享 API 规范。
¥Hono provides only a very thin Validator.
But, it can be powerful when combined with a third-party Validator.
In addition, the RPC feature allows you to share API specifications with your clients through types.
## 手动验证器
¥Manual validator
首先,引入一种无需使用第三方验证器即可验证传入值的方法。
¥First, introduce a way to validate incoming values without using the third-party Validator.
从 `hono/validator` 导入 `validator`。
¥Import `validator` from `hono/validator`.
```ts
import { validator } from 'hono/validator'
```
要验证表单数据,请将 `form` 指定为第一个参数,并将回调指定为第二个参数。在回调中,验证值并在最后返回验证后的值。`validator` 可以用作中间件。
¥To validate form data, specify `form` as the first argument and a callback as the second argument.
In the callback, validates the value and return the validated values at the end.
The `validator` can be used as middleware.
```ts
app.post(
'/posts',
validator('form', (value, c) => {
const body = value['body']
if (!body || typeof body !== 'string') {
return c.text('Invalid!', 400)
}
return {
body: body,
}
}),
//...
```
在处理程序中,你可以使用 `c.req.valid('form')` 获取经过验证的值。
¥Within the handler you can get the validated value with `c.req.valid('form')`.
```ts
, (c) => {
const { body } = c.req.valid('form')
// ... do something
return c.json(
{
message: 'Created!',
},
201
)
}
```
除了 `form` 之外,验证目标还包括 `json`、`query`、`header`、`param` 和 `cookie`。
¥Validation targets include `json`, `query`, `header`, `param` and `cookie` in addition to `form`.
::: warning 警告
验证 `json` 时,请求必须包含 `Content-Type: application/json` 标头,否则请求正文将无法解析,你将收到警告。
¥When you validate `json`, the request *must* contain a `Content-Type: application/json` header
otherwise the request body will not be parsed and you will receive a warning.
使用 [`app.request()`](../api/request.md) 进行测试时,设置 `content-type` 标头非常重要。
¥It is important to set the `content-type` header when testing using
[`app.request()`](../api/request.md).
给定一个这样的应用。
¥Given an application like this.
```ts
const app = new Hono()
app.post(
'/testing',
validator('json', (value, c) => {
// pass-through validator
return value
}),
(c) => {
const body = c.req.valid('json')
return c.json(body)
}
)
```
你的测试可以这样写。
¥Your tests can be written like this.
```ts
// ❌ this will not work
const res = await app.request('/testing', {
method: 'POST',
body: JSON.stringify({ key: 'value' }),
})
const data = await res.json()
console.log(data) // undefined
// ✅ this will work
const res = await app.request('/testing', {
method: 'POST',
body: JSON.stringify({ key: 'value' }),
headers: new Headers({ 'Content-Type': 'application/json' }),
})
const data = await res.json()
console.log(data) // { key: 'value' }
```
:::
::: warning 警告
验证 `header` 时,你需要使用小写名称作为键。
¥When you validate `header`, you need to use **lowercase** name as the key.
如果要验证 `Idempotency-Key` 标头,则需要使用 `idempotency-key` 作为密钥。
¥If you want to validate the `Idempotency-Key` header, you need to use `idempotency-key` as the key.
```ts
// ❌ this will not work
app.post(
'/api',
validator('header', (value, c) => {
// idempotencyKey is always undefined
// so this middleware always return 400 as not expected
const idempotencyKey = value['Idempotency-Key']
if (idempotencyKey == undefined || idempotencyKey === '') {
throw HTTPException(400, {
message: 'Idempotency-Key is required',
})
}
return { idempotencyKey }
}),
(c) => {
const { idempotencyKey } = c.req.valid('header')
// ...
}
)
// ✅ this will work
app.post(
'/api',
validator('header', (value, c) => {
// can retrieve the value of the header as expected
const idempotencyKey = value['idempotency-key']
if (idempotencyKey == undefined || idempotencyKey === '') {
throw HTTPException(400, {
message: 'Idempotency-Key is required',
})
}
return { idempotencyKey }
}),
(c) => {
const { idempotencyKey } = c.req.valid('header')
// ...
}
)
```
:::
## 多个验证器
¥Multiple validators
你还可以包含多个验证器来验证请求的不同部分:
¥You can also include multiple validators to validate different parts of request:
```ts
app.post(
'/posts/:id',
validator('param', ...),
validator('query', ...),
validator('json', ...),
(c) => {
//...
}
```
## 使用 Zod
¥With Zod
你可以使用第三方验证器之一 [Zod](https://zod.nodejs.cn)。我们建议使用第三方验证器。
¥You can use [Zod](https://zod.nodejs.cn), one of third-party validators.
We recommend using a third-party validator.
从 Npm 注册表安装。
¥Install from the Npm registry.
::: code-group
```sh [npm]
npm i zod
```
```sh [yarn]
yarn add zod
```
```sh [pnpm]
pnpm add zod
```
```sh [bun]
bun add zod
```
:::
从 `zod` 导入 `z`。
¥Import `z` from `zod`.
```ts
import { z } from 'zod'
```
编写你的模式。
¥Write your schema.
```ts
const schema = z.object({
body: z.string(),
})
```
你可以在回调函数中使用模式进行验证并返回验证后的值。
¥You can use the schema in the callback function for validation and return the validated value.
```ts
const route = app.post(
'/posts',
validator('form', (value, c) => {
const parsed = schema.safeParse(value)
if (!parsed.success) {
return c.text('Invalid!', 401)
}
return parsed.data
}),
(c) => {
const { body } = c.req.valid('form')
// ... do something
return c.json(
{
message: 'Created!',
},
201
)
}
)
```
## Zod 验证器中间件
¥Zod Validator Middleware
你可以使用 [Zod 验证器中间件](https://github.com/honojs/middleware/tree/main/packages/zod-validator) 使其更加容易。
¥You can use the [Zod Validator Middleware](https://github.com/honojs/middleware/tree/main/packages/zod-validator) to make it even easier.
::: code-group
```sh [npm]
npm i @hono/zod-validator
```
```sh [yarn]
yarn add @hono/zod-validator
```
```sh [pnpm]
pnpm add @hono/zod-validator
```
```sh [bun]
bun add @hono/zod-validator
```
:::
并导入 `zValidator`。
¥And import `zValidator`.
```ts
import { zValidator } from '@hono/zod-validator'
```
并按如下方式编写。
¥And write as follows.
```ts
const route = app.post(
'/posts',
zValidator(
'form',
z.object({
body: z.string(),
})
),
(c) => {
const validated = c.req.valid('form')
// ... use your validated data
}
)
```
# 阿里云函数计算
¥Alibaba Cloud Function Compute
[阿里云函数计算](https://www.alibabacloud.com/en/product/function-compute) 是一种完全托管的、事件驱动的计算服务。函数计算允许你专注于编写和上传代码,而无需管理服务器等基础设施。
¥[Alibaba Cloud Function Compute](https://www.alibabacloud.com/en/product/function-compute) is a fully managed, event-driven compute service. Function Compute allows you to focus on writing and uploading code without having to manage infrastructure such as servers.
本指南使用第三方适配器 [rwv/hono-alibaba-cloud-fc3-adapter](https://github.com/rwv/hono-alibaba-cloud-fc3-adapter) 在阿里云函数计算上运行 Hono。
¥This guide uses a third-party adapter [rwv/hono-alibaba-cloud-fc3-adapter](https://github.com/rwv/hono-alibaba-cloud-fc3-adapter) to run Hono on Alibaba Cloud Function Compute.
## 1. 设置
¥ Setup
::: code-group
```sh [npm]
mkdir my-app
cd my-app
npm i hono hono-alibaba-cloud-fc3-adapter
npm i -D @serverless-devs/s esbuild
mkdir src
touch src/index.ts
```
```sh [yarn]
mkdir my-app
cd my-app
yarn add hono hono-alibaba-cloud-fc3-adapter
yarn add -D @serverless-devs/s esbuild
mkdir src
touch src/index.ts
```
```sh [pnpm]
mkdir my-app
cd my-app
pnpm add hono hono-alibaba-cloud-fc3-adapter
pnpm add -D @serverless-devs/s esbuild
mkdir src
touch src/index.ts
```
```sh [bun]
mkdir my-app
cd my-app
bun add hono hono-alibaba-cloud-fc3-adapter
bun add -D esbuild @serverless-devs/s
mkdir src
touch src/index.ts
```
:::
## 2. Hello World
编辑 `src/index.ts`。
¥Edit `src/index.ts`.
```ts
import { Hono } from 'hono'
import { handle } from 'hono-alibaba-cloud-fc3-adapter'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
export const handler = handle(app)
```
## 3. 设置 serverless-devs
¥ Setup serverless-devs
> [serverless-devs](https://github.com/Serverless-Devs/Serverless-Devs) 是一个开源的开放式无服务器开发者平台,致力于为开发者提供强大的工具链系统。通过该平台,开发者不仅可以一键体验多云无服务器产品、快速部署无服务器项目,还可以在无服务器应用的全生命周期中管理项目,并非常简单快捷地将无服务器开发与其他工具/平台结合起来,进一步提高研发和运维的效率。
>
> ¥[serverless-devs](https://github.com/Serverless-Devs/Serverless-Devs) is an open source and open serverless developer platform dedicated to providing developers with a powerful tool chain system. Through this platform, developers can not only experience multi cloud serverless products with one click and rapidly deploy serverless projects, but also manage projects in the whole life cycle of serverless applications, and combine serverless devs with other tools / platforms very simply and quickly to further improve the efficiency of R & D, operation and maintenance.
添加阿里云 AccessKeyID 和 AccessKeySecret
¥Add the Alibaba Cloud AccessKeyID & AccessKeySecret
```sh
npx s config add
# Please select a provider: Alibaba Cloud (alibaba)
# Input your AccessKeyID & AccessKeySecret
```
编辑 `s.yaml`
¥Edit `s.yaml`
```yaml
edition: 3.0.0
name: my-app
access: 'default'
vars:
region: 'us-west-1'
resources:
my-app:
component: fc3
props:
region: ${vars.region}
functionName: 'my-app'
description: 'Hello World by Hono'
runtime: 'nodejs20'
code: ./dist
handler: index.handler
memorySize: 1024
timeout: 300
```
编辑 `package.json` 中的 `scripts` 部分:
¥Edit `scripts` section in `package.json`:
```json
{
"scripts": {
"build": "esbuild --bundle --outfile=./dist/index.js --platform=node --target=node20 ./src/index.ts",
"deploy": "s deploy -y"
}
}
```
## 4. 部署
¥ Deploy
最后,运行命令进行部署:
¥Finally, run the command to deploy:
```sh
npm run build # Compile the TypeScript code to JavaScript
npm run deploy # Deploy the function to Alibaba Cloud Function Compute
```
# AWS Lambda
AWS Lambda 是 Amazon Web Services 提供的无服务器平台。你可以运行代码以响应事件并自动为你管理底层计算资源。
¥AWS Lambda is a serverless platform by Amazon Web Services.
You can run your code in response to events and automatically manages the underlying compute resources for you.
Hono 在 Node.js 18+ 环境中在 AWS Lambda 上运行。
¥Hono works on AWS Lambda with the Node.js 18+ environment.
## 1. 设置
¥ Setup
在 AWS Lambda 上创建应用时,[CDK](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-cdk.html) 可用于设置 IAM 角色、API 网关等功能。
¥When creating the application on AWS Lambda,
[CDK](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-cdk.html)
is useful to setup the functions such as IAM Role, API Gateway, and others.
使用 `cdk` CLI 初始化你的项目。
¥Initialize your project with the `cdk` CLI.
::: code-group
```sh [npm]
mkdir my-app
cd my-app
cdk init app -l typescript
npm i hono
mkdir lambda
touch lambda/index.ts
```
```sh [yarn]
mkdir my-app
cd my-app
cdk init app -l typescript
yarn add hono
mkdir lambda
touch lambda/index.ts
```
```sh [pnpm]
mkdir my-app
cd my-app
cdk init app -l typescript
pnpm add hono
mkdir lambda
touch lambda/index.ts
```
```sh [bun]
mkdir my-app
cd my-app
cdk init app -l typescript
bun add hono
mkdir lambda
touch lambda/index.ts
```
:::
## 2. Hello World
编辑 `lambda/index.ts`。
¥Edit `lambda/index.ts`.
```ts
import { Hono } from 'hono'
import { handle } from 'hono/aws-lambda'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
export const handler = handle(app)
```
## 3. 部署
¥ Deploy
编辑 `lib/cdk-stack.ts`。
¥Edit `lib/cdk-stack.ts`.
```ts
import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
import * as lambda from 'aws-cdk-lib/aws-lambda'
import * as apigw from 'aws-cdk-lib/aws-apigateway'
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'
export class MyAppStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props)
const fn = new NodejsFunction(this, 'lambda', {
entry: 'lambda/index.ts',
handler: 'handler',
runtime: lambda.Runtime.NODEJS_20_X,
})
fn.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.NONE,
})
new apigw.LambdaRestApi(this, 'myapi', {
handler: fn,
})
}
}
```
最后,运行命令进行部署:
¥Finally, run the command to deploy:
```sh
cdk deploy
```
## 提供二进制数据
¥Serve Binary data
Hono 支持二进制数据作为响应。在 Lambda 中,需要使用 base64 编码才能返回二进制数据。将二进制类型设置为 `Content-Type` 标头后,Hono 会自动将数据编码为 base64。
¥Hono supports binary data as a response.
In Lambda, base64 encoding is required to return binary data.
Once binary type is set to `Content-Type` header, Hono automatically encodes data to base64.
```ts
app.get('/binary', async (c) => {
// ...
c.status(200)
c.header('Content-Type', 'image/png') // means binary data
return c.body(buffer) // supports `ArrayBufferLike` type, encoded to base64.
})
```
## 访问 AWS Lambda 对象
¥Access AWS Lambda Object
在 Hono 中,你可以通过绑定 `LambdaEvent`、`LambdaContext` 类型并使用 `c.env` 来访问 AWS Lambda 事件和上下文
¥In Hono, you can access the AWS Lambda Events and Context by binding the `LambdaEvent`, `LambdaContext` type and using `c.env`
```ts
import { Hono } from 'hono'
import type { LambdaEvent, LambdaContext } from 'hono/aws-lambda'
import { handle } from 'hono/aws-lambda'
type Bindings = {
event: LambdaEvent
lambdaContext: LambdaContext
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/aws-lambda-info/', (c) => {
return c.json({
isBase64Encoded: c.env.event.isBase64Encoded,
awsRequestId: c.env.lambdaContext.awsRequestId,
})
})
export const handler = handle(app)
```
## 访问 RequestContext
¥Access RequestContext
在 Hono 中,你可以通过绑定 `LambdaEvent` 类型并使用 `c.env.event.requestContext` 来访问 AWS Lambda 请求上下文。
¥In Hono, you can access the AWS Lambda request context by binding the `LambdaEvent` type and using `c.env.event.requestContext`.
```ts
import { Hono } from 'hono'
import type { LambdaEvent } from 'hono/aws-lambda'
import { handle } from 'hono/aws-lambda'
type Bindings = {
event: LambdaEvent
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/custom-context/', (c) => {
const lambdaContext = c.env.event.requestContext
return c.json(lambdaContext)
})
export const handler = handle(app)
```
### v3.10.0 之前(已弃用)
¥Before v3.10.0 (deprecated)
你可以通过绑定 `ApiGatewayRequestContext` 类型并使用 `c.env.` 来访问 AWS Lambda 请求上下文
¥you can access the AWS Lambda request context by binding the `ApiGatewayRequestContext` type and using `c.env.`
```ts
import { Hono } from 'hono'
import type { ApiGatewayRequestContext } from 'hono/aws-lambda'
import { handle } from 'hono/aws-lambda'
type Bindings = {
requestContext: ApiGatewayRequestContext
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/custom-context/', (c) => {
const lambdaContext = c.env.requestContext
return c.json(lambdaContext)
})
export const handler = handle(app)
```
## Lambda 响应流
¥Lambda response streaming
通过更改 AWS Lambda 的调用模式,你可以实现 [流式响应](https://aws.amazon.com/blogs/compute/introducing-aws-lambda-response-streaming/)。
¥By changing the invocation mode of AWS Lambda, you can achieve [Streaming Response](https://aws.amazon.com/blogs/compute/introducing-aws-lambda-response-streaming/).
```diff
fn.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.NONE,
+ invokeMode: lambda.InvokeMode.RESPONSE_STREAM,
})
```
通常,实现需要使用 awslambda.streamifyResponse 将块写入 NodeJS.WritableStream,但使用 AWS Lambda Adaptor,你可以使用 streamHandle 而不是 handle 来实现 Hono 的传统流式响应。
¥Typically, the implementation requires writing chunks to NodeJS.WritableStream using awslambda.streamifyResponse, but with the AWS Lambda Adaptor, you can achieve the traditional streaming response of Hono by using streamHandle instead of handle.
```ts
import { Hono } from 'hono'
import { streamHandle } from 'hono/aws-lambda'
const app = new Hono()
app.get('/stream', async (c) => {
return streamText(c, async (stream) => {
for (let i = 0; i < 3; i++) {
await stream.writeln(`${i}`)
await stream.sleep(1)
}
})
})
const handler = streamHandle(app)
```
# Azure 函数
¥Azure Functions
[Azure 函数](https://azure.microsoft.com/en-us/products/functions) 是来自 Microsoft Azure 的无服务器平台。你可以运行代码以响应事件,它会自动为你管理底层计算资源。
¥[Azure Functions](https://azure.microsoft.com/en-us/products/functions) is a serverless platform from Microsoft Azure. You can run your code in response to events, and it automatically manages the underlying compute resources for you.
Hono 最初并不是为 Azure Functions 设计的。但使用 [Azure Functions 适配器](https://github.com/Marplex/hono-azurefunc-adapter) 它也可以在它上面运行。
¥Hono was not designed for Azure Functions at first. But with [Azure Functions Adapter](https://github.com/Marplex/hono-azurefunc-adapter) it can run on it as well.
它适用于在 Node.js 18 或更高版本上运行的 Azure Functions V4。
¥It works with Azure Functions **V4** running on Node.js 18 or above.
## 1. 安装 CLI
¥ Install CLI
要创建 Azure 函数,必须先安装 [Azure Functions 核心工具](https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-typescript?pivots=nodejs-model-v4#install-the-azure-functions-core-tools)。
¥To create an Azure Function, you must first install [Azure Functions Core Tools](https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-typescript?pivots=nodejs-model-v4#install-the-azure-functions-core-tools).
在 macOS 上
¥On macOS
```sh
brew tap azure/functions
brew install azure-functions-core-tools@4
```
其他操作系统请点击此链接:
¥Follow this link for other OS:
* [安装 Azure Functions Core 工具 | Microsoft Learn](https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-typescript?pivots=nodejs-model-v4#install-the-azure-functions-core-tools)
¥[Install the Azure Functions Core Tools | Microsoft Learn](https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-typescript?pivots=nodejs-model-v4#install-the-azure-functions-core-tools)
## 2. 设置
¥ Setup
在当前文件夹中创建一个 TypeScript Node.js V4 项目。
¥Create a TypeScript Node.js V4 project in the current folder.
```sh
func init --typescript
```
更改主机的默认路由前缀。将此属性添加到 `host.json` 的根 json 对象:
¥Change the default route prefix of the host. Add this property to the root json object of `host.json`:
```json
"extensions": {
"http": {
"routePrefix": ""
}
}
```
::: info 信息
默认的 Azure Functions 路由前缀为 `/api`。如果你不按照上面那样改的话,一定要确保所有 Hono 路由都以 `/api` 开头
¥The default Azure Functions route prefix is `/api`. If you don't change it as shown above, be sure to start all your Hono routes with `/api`
:::
现在你已准备好安装 Hono 和 Azure Functions Adapter:
¥Now you are ready to install Hono and the Azure Functions Adapter with:
::: code-group
```sh [npm]
npm i @marplex/hono-azurefunc-adapter hono
```
```sh [yarn]
yarn add @marplex/hono-azurefunc-adapter hono
```
```sh [pnpm]
pnpm add @marplex/hono-azurefunc-adapter hono
```
```sh [bun]
bun add @marplex/hono-azurefunc-adapter hono
```
:::
## 3. Hello World
创建 `src/app.ts`:
¥Create `src/app.ts`:
```ts
// src/app.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Azure Functions!'))
export default app
```
创建 `src/functions/httpTrigger.ts`:
¥Create `src/functions/httpTrigger.ts`:
```ts
// src/functions/httpTrigger.ts
import { app } from '@azure/functions'
import { azureHonoHandler } from '@marplex/hono-azurefunc-adapter'
import honoApp from '../app'
app.http('httpTrigger', {
methods: [
//Add all your supported HTTP methods here
'GET',
'POST',
'DELETE',
'PUT',
],
authLevel: 'anonymous',
route: '{*proxy}',
handler: azureHonoHandler(honoApp.fetch),
})
```
## 4. 运行
¥ Run
在本地运行开发服务器。然后,在你的 Web 浏览器中访问 `http://localhost:7071`。
¥Run the development server locally. Then, access `http://localhost:7071` in your Web browser.
::: code-group
```sh [npm]
npm run start
```
```sh [yarn]
yarn start
```
```sh [pnpm]
pnpm start
```
```sh [bun]
bun run start
```
:::
## 5. 部署
¥ Deploy
::: info 信息
在部署到 Azure 之前,你需要在云基础架构中创建一些资源。请访问有关 [为你的函数创建支持 Azure 资源](https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-typescript?pivots=nodejs-model-v4\&tabs=windows%2Cazure-cli%2Cbrowser#create-supporting-azure-resources-for-your-function) 的 Microsoft 文档
¥Before you can deploy to Azure, you need to create some resources in your cloud infrastructure. Please visit the Microsoft documentation on [Create supporting Azure resources for your function](https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-typescript?pivots=nodejs-model-v4\&tabs=windows%2Cazure-cli%2Cbrowser#create-supporting-azure-resources-for-your-function)
:::
构建项目以进行部署:
¥Build the project for deployment:
::: code-group
```sh [npm]
npm run build
```
```sh [yarn]
yarn build
```
```sh [pnpm]
pnpm build
```
```sh [bun]
bun run build
```
:::
将你的项目部署到 Azure Cloud 中的函数应用。将 `` 替换为你的应用名称。
¥Deploy your project to the function app in Azure Cloud. Replace `` with the name of your app.
```sh
func azure functionapp publish
```
# 入门
¥Getting Started
使用 Hono 非常简单。我们可以设置项目、编写代码、使用本地服务器进行开发并快速部署。相同的代码可以在任何运行时上运行,只是入口点不同。让我们看看 Hono 的基本用法。
¥Using Hono is super easy. We can set up the project, write code, develop with a local server, and deploy quickly. The same code will work on any runtime, just with different entry points. Let's look at the basic usage of Hono.
## 入门
¥Starter
每个平台都有入门模板。使用以下 "create-hono" 命令。
¥Starter templates are available for each platform. Use the following "create-hono" command.
::: code-group
```sh [npm]
npm create hono@latest my-app
```
```sh [yarn]
yarn create hono my-app
```
```sh [pnpm]
pnpm create hono@latest my-app
```
```sh [bun]
bun create hono@latest my-app
```
```sh [deno]
deno init --npm hono@latest my-app
```
:::
然后会询问你要使用哪个模板。让我们为这个例子选择 Cloudflare Workers。
¥Then you will be asked which template you would like to use.
Let's select Cloudflare Workers for this example.
```
? Which template do you want to use?
aws-lambda
bun
cloudflare-pages
❯ cloudflare-workers
deno
fastly
nextjs
nodejs
vercel
```
模板将被拉入 `my-app`,因此请转到它并安装依赖。
¥The template will be pulled into `my-app`, so go to it and install the dependencies.
::: code-group
```sh [npm]
cd my-app
npm i
```
```sh [yarn]
cd my-app
yarn
```
```sh [pnpm]
cd my-app
pnpm i
```
```sh [bun]
cd my-app
bun i
```
:::
软件包安装完成后,运行以下命令启动本地服务器。
¥Once the package installation is complete, run the following command to start up a local server.
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm dev
```
```sh [bun]
bun run dev
```
:::
## Hello World
你可以使用 Cloudflare Workers 开发工具 "Wrangler"、Deno、Bun 或其他工具以 TypeScript 编写代码,而无需注意转译。
¥You can write code in TypeScript with the Cloudflare Workers development tool "Wrangler", Deno, Bun, or others without being aware of transpiling.
在 `src/index.ts` 中使用 Hono 编写你的第一个应用。以下示例是入门级 Hono 应用。
¥Write your first application with Hono in `src/index.ts`. The example below is a starter Hono application.
`import` 和最终的 `export default` 部分可能因运行时而异,但所有应用代码将在任何地方运行相同的代码。
¥The `import` and the final `export default` parts may vary from runtime to runtime,
but all of the application code will run the same code everywhere.
```ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => {
return c.text('Hello Hono!')
})
export default app
```
启动开发服务器并使用浏览器访问 `http://localhost:8787`。
¥Start the development server and access `http://localhost:8787` with your browser.
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm dev
```
```sh [bun]
bun run dev
```
:::
## 返回 JSON
¥Return JSON
返回 JSON 也很容易。以下是处理对 `/api/hello` 的 GET 请求并返回 `application/json` 响应的示例。
¥Returning JSON is also easy. The following is an example of handling a GET Request to `/api/hello` and returning an `application/json` Response.
```ts
app.get('/api/hello', (c) => {
return c.json({
ok: true,
message: 'Hello Hono!',
})
})
```
## 请求和响应
¥Request and Response
获取路径参数、URL 查询值并附加响应标头的写法如下。
¥Getting a path parameter, URL query value, and appending a Response header is written as follows.
```ts
app.get('/posts/:id', (c) => {
const page = c.req.query('page')
const id = c.req.param('id')
c.header('X-Message', 'Hi!')
return c.text(`You want to see ${page} of ${id}`)
})
```
我们可以轻松处理 POST、PUT 和 DELETE,而不仅仅是 GET。
¥We can easily handle POST, PUT, and DELETE not only GET.
```ts
app.post('/posts', (c) => c.text('Created!', 201))
app.delete('/posts/:id', (c) =>
c.text(`${c.req.param('id')} is deleted!`)
)
```
## 返回 HTML
¥Return HTML
你可以使用 [html 助手](/docs/helpers/html) 或 [JSX](/docs/guides/jsx) 语法编写 HTML。如果要使用 JSX,请将文件重命名为 `src/index.tsx` 并进行配置(请检查每个运行时,因为它们不同)。下面是使用 JSX 的示例。
¥You can write HTML with [the html Helper](/docs/helpers/html) or using [JSX](/docs/guides/jsx) syntax. If you want to use JSX, rename the file to `src/index.tsx` and configure it (check with each runtime as it is different). Below is an example using JSX.
```tsx
const View = () => {
return (
Hello Hono!
)
}
app.get('/page', (c) => {
return c.html()
})
```
## 返回原始响应
¥Return raw Response
你还可以返回原始 [响应](https://web.nodejs.cn/en-US/docs/Web/API/Response)。
¥You can also return the raw [Response](https://web.nodejs.cn/en-US/docs/Web/API/Response).
```ts
app.get('/', () => {
return new Response('Good morning!')
})
```
## 使用中间件
¥Using Middleware
中间件可以为你完成艰苦的工作。例如,添加基本身份验证。
¥Middleware can do the hard work for you.
For example, add in Basic Authentication.
```ts
import { basicAuth } from 'hono/basic-auth'
// ...
app.use(
'/admin/*',
basicAuth({
username: 'admin',
password: 'secret',
})
)
app.get('/admin', (c) => {
return c.text('You are authorized!')
})
```
有有用的内置中间件,包括 Bearer 和使用 JWT、CORS 和 ETag 的身份验证。Hono 还使用外部库(如 GraphQL Server 和 Firebase Auth)提供第三方中间件。你可以制作自己的中间件。
¥There are useful built-in middleware including Bearer and authentication using JWT, CORS and ETag.
Hono also provides third-party middleware using external libraries such as GraphQL Server and Firebase Auth.
And, you can make your own middleware.
## 适配器
¥Adapter
有用于平台相关函数的适配器,例如处理静态文件或 WebSocket。例如,要在 Cloudflare Workers 中处理 WebSocket,请导入 `hono/cloudflare-workers`。
¥There are Adapters for platform-dependent functions, e.g., handling static files or WebSocket.
For example, to handle WebSocket in Cloudflare Workers, import `hono/cloudflare-workers`.
```ts
import { upgradeWebSocket } from 'hono/cloudflare-workers'
app.get(
'/ws',
upgradeWebSocket((c) => {
// ...
})
)
```
## 下一步
¥Next step
大多数代码可以在任何平台上运行,但每个平台都有指南。例如,如何设置项目或如何部署。请参阅你要用于创建应用的确切平台的页面!
¥Most code will work on any platform, but there are guides for each.
For instance, how to set up projects or how to deploy.
Please see the page for the exact platform you want to use to create your application!
# Bun
[Bun](https://bun.sh) 是另一个 JavaScript 运行时。它不是 Node.js 或 Deno。Bun 包含一个转译编译器,我们可以使用 TypeScript 编写代码。Hono 也适用于 Bun。
¥[Bun](https://bun.sh) is another JavaScript runtime. It's not Node.js or Deno. Bun includes a trans compiler, we can write the code with TypeScript.
Hono also works on Bun.
## 1. 安装 Bun
¥ Install Bun
要安装 `bun` 命令,请按照 [官方网站](https://bun.sh) 中的说明进行操作。
¥To install `bun` command, follow the instruction in [the official web site](https://bun.sh).
## 2. 设置
¥ Setup
### 2.1.设置新项目
¥2.1. Setup a new project
Bun 的入门程序可用。使用 "bun create" 命令启动你的项目。为此示例选择 `bun` 模板。
¥A starter for Bun is available. Start your project with "bun create" command.
Select `bun` template for this example.
```sh
bun create hono@latest my-app
```
移入 my-app 并安装依赖。
¥Move into my-app and install the dependencies.
```sh
cd my-app
bun install
```
### 2.2.设置现有项目
¥2.2. Setup an existing project
在现有的 Bun 项目中,我们只需要通过以下方式在项目根目录中安装 `hono` 依赖:
¥On an existing Bun project, we only need to install `hono` dependencies on the project root directory via
```sh
bun add hono
```
## 3. Hello World
"Hello World" 脚本如下。几乎与在其他平台上编写相同。
¥"Hello World" script is below. Almost the same as writing on other platforms.
```ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Bun!'))
export default app
```
## 4. 运行
¥ Run
运行命令。
¥Run the command.
```sh
bun run dev
```
然后,在你的浏览器中访问 `http://localhost:3000`。
¥Then, access `http://localhost:3000` in your browser.
## 更改端口编号
¥Change port number
你可以通过导出 `port` 来指定端口号。
¥You can specify the port number with exporting the `port`.
```ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Bun!'))
export default app // [!code --]
export default { // [!code ++]
port: 3000, // [!code ++]
fetch: app.fetch, // [!code ++]
} // [!code ++]
```
## 提供静态文件
¥Serve static files
要提供静态文件,请使用从 `hono/bun` 导入的 `serveStatic`。
¥To serve static files, use `serveStatic` imported from `hono/bun`.
```ts
import { serveStatic } from 'hono/bun'
const app = new Hono()
app.use('/static/*', serveStatic({ root: './' }))
app.use('/favicon.ico', serveStatic({ path: './favicon.ico' }))
app.get('/', (c) => c.text('You can access: /static/hello.txt'))
app.get('*', serveStatic({ path: './static/fallback.txt' }))
```
对于上述代码,它将与以下目录结构配合良好。
¥For the above code, it will work well with the following directory structure.
```
./
├── favicon.ico
├── src
└── static
├── demo
│ └── index.html
├── fallback.txt
├── hello.txt
└── images
└── dinotocat.png
```
### `rewriteRequestPath`
如果你想要将 `http://localhost:3000/static/*` 映射到 `./statics`,你可以使用 `rewriteRequestPath` 选项:
¥If you want to map `http://localhost:3000/static/*` to `./statics`, you can use the `rewriteRequestPath` option:
```ts
app.get(
'/static/*',
serveStatic({
root: './',
rewriteRequestPath: (path) =>
path.replace(/^\/static/, '/statics'),
})
)
```
### `mimes`
你可以使用 `mimes` 添加 MIME 类型:
¥You can add MIME types with `mimes`:
```ts
app.get(
'/static/*',
serveStatic({
mimes: {
m3u8: 'application/vnd.apple.mpegurl',
ts: 'video/mp2t',
},
})
)
```
### `onFound`
你可以使用 `onFound` 指定找到请求的文件时的处理:
¥You can specify handling when the requested file is found with `onFound`:
```ts
app.get(
'/static/*',
serveStatic({
// ...
onFound: (_path, c) => {
c.header('Cache-Control', `public, immutable, max-age=31536000`)
},
})
)
```
### `onNotFound`
你可以使用 `onNotFound` 指定未找到请求的文件时的处理:
¥You can specify handling when the requested file is not found with `onNotFound`:
```ts
app.get(
'/static/*',
serveStatic({
onNotFound: (path, c) => {
console.log(`${path} is not found, you access ${c.req.path}`)
},
})
)
```
### `precompressed`
`precompressed` 选项检查是否有扩展名为 `.br` 或 `.gz` 的文件,并根据 `Accept-Encoding` 标头提供这些文件。它优先考虑 Brotli,然后是 Zstd 和 Gzip。如果没有可用的目标,它将提供原始文件。
¥The `precompressed` option checks if files with extensions like `.br` or `.gz` are available and serves them based on the `Accept-Encoding` header. It prioritizes Brotli, then Zstd, and Gzip. If none are available, it serves the original file.
```ts
app.get(
'/static/*',
serveStatic({
precompressed: true,
})
)
```
## 测试
¥Testing
你可以使用 `bun:test` 在 Bun 上进行测试。
¥You can use `bun:test` for testing on Bun.
```ts
import { describe, expect, it } from 'bun:test'
import app from '.'
describe('My first test', () => {
it('Should return 200 Response', async () => {
const req = new Request('http://localhost/')
const res = await app.fetch(req)
expect(res.status).toBe(200)
})
})
```
然后,运行命令。
¥Then, run the command.
```sh
bun test index.test.ts
```
# Cloudflare 页面
¥Cloudflare Pages
[Cloudflare 页面](https://pages.cloudflare.com) 是全栈 Web 应用的边缘平台。它提供由 Cloudflare Workers 提供的静态文件和动态内容。
¥[Cloudflare Pages](https://pages.cloudflare.com) is an edge platform for full-stack web applications.
It serves static files and dynamic content provided by Cloudflare Workers.
Hono 完全支持 Cloudflare Pages。它带来了令人愉悦的开发者体验。Vite 的开发服务器速度很快,使用 Wrangler 部署速度超快。
¥Hono fully supports Cloudflare Pages.
It introduces a delightful developer experience. Vite's dev server is fast, and deploying with Wrangler is super quick.
## 1. 设置
¥ Setup
Cloudflare Pages 的入门程序可用。使用 "create-hono" 命令启动你的项目。为此示例选择 `cloudflare-pages` 模板。
¥A starter for Cloudflare Pages is available.
Start your project with "create-hono" command.
Select `cloudflare-pages` template for this example.
::: code-group
```sh [npm]
npm create hono@latest my-app
```
```sh [yarn]
yarn create hono my-app
```
```sh [pnpm]
pnpm create hono my-app
```
```sh [bun]
bun create hono@latest my-app
```
```sh [deno]
deno init --npm hono my-app
```
:::
移入 `my-app` 并安装依赖。
¥Move into `my-app` and install the dependencies.
::: code-group
```sh [npm]
cd my-app
npm i
```
```sh [yarn]
cd my-app
yarn
```
```sh [pnpm]
cd my-app
pnpm i
```
```sh [bun]
cd my-app
bun i
```
:::
下面是一个基本的目录结构。
¥Below is a basic directory structure.
```text
./
├── package.json
├── public
│ └── static // Put your static files.
│ └── style.css // You can refer to it as `/static/style.css`.
├── src
│ ├── index.tsx // The entry point for server-side.
│ └── renderer.tsx
├── tsconfig.json
└── vite.config.ts
```
## 2. Hello World
按如下方式编辑 `src/index.tsx`:
¥Edit `src/index.tsx` like the following:
```tsx
import { Hono } from 'hono'
import { renderer } from './renderer'
const app = new Hono()
app.get('*', renderer)
app.get('/', (c) => {
return c.render(
Hello, Cloudflare Pages!
)
})
export default app
```
## 3. 运行
¥ Run
在本地运行开发服务器。然后,在你的 Web 浏览器中访问 `http://localhost:5173`。
¥Run the development server locally. Then, access `http://localhost:5173` in your Web browser.
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm dev
```
```sh [bun]
bun run dev
```
:::
## 4. 部署
¥ Deploy
如果你有 Cloudflare 账户,可以部署到 Cloudflare。在 `package.json` 中,需要将 `$npm_execpath` 更改为你选择的包管理器。
¥If you have a Cloudflare account, you can deploy to Cloudflare. In `package.json`, `$npm_execpath` needs to be changed to your package manager of choice.
::: code-group
```sh [npm]
npm run deploy
```
```sh [yarn]
yarn deploy
```
```sh [pnpm]
pnpm run deploy
```
```sh [bun]
bun run deploy
```
:::
### 通过 Cloudflare 仪表板使用 GitHub 部署
¥Deploy via the Cloudflare dashboard with GitHub
1. 登录 [Cloudflare 仪表板](https://dash.cloudflare.com) 并选择你的账户。
¥Log in to the [Cloudflare dashboard](https://dash.cloudflare.com) and select your account.
2. 在 Account Home 中,选择 Workers & Pages > Create application > Pages > Connect to Git。
¥In Account Home, select Workers & Pages > Create application > Pages > Connect to Git.
3. 授权你的 GitHub 账户,然后选择存储库。在设置构建和部署中,提供以下信息:
¥Authorize your GitHub account, and select the repository. In Set up builds and deployments, provide the following information:
| 配置选项 | 值 |
| ---- | --------------- |
| 生产分支 | `main` |
| 构建命令 | `npm run build` |
| 构建目录 | `dist` |
## 绑定
¥Bindings
你可以使用 Cloudflare 绑定,如变量、KV、D1 等。在本节中,让我们使用变量和 KV。
¥You can use Cloudflare Bindings like Variables, KV, D1, and others.
In this section, let's use Variables and KV.
### 创建 `wrangler.toml`
¥Create `wrangler.toml`
首先,为本地绑定创建 `wrangler.toml`:
¥First, create `wrangler.toml` for local Bindings:
```sh
touch wrangler.toml
```
编辑 `wrangler.toml`。使用名称 `MY_NAME` 指定变量。
¥Edit `wrangler.toml`. Specify Variable with the name `MY_NAME`.
```toml
[vars]
MY_NAME = "Hono"
```
### 创建 KV
¥Create KV
接下来,制作 KV。运行以下 `wrangler` 命令:
¥Next, make the KV. Run the following `wrangler` command:
```sh
wrangler kv namespace create MY_KV --preview
```
记下 `preview_id` 作为以下输出:
¥Note down the `preview_id` as the following output:
```
{ binding = "MY_KV", preview_id = "abcdef" }
```
使用 Bindings、`MY_KV` 的名称指定 `preview_id`:
¥Specify `preview_id` with the name of Bindings, `MY_KV`:
```toml
[[kv_namespaces]]
binding = "MY_KV"
id = "abcdef"
```
### 编辑 `vite.config.ts`
¥Edit `vite.config.ts`
编辑 `vite.config.ts`:
¥Edit the `vite.config.ts`:
```ts
import devServer from '@hono/vite-dev-server'
import adapter from '@hono/vite-dev-server/cloudflare'
import build from '@hono/vite-cloudflare-pages'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
devServer({
entry: 'src/index.tsx',
adapter, // Cloudflare Adapter
}),
build(),
],
})
```
### 在你的应用中使用绑定
¥Use Bindings in your application
在你的应用中使用 Variable 和 KV。设置类型。
¥Use Variable and KV in your application. Set the types.
```ts
type Bindings = {
MY_NAME: string
MY_KV: KVNamespace
}
const app = new Hono<{ Bindings: Bindings }>()
```
使用它们:
¥Use them:
```tsx
app.get('/', async (c) => {
await c.env.MY_KV.put('name', c.env.MY_NAME)
const name = await c.env.MY_KV.get('name')
return c.render(
Hello! {name}
)
})
```
### 在生产中
¥In production
对于 Cloudflare Pages,你将使用 `wrangler.toml` 进行本地开发,但对于生产,你将在仪表板中设置绑定。
¥For Cloudflare Pages, you will use `wrangler.toml` for local development, but for production, you will set up Bindings in the dashboard.
## 客户端
¥Client-side
你可以编写客户端脚本并使用 Vite 的功能将其导入你的应用。如果 `/src/client.ts` 是客户端的入口点,只需将其写入脚本标记中即可。此外,`import.meta.env.PROD` 对于检测它是在开发服务器上运行还是在构建阶段运行很有用。
¥You can write client-side scripts and import them into your application using Vite's features.
If `/src/client.ts` is the entry point for the client, simply write it in the script tag.
Additionally, `import.meta.env.PROD` is useful for detecting whether it's running on a dev server or in the build phase.
```tsx
app.get('/', (c) => {
return c.html(
{import.meta.env.PROD ? (
) : (
)}
Hello
)
})
```
为了正确构建脚本,你可以使用示例配置文件 `vite.config.ts`,如下所示。
¥In order to build the script properly, you can use the example config file `vite.config.ts` as shown below.
```ts
import pages from '@hono/vite-cloudflare-pages'
import devServer from '@hono/vite-dev-server'
import { defineConfig } from 'vite'
export default defineConfig(({ mode }) => {
if (mode === 'client') {
return {
build: {
rollupOptions: {
input: './src/client.ts',
output: {
entryFileNames: 'static/client.js',
},
},
},
}
} else {
return {
plugins: [
pages(),
devServer({
entry: 'src/index.tsx',
}),
],
}
}
})
```
你可以运行以下命令来构建服务器和客户端脚本。
¥You can run the following command to build the server and client script.
```sh
vite build --mode client && vite build
```
## Cloudflare 页面中间件
¥Cloudflare Pages Middleware
Cloudflare Pages 使用自己的 [中间件](https://developers.cloudflare.com/pages/functions/middleware/) 系统,与 Hono 的中间件不同。你可以通过将 `onRequest` 导出到名为 `_middleware.ts` 的文件中来启用它,如下所示:
¥Cloudflare Pages uses its own [middleware](https://developers.cloudflare.com/pages/functions/middleware/) system that is different from Hono's middleware. You can enable it by exporting `onRequest` in a file named `_middleware.ts` like this:
```ts
// functions/_middleware.ts
export async function onRequest(pagesContext) {
console.log(`You are accessing ${pagesContext.request.url}`)
return await pagesContext.next()
}
```
使用 `handleMiddleware`,你可以将 Hono 的中间件用作 Cloudflare Pages 中间件。
¥Using `handleMiddleware`, you can use Hono's middleware as Cloudflare Pages middleware.
```ts
// functions/_middleware.ts
import { handleMiddleware } from 'hono/cloudflare-pages'
export const onRequest = handleMiddleware(async (c, next) => {
console.log(`You are accessing ${c.req.url}`)
await next()
})
```
你还可以为 Hono 使用内置和第三方中间件。例如,要添加基本身份验证,你可以使用 [Hono 的基本身份验证中间件](/docs/middleware/builtin/basic-auth)。
¥You can also use built-in and 3rd party middleware for Hono. For example, to add Basic Authentication, you can use [Hono's Basic Authentication Middleware](/docs/middleware/builtin/basic-auth).
```ts
// functions/_middleware.ts
import { handleMiddleware } from 'hono/cloudflare-pages'
import { basicAuth } from 'hono/basic-auth'
export const onRequest = handleMiddleware(
basicAuth({
username: 'hono',
password: 'acoolproject',
})
)
```
如果要应用多个中间件,可以这样写:
¥If you want to apply multiple middleware, you can write it like this:
```ts
import { handleMiddleware } from 'hono/cloudflare-pages'
// ...
export const onRequest = [
handleMiddleware(middleware1),
handleMiddleware(middleware2),
handleMiddleware(middleware3),
]
```
### 访问 `EventContext`
¥Accessing `EventContext`
你可以通过 `handleMiddleware` 中的 `c.env` 访问 [`EventContext`](https://developers.cloudflare.com/pages/functions/api-reference/#eventcontext) 对象。
¥You can access [`EventContext`](https://developers.cloudflare.com/pages/functions/api-reference/#eventcontext) object via `c.env` in `handleMiddleware`.
```ts
// functions/_middleware.ts
import { handleMiddleware } from 'hono/cloudflare-pages'
export const onRequest = [
handleMiddleware(async (c, next) => {
c.env.eventContext.data.user = 'Joe'
await next()
}),
]
```
然后,你可以通过处理程序中的 `c.env.eventContext` 访问数据值:
¥Then, you can access the data value in via `c.env.eventContext` in the handler:
```ts
// functions/api/[[route]].ts
import type { EventContext } from 'hono/cloudflare-pages'
import { handle } from 'hono/cloudflare-pages'
// ...
type Env = {
Bindings: {
eventContext: EventContext
}
}
const app = new Hono()
app.get('/hello', (c) => {
return c.json({
message: `Hello, ${c.env.eventContext.data.user}!`, // 'Joe'
})
})
export const onRequest = handle(app)
```
# Cloudflare Workers
[Cloudflare Workers](https://workers.cloudflare.com) 是 Cloudflare CDN 上的 JavaScript 边缘运行时。
¥[Cloudflare Workers](https://workers.cloudflare.com) is a JavaScript edge runtime on Cloudflare CDN.
你可以在本地开发应用并使用 [Wrangler](https://developers.cloudflare.com/workers/wrangler/) 使用几个命令发布它。Wrangler 包含 trans 编译器,因此我们可以使用 TypeScript 编写代码。
¥You can develop the application locally and publish it with a few commands using [Wrangler](https://developers.cloudflare.com/workers/wrangler/).
Wrangler includes trans compiler, so we can write the code with TypeScript.
让我们用 Hono 为 Cloudflare Workers 制作你的第一个应用。
¥Let’s make your first application for Cloudflare Workers with Hono.
## 1. 设置
¥ Setup
Cloudflare Workers 的入门程序可用。使用 "create-hono" 命令启动你的项目。为此示例选择 `cloudflare-workers` 模板。
¥A starter for Cloudflare Workers is available.
Start your project with "create-hono" command.
Select `cloudflare-workers` template for this example.
::: code-group
```sh [npm]
npm create hono@latest my-app
```
```sh [yarn]
yarn create hono my-app
```
```sh [pnpm]
pnpm create hono my-app
```
```sh [bun]
bun create hono@latest my-app
```
```sh [deno]
deno init --npm hono my-app
```
:::
移至 `my-app` 并安装依赖。
¥Move to `my-app` and install the dependencies.
::: code-group
```sh [npm]
cd my-app
npm i
```
```sh [yarn]
cd my-app
yarn
```
```sh [pnpm]
cd my-app
pnpm i
```
```sh [bun]
cd my-app
bun i
```
:::
## 2. Hello World
按如下方式编辑 `src/index.ts`。
¥Edit `src/index.ts` like below.
```ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Cloudflare Workers!'))
export default app
```
## 3. 运行
¥ Run
在本地运行开发服务器。然后,在你的 Web 浏览器中访问 `http://localhost:8787`。
¥Run the development server locally. Then, access `http://localhost:8787` in your web browser.
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm dev
```
```sh [bun]
bun run dev
```
:::
### 更改端口编号
¥Change port number
如果你需要更改端口号,可以按照此处的说明更新 `wrangler.toml` / `wrangler.json` / `wrangler.jsonc` 文件:[Wrangler 配置](https://developers.cloudflare.com/workers/wrangler/configuration/#local-development-settings)
¥If you need to change the port number you can follow the instructions here to update `wrangler.toml` / `wrangler.json` / `wrangler.jsonc` files:
[Wrangler Configuration](https://developers.cloudflare.com/workers/wrangler/configuration/#local-development-settings)
或者,你可以按照此处的说明设置 CLI 选项:[Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/commands/#dev)
¥Or, you can follow the instructions here to set CLI options:
[Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/commands/#dev)
## 4. 部署
¥ Deploy
如果你有 Cloudflare 账户,可以部署到 Cloudflare。在 `package.json` 中,需要将 `$npm_execpath` 更改为你选择的包管理器。
¥If you have a Cloudflare account, you can deploy to Cloudflare. In `package.json`, `$npm_execpath` needs to be changed to your package manager of choice.
::: code-group
```sh [npm]
npm run deploy
```
```sh [yarn]
yarn deploy
```
```sh [pnpm]
pnpm run deploy
```
```sh [bun]
bun run deploy
```
:::
就这些!
¥That's all!
## 服务工作线程模式或模块工作线程模式
¥Service Worker mode or Module Worker mode
有两种用于编写 Cloudflare Workers 的语法。Module Worker 模式和 Service Worker 模式。使用 Hono,你可以同时使用两种语法编写,但我们建议使用 Module Worker 模式,以便绑定变量本地化。
¥There are two syntaxes for writing the Cloudflare Workers. *Module Worker mode* and *Service Worker mode*. Using Hono, you can write with both syntax, but we recommend using Module Worker mode so that binding variables are localized.
```ts
// Module Worker
export default app
```
```ts
// Service Worker
app.fire()
```
## 将 Hono 与其他事件处理程序一起使用
¥Using Hono with other event handlers
你可以在 Module Worker 模式下将 Hono 与其他事件处理程序(例如 `scheduled`)集成。
¥You can integrate Hono with other event handlers (such as `scheduled`) in *Module Worker mode*.
为此,请将 `app.fetch` 导出为模块的 `fetch` 处理程序,然后根据需要实现其他处理程序:
¥To do this, export `app.fetch` as the module's `fetch` handler, and then implement other handlers as needed:
```ts
const app = new Hono()
export default {
fetch: app.fetch,
scheduled: async (batch, env) => {},
}
```
## 提供静态文件
¥Serve static files
如果你想要提供静态文件,你可以使用 Cloudflare Workers 的 [静态资源功能](https://developers.cloudflare.com/workers/static-assets/)。指定 `wrangler.toml` 中文件的目录:
¥If you want to serve static files, you can use [the Static Assets feature](https://developers.cloudflare.com/workers/static-assets/) of Cloudflare Workers. Specify the directory for the files in `wrangler.toml`:
```toml
assets = { directory = "public" }
```
然后创建 `public` 目录并将文件放在那里。例如,`./public/static/hello.txt` 将作为 `/static/hello.txt` 提供。
¥Then create the `public` directory and place the files there. For instance, `./public/static/hello.txt` will be served as `/static/hello.txt`.
```
.
├── package.json
├── public
│ ├── favicon.ico
│ └── static
│ └── hello.txt
├── src
│ └── index.ts
└── wrangler.toml
```
## 类型
¥Types
如果你想拥有 worker 类型,则必须安装 `@cloudflare/workers-types`。
¥You have to install `@cloudflare/workers-types` if you want to have workers types.
::: code-group
```sh [npm]
npm i --save-dev @cloudflare/workers-types
```
```sh [yarn]
yarn add -D @cloudflare/workers-types
```
```sh [pnpm]
pnpm add -D @cloudflare/workers-types
```
```sh [bun]
bun add --dev @cloudflare/workers-types
```
:::
## 测试
¥Testing
对于测试,我们建议使用 `@cloudflare/vitest-pool-workers`。请参阅 [examples](https://github.com/honojs/examples) 进行设置。
¥For testing, we recommend using `@cloudflare/vitest-pool-workers`.
Refer to [examples](https://github.com/honojs/examples) for setting it up.
如果有以下应用。
¥If there is the application below.
```ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Please test me!'))
```
我们可以使用此代码测试它是否返回 "200 OK" 响应。
¥We can test if it returns "*200 OK*" Response with this code.
```ts
describe('Test the application', () => {
it('Should return 200 response', async () => {
const res = await app.request('http://localhost/')
expect(res.status).toBe(200)
})
})
```
## 绑定
¥Bindings
在 Cloudflare Workers 中,我们可以绑定环境值、KV 命名空间、R2 bucket 或 Durable Object。你可以在 `c.env` 中访问它们。如果你将绑定的 "类型结构" 作为泛型传递给 `Hono`,它将具有类型。
¥In the Cloudflare Workers, we can bind the environment values, KV namespace, R2 bucket, or Durable Object. You can access them in `c.env`. It will have the types if you pass the "*type struct*" for the bindings to the `Hono` as generics.
```ts
type Bindings = {
MY_BUCKET: R2Bucket
USERNAME: string
PASSWORD: string
}
const app = new Hono<{ Bindings: Bindings }>()
// Access to environment values
app.put('/upload/:key', async (c, next) => {
const key = c.req.param('key')
await c.env.MY_BUCKET.put(key, c.req.body)
return c.text(`Put ${key} successfully!`)
})
```
## 在中间件中使用变量
¥Using Variables in Middleware
这是 Module Worker 模式的唯一情况。如果要在中间件中使用变量或秘密变量,例如基本身份验证中间件中的 "username" 或 "password",则需要像下面这样编写。
¥This is the only case for Module Worker mode.
If you want to use Variables or Secret Variables in Middleware, for example, "username" or "password" in Basic Authentication Middleware, you need to write like the following.
```ts
import { basicAuth } from 'hono/basic-auth'
type Bindings = {
USERNAME: string
PASSWORD: string
}
const app = new Hono<{ Bindings: Bindings }>()
//...
app.use('/auth/*', async (c, next) => {
const auth = basicAuth({
username: c.env.USERNAME,
password: c.env.PASSWORD,
})
return auth(c, next)
})
```
同样适用于 Bearer 身份验证中间件、JWT 身份验证或其他。
¥The same is applied to Bearer Authentication Middleware, JWT Authentication, or others.
## 从 GitHub Actions 部署
¥Deploy from GitHub Actions
在通过 CI 将代码部署到 Cloudflare 之前,你需要一个 Cloudflare 令牌。你可以从 [用户 API 令牌](https://dash.cloudflare.com/profile/api-tokens) 进行管理。
¥Before deploying code to Cloudflare via CI, you need a Cloudflare token. You can manage it from [User API Tokens](https://dash.cloudflare.com/profile/api-tokens).
如果是新创建的令牌,请选择编辑 Cloudflare Workers 模板,如果你已经有另一个令牌,请确保该令牌具有相应的权限(否,令牌权限不在 Cloudflare Pages 和 Cloudflare Workers 之间共享)。
¥If it's a newly created token, select the **Edit Cloudflare Workers** template, if you already have another token, make sure the token has the corresponding permissions(No, token permissions are not shared between Cloudflare Pages and Cloudflare Workers).
然后转到你的 GitHub 存储库设置仪表板:`Settings->Secrets and variables->Actions->Repository secrets`,并添加一个名为 `CLOUDFLARE_API_TOKEN` 的新密钥。
¥then go to your GitHub repository settings dashboard: `Settings->Secrets and variables->Actions->Repository secrets`, and add a new secret with the name `CLOUDFLARE_API_TOKEN`.
然后在你的 Hono 项目根文件夹中创建 `.github/workflows/deploy.yml`,粘贴以下代码:
¥then create `.github/workflows/deploy.yml` in your Hono project root folder, paste the following code:
```yml
name: Deploy
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
name: Deploy
steps:
- uses: actions/checkout@v4
- name: Deploy
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
```
然后编辑 `wrangler.toml`,并在 `compatibility_date` 行后添加此代码。
¥then edit `wrangler.toml`, and add this code after `compatibility_date` line.
```toml
main = "src/index.ts"
minify = true
```
一切就绪!现在推送代码并享受它。
¥Everything is ready! Now push the code and enjoy it.
## 本地开发时加载环境
¥Load env when local development
要配置本地开发的环境变量,请在项目根目录中创建 `.dev.vars` 文件。然后像使用普通环境文件一样配置环境变量。
¥To configure the environment variables for local development, create the `.dev.vars` file in the root directory of the project.
Then configure your environment variables as you would with a normal env file.
```
SECRET_KEY=value
API_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
```
> 有关此部分的更多信息,你可以在 Cloudflare 文档中找到:[https://developers.cloudflare.com/workers/wrangler/configuration/#secrets](https://developers.cloudflare.com/workers/wrangler/configuration/#secrets)
>
> ¥For more about this section you can find in the Cloudflare documentation:
> [https://developers.cloudflare.com/workers/wrangler/configuration/#secrets](https://developers.cloudflare.com/workers/wrangler/configuration/#secrets)
然后我们使用 `c.env.*` 获取代码中的环境变量。对于 Cloudflare Workers,必须通过 `c` 获取环境变量,而不是通过 `process.env`。
¥Then we use the `c.env.*` to get the environment variables in our code.\
**For Cloudflare Workers, environment variables must be obtained via `c`, not via `process.env`.**
```ts
type Bindings = {
SECRET_KEY: string
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/env', (c) => {
const SECRET_KEY = c.env.SECRET_KEY
return c.text(SECRET_KEY)
})
```
在将项目部署到 Cloudflare 之前,请记住在 Cloudflare Workers 项目的配置中设置环境变量/密钥。
¥Before you deploy your project to Cloudflare, remember to set the environment variable/secrets in the Cloudflare Workers project's configuration.
> 有关此部分的更多信息,你可以在 Cloudflare 文档中找到:[https://developers.cloudflare.com/workers/configuration/environment-variables/#add-environment-variables-via-the-dashboard](https://developers.cloudflare.com/workers/configuration/environment-variables/#add-environment-variables-via-the-dashboard)
>
> ¥For more about this section you can find in the Cloudflare documentation:
> [https://developers.cloudflare.com/workers/configuration/environment-variables/#add-environment-variables-via-the-dashboard](https://developers.cloudflare.com/workers/configuration/environment-variables/#add-environment-variables-via-the-dashboard)
# Deno
[Deno](https://deno.com/) 是基于 V8 构建的 JavaScript 运行时。它不是 Node.js。Hono 也适用于 Deno。
¥[Deno](https://deno.com/) is a JavaScript runtime built on V8. It's not Node.js.
Hono also works on Deno.
你可以使用 Hono,用 TypeScript 编写代码,使用 `deno` 命令运行应用,然后将其部署到 "Deno 部署"。
¥You can use Hono, write the code with TypeScript, run the application with the `deno` command, and deploy it to "Deno Deploy".
## 1. 安装 Deno
¥ Install Deno
首先,安装 `deno` 命令。请参考 [官方文档](https://docs.deno.com/runtime/manual/getting_started/installation)。
¥First, install `deno` command.
Please refer to [the official document](https://docs.deno.com/runtime/manual/getting_started/installation).
## 2. 设置
¥ Setup
Deno 的启动器可用。使用 "create-hono" 命令启动你的项目。
¥A starter for Deno is available.
Start your project with "create-hono" command.
```sh
deno init --npm hono my-app
```
为此示例选择 `deno` 模板。
¥Select `deno` template for this example.
移入 `my-app`。对于 Deno,你不必明确安装 Hono。
¥Move into `my-app`. For Deno, you don't have to install Hono explicitly.
```sh
cd my-app
```
## 3. Hello World
编写你的第一个应用。
¥Write your first application.
```ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Deno!'))
Deno.serve(app.fetch)
```
## 4. 运行
¥ Run
只需这个命令:
¥Just this command:
```sh
deno task start
```
## 更改端口编号
¥Change port number
你可以通过在 `main.ts` 中更新 `Deno.serve` 的参数来指定端口号:
¥You can specify the port number by updating the arguments of `Deno.serve` in `main.ts`:
```ts
Deno.serve(app.fetch) // [!code --]
Deno.serve({ port: 8787 }, app.fetch) // [!code ++]
```
## 提供静态文件
¥Serve static files
要提供静态文件,请使用从 `hono/middleware.ts` 导入的 `serveStatic`。
¥To serve static files, use `serveStatic` imported from `hono/middleware.ts`.
```ts
import { Hono } from 'hono'
import { serveStatic } from 'hono/deno'
const app = new Hono()
app.use('/static/*', serveStatic({ root: './' }))
app.use('/favicon.ico', serveStatic({ path: './favicon.ico' }))
app.get('/', (c) => c.text('You can access: /static/hello.txt'))
app.get('*', serveStatic({ path: './static/fallback.txt' }))
Deno.serve(app.fetch)
```
对于上述代码,它将与以下目录结构配合良好。
¥For the above code, it will work well with the following directory structure.
```
./
├── favicon.ico
├── index.ts
└── static
├── demo
│ └── index.html
├── fallback.txt
├── hello.txt
└── images
└── dinotocat.png
```
### `rewriteRequestPath`
如果你想要将 `http://localhost:8000/static/*` 映射到 `./statics`,你可以使用 `rewriteRequestPath` 选项:
¥If you want to map `http://localhost:8000/static/*` to `./statics`, you can use the `rewriteRequestPath` option:
```ts
app.get(
'/static/*',
serveStatic({
root: './',
rewriteRequestPath: (path) =>
path.replace(/^\/static/, '/statics'),
})
)
```
### `mimes`
你可以使用 `mimes` 添加 MIME 类型:
¥You can add MIME types with `mimes`:
```ts
app.get(
'/static/*',
serveStatic({
mimes: {
m3u8: 'application/vnd.apple.mpegurl',
ts: 'video/mp2t',
},
})
)
```
### `onFound`
你可以使用 `onFound` 指定找到请求的文件时的处理:
¥You can specify handling when the requested file is found with `onFound`:
```ts
app.get(
'/static/*',
serveStatic({
// ...
onFound: (_path, c) => {
c.header('Cache-Control', `public, immutable, max-age=31536000`)
},
})
)
```
### `onNotFound`
你可以使用 `onNotFound` 指定未找到请求的文件时的处理:
¥You can specify handling when the requested file is not found with `onNotFound`:
```ts
app.get(
'/static/*',
serveStatic({
onNotFound: (path, c) => {
console.log(`${path} is not found, you access ${c.req.path}`)
},
})
)
```
### `precompressed`
`precompressed` 选项检查是否有扩展名为 `.br` 或 `.gz` 的文件,并根据 `Accept-Encoding` 标头提供这些文件。它优先考虑 Brotli,然后是 Zstd 和 Gzip。如果没有可用的目标,它将提供原始文件。
¥The `precompressed` option checks if files with extensions like `.br` or `.gz` are available and serves them based on the `Accept-Encoding` header. It prioritizes Brotli, then Zstd, and Gzip. If none are available, it serves the original file.
```ts
app.get(
'/static/*',
serveStatic({
precompressed: true,
})
)
```
## Deno 部署
¥Deno Deploy
Deno Deploy 是 Deno 的边缘运行时平台。我们可以在 Deno Deploy 上向全世界发布应用。
¥Deno Deploy is an edge runtime platform for Deno.
We can publish the application world widely on Deno Deploy.
Hono 还支持 Deno Deploy。请参考 [官方文档](https://docs.deno.com/deploy/manual/)。
¥Hono also supports Deno Deploy. Please refer to [the official document](https://docs.deno.com/deploy/manual/).
## 测试
¥Testing
在 Deno 上测试应用很容易。你可以使用 `Deno.test` 编写并使用 `assert` 或 `assertEquals`(从 [@std/assert](https://jsr.io/@std/assert) 开始)。
¥Testing the application on Deno is easy.
You can write with `Deno.test` and use `assert` or `assertEquals` from [@std/assert](https://jsr.io/@std/assert).
```sh
deno add jsr:@std/assert
```
```ts
import { Hono } from 'hono'
import { assertEquals } from '@std/assert'
Deno.test('Hello World', async () => {
const app = new Hono()
app.get('/', (c) => c.text('Please test me'))
const res = await app.request('http://localhost/')
assertEquals(res.status, 200)
})
```
然后运行命令:
¥Then run the command:
```sh
deno test hello.ts
```
## `npm:` 说明符
¥`npm:` specifier
`npm:hono` 也可用。你可以通过修复 `deno.json` 来使用它:
¥`npm:hono` is also available. You can use it by fixing the `deno.json`:
```json
{
"imports": {
"hono": "jsr:@hono/hono" // [!code --]
"hono": "npm:hono" // [!code ++]
}
}
```
你可以使用 `npm:hono` 或 `jsr:@hono/hono`。
¥You can use either `npm:hono` or `jsr:@hono/hono`.
如果要将第三方中间件(如 `npm:@hono/zod-validator`)与 TypeScript 类型推断一起使用,则需要使用 `npm:` 说明符。
¥If you want to use Third-party Middleware such as `npm:@hono/zod-validator` with the TypeScript Type inferences, you need to use the `npm:` specifier.
```json
{
"imports": {
"hono": "npm:hono",
"zod": "npm:zod",
"@hono/zod-validator": "npm:@hono/zod-validator"
}
}
```
# Fastly 计算
¥Fastly Compute
[Fastly 的计算](https://www.fastly.com/products/edge-compute) 产品允许我们构建高规模、全球分布的应用并在 Fastly CDN 边缘执行代码。
¥[Fastly's Compute](https://www.fastly.com/products/edge-compute) offering allows us to build high scale, globally distributed applications and execute code at the edge of Fastly CDN.
Hono 也适用于 Fastly Compute。
¥Hono also works on Fastly Compute.
## 1. 安装 CLI
¥ Install CLI
要使用 Fastly Compute,如果你还没有 [创建一个 Fastly 账户](https://www.fastly.com/signup/),则必须有 [创建一个 Fastly 账户](https://www.fastly.com/signup/)。然后,安装 [Fastly CLI](https://github.com/fastly/cli)。
¥To use Fastly Compute, you must [create a Fastly account](https://www.fastly.com/signup/) if you don't already have one.
Then, install [Fastly CLI](https://github.com/fastly/cli).
macOS
```sh
brew install fastly/tap/fastly
```
其他操作系统请点击此链接:
¥Follow this link for other OS:
* [计算服务 | Fastly 开发者中心](https://developer.fastly.com/learning/compute/#download-and-install-the-fastly-cli)
¥[Compute services | Fastly Developer Hub](https://developer.fastly.com/learning/compute/#download-and-install-the-fastly-cli)
## 2. 设置
¥ Setup
Fastly Compute 的启动器可用。使用 "create-hono" 命令启动你的项目。为此示例选择 `fastly` 模板。
¥A starter for Fastly Compute is available.
Start your project with "create-hono" command.
Select `fastly` template for this example.
::: code-group
```sh [npm]
npm create hono@latest my-app
```
```sh [yarn]
yarn create hono my-app
```
```sh [pnpm]
pnpm create hono my-app
```
```sh [bun]
bun create hono@latest my-app
```
```sh [deno]
deno init --npm hono my-app
```
:::
移至 `my-app` 并安装依赖。
¥Move to `my-app` and install the dependencies.
::: code-group
```sh [npm]
cd my-app
npm i
```
```sh [yarn]
cd my-app
yarn
```
```sh [pnpm]
cd my-app
pnpm i
```
```sh [bun]
cd my-app
bun i
```
:::
## 3. Hello World
编辑 `src/index.ts`:
¥Edit `src/index.ts`:
```ts
// src/index.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Fastly!'))
app.fire()
```
## 4. 运行
¥ Run
在本地运行开发服务器。然后,在你的 Web 浏览器中访问 `http://localhost:7676`。
¥Run the development server locally. Then, access `http://localhost:7676` in your Web browser.
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm dev
```
```sh [bun]
bun run dev
```
:::
## 4. 部署
¥ Deploy
::: code-group
```sh [npm]
npm run deploy
```
```sh [yarn]
yarn deploy
```
```sh [pnpm]
pnpm deploy
```
```sh [bun]
bun run deploy
```
:::
就这些!!
¥That's all!!
# Lambda\@Edge
[Lambda@Edge](https://aws.amazon.com/lambda/edge/) 是 Amazon Web Services 的无服务器平台。它允许你在 Amazon CloudFront 的边缘位置运行 Lambda 函数,使你能够自定义 HTTP 请求/响应的行为。
¥[Lambda@Edge](https://aws.amazon.com/lambda/edge/) is a serverless platform by Amazon Web Services. It allows you to run Lambda functions at Amazon CloudFront's edge locations, enabling you to customize behaviors for HTTP requests/responses.
Hono 在 Node.js 18+ 环境中支持 Lambda@Edge。
¥Hono supports Lambda@Edge with the Node.js 18+ environment.
## 1. 设置
¥ Setup
在 Lambda@Edge 上创建应用时,[CDK](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-cdk.html) 可用于设置 CloudFront、IAM 角色、API 网关等功能。
¥When creating the application on Lambda@Edge,
[CDK](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-cdk.html)
is useful to setup the functions such as CloudFront, IAM Role, API Gateway, and others.
使用 `cdk` CLI 初始化你的项目。
¥Initialize your project with the `cdk` CLI.
::: code-group
```sh [npm]
mkdir my-app
cd my-app
cdk init app -l typescript
npm i hono
mkdir lambda
```
```sh [yarn]
mkdir my-app
cd my-app
cdk init app -l typescript
yarn add hono
mkdir lambda
```
```sh [pnpm]
mkdir my-app
cd my-app
cdk init app -l typescript
pnpm add hono
mkdir lambda
```
```sh [bun]
mkdir my-app
cd my-app
cdk init app -l typescript
bun add hono
mkdir lambda
```
:::
## 2. Hello World
编辑 `lambda/index_edge.ts`。
¥Edit `lambda/index_edge.ts`.
```ts
import { Hono } from 'hono'
import { handle } from 'hono/lambda-edge'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono on Lambda@Edge!'))
export const handler = handle(app)
```
## 3. 部署
¥ Deploy
编辑 `bin/my-app.ts`。
¥Edit `bin/my-app.ts`.
```ts
#!/usr/bin/env node
import 'source-map-support/register'
import * as cdk from 'aws-cdk-lib'
import { MyAppStack } from '../lib/my-app-stack'
const app = new cdk.App()
new MyAppStack(app, 'MyAppStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: 'us-east-1',
},
})
```
编辑 `lambda/cdk-stack.ts`。
¥Edit `lambda/cdk-stack.ts`.
```ts
import { Construct } from 'constructs'
import * as cdk from 'aws-cdk-lib'
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'
import * as lambda from 'aws-cdk-lib/aws-lambda'
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'
import * as s3 from 'aws-cdk-lib/aws-s3'
export class MyAppStack extends cdk.Stack {
public readonly edgeFn: lambda.Function
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props)
const edgeFn = new NodejsFunction(this, 'edgeViewer', {
entry: 'lambda/index_edge.ts',
handler: 'handler',
runtime: lambda.Runtime.NODEJS_20_X,
})
// Upload any html
const originBucket = new s3.Bucket(this, 'originBucket')
new cloudfront.Distribution(this, 'Cdn', {
defaultBehavior: {
origin: new origins.S3Origin(originBucket),
edgeLambdas: [
{
functionVersion: edgeFn.currentVersion,
eventType: cloudfront.LambdaEdgeEventType.VIEWER_REQUEST,
},
],
},
})
}
}
```
最后,运行命令进行部署:
¥Finally, run the command to deploy:
```sh
cdk deploy
```
## 回调
¥Callback
如果要添加 Basic Auth 并在验证后继续处理请求,则可以使用 `c.env.callback()`
¥If you want to add Basic Auth and continue with request processing after verification, you can use `c.env.callback()`
```ts
import { Hono } from 'hono'
import { basicAuth } from 'hono/basic-auth'
import type { Callback, CloudFrontRequest } from 'hono/lambda-edge'
import { handle } from 'hono/lambda-edge'
type Bindings = {
callback: Callback
request: CloudFrontRequest
}
const app = new Hono<{ Bindings: Bindings }>()
app.get(
'*',
basicAuth({
username: 'hono',
password: 'acoolproject',
})
)
app.get('/', async (c, next) => {
await next()
c.env.callback(null, c.env.request)
})
export const handler = handle(app)
```
# Netlify
Netlify 提供静态站点托管和无服务器后端服务。[边缘函数](https://docs.netlify.com/edge-functions/overview/) 使我们能够使网页动态化。
¥Netlify provides static site hosting and serverless backend services. [Edge Functions](https://docs.netlify.com/edge-functions/overview/) enables us to make the web pages dynamic.
Edge Functions 支持用 Deno 和 TypeScript 编写,通过 [Netlify CLI](https://docs.netlify.com/cli/get-started/) 可以轻松部署。使用 Hono,你可以为 Netlify Edge Functions 创建应用。
¥Edge Functions support writing in Deno and TypeScript, and deployment is made easy through the [Netlify CLI](https://docs.netlify.com/cli/get-started/). With Hono, you can create the application for Netlify Edge Functions.
## 1. 设置
¥ Setup
Netlify 的启动器可用。使用 "create-hono" 命令启动你的项目。为此示例选择 `netlify` 模板。
¥A starter for Netlify is available.
Start your project with "create-hono" command.
Select `netlify` template for this example.
::: code-group
```sh [npm]
npm create hono@latest my-app
```
```sh [yarn]
yarn create hono my-app
```
```sh [pnpm]
pnpm create hono my-app
```
```sh [bun]
bun create hono@latest my-app
```
```sh [deno]
deno init --npm hono my-app
```
:::
移入 `my-app`。
¥Move into `my-app`.
## 2. Hello World
编辑 `netlify/edge-functions/index.ts`:
¥Edit `netlify/edge-functions/index.ts`:
```ts
import { Hono } from 'jsr:@hono/hono'
import { handle } from 'jsr:@hono/hono/netlify'
const app = new Hono()
app.get('/', (c) => {
return c.text('Hello Hono!')
})
export default handle(app)
```
## 3. 运行
¥ Run
使用 Netlify CLI 运行开发服务器。然后,在你的 Web 浏览器中访问 `http://localhost:8888`。
¥Run the development server with Netlify CLI. Then, access `http://localhost:8888` in your Web browser.
```sh
netlify dev
```
## 4. 部署
¥ Deploy
你可以使用 `netlify deploy` 命令进行部署。
¥You can deploy with a `netlify deploy` command.
```sh
netlify deploy --prod
```
## `Context`
你可以通过 `c.env` 访问 Netlify 的 `Context`:
¥You can access the Netlify's `Context` through `c.env`:
```ts
import { Hono } from 'jsr:@hono/hono'
import { handle } from 'jsr:@hono/hono/netlify'
// Import the type definition
import type { Context } from 'https://edge.netlify.com/'
export type Env = {
Bindings: {
context: Context
}
}
const app = new Hono()
app.get('/country', (c) =>
c.json({
'You are in': c.env.context.geo.country?.name,
})
)
export default handle(app)
```
# Node.js
[Node.js](https://nodejs.cn/) 是一个开源的跨平台 JavaScript 运行时环境。
¥[Node.js](https://nodejs.cn/) is an open-source, cross-platform JavaScript runtime environment.
Hono 最初并不是为 Node.js 设计的。但使用 [Node.js 适配器](https://github.com/honojs/node-server) 它也可以在 Node.js 上运行。
¥Hono was not designed for Node.js at first. But with a [Node.js Adapter](https://github.com/honojs/node-server) it can run on Node.js as well.
::: info 信息
它适用于大于 18.x 的 Node.js 版本。具体所需的 Node.js 版本如下:
¥It works on Node.js versions greater than 18.x. The specific required Node.js versions are as follows:
* 18.x => 18.14.1+
* 19.x => 19.7.0+
* 20.x => 20.0.0+
本质上,你可以简单地使用每个主要版本的最新版本。
¥Essentially, you can simply use the latest version of each major release.
:::
## 1. 设置
¥ Setup
Node.js 的启动器可用。使用 "create-hono" 命令启动你的项目。为此示例选择 `nodejs` 模板。
¥A starter for Node.js is available.
Start your project with "create-hono" command.
Select `nodejs` template for this example.
::: code-group
```sh [npm]
npm create hono@latest my-app
```
```sh [yarn]
yarn create hono my-app
```
```sh [pnpm]
pnpm create hono my-app
```
```sh [bun]
bun create hono@latest my-app
```
```sh [deno]
deno init --npm hono my-app
```
:::
移至 `my-app` 并安装依赖。
¥Move to `my-app` and install the dependencies.
::: code-group
```sh [npm]
cd my-app
npm i
```
```sh [yarn]
cd my-app
yarn
```
```sh [pnpm]
cd my-app
pnpm i
```
```sh [bun]
cd my-app
bun i
```
:::
## 2. Hello World
编辑 `src/index.ts`:
¥Edit `src/index.ts`:
```ts
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Node.js!'))
serve(app)
```
## 3. 运行
¥ Run
在本地运行开发服务器。然后,在你的 Web 浏览器中访问 `http://localhost:3000`。
¥Run the development server locally. Then, access `http://localhost:3000` in your Web browser.
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm dev
```
:::
## 更改端口编号
¥Change port number
你可以使用 `port` 选项指定端口号。
¥You can specify the port number with the `port` option.
```ts
serve({
fetch: app.fetch,
port: 8787,
})
```
## 访问原始 Node.js API
¥Access the raw Node.js APIs
你可以从 `c.env.incoming` 和 `c.env.outgoing` 访问 Node.js API。
¥You can access the Node.js APIs from `c.env.incoming` and `c.env.outgoing`.
```ts
import { Hono } from 'hono'
import { serve, type HttpBindings } from '@hono/node-server'
// or `Http2Bindings` if you use HTTP2
type Bindings = HttpBindings & {
/* ... */
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/', (c) => {
return c.json({
remoteAddress: c.env.incoming.socket.remoteAddress,
})
})
serve(app)
```
## 提供静态文件
¥Serve static files
你可以使用 `serveStatic` 从本地文件系统提供静态文件。例如,假设目录结构如下:
¥You can use `serveStatic` to serve static files from the local file system. For example, suppose the directory structure is as follows:
```sh
./
├── favicon.ico
├── index.ts
└── static
├── hello.txt
└── image.png
```
如果访问路径 `/static/*` 并返回 `./static` 下的文件,你可以编写以下内容:
¥If access to the path `/static/*` comes in and returns a file under `./static`, you can write the following:
```ts
import { serveStatic } from '@hono/node-server/serve-static'
app.use('/static/*', serveStatic({ root: './' }))
```
使用 `path` 选项在目录根中提供 `favicon.ico`:
¥Use the `path` option to serve `favicon.ico` in the directory root:
```ts
app.use('/favicon.ico', serveStatic({ path: './favicon.ico' }))
```
如果访问路径 `/hello.txt` 或 `/image.png` 并返回名为 `./static/hello.txt` 或 `./static/image.png` 的文件,你可以使用以下内容:
¥If access comes to the path `/hello.txt` or `/image.png` and returns a file named `./static/hello.txt` or `./static/image.png`, you can use the following:
```ts
app.use('*', serveStatic({ root: './static' }))
```
### `rewriteRequestPath`
如果你想要将 `http://localhost:3000/static/*` 映射到 `./statics`,你可以使用 `rewriteRequestPath` 选项:
¥If you want to map `http://localhost:3000/static/*` to `./statics`, you can use the `rewriteRequestPath` option:
```ts
app.get(
'/static/*',
serveStatic({
root: './',
rewriteRequestPath: (path) =>
path.replace(/^\/static/, '/statics'),
})
)
```
## http2
你可以在 [Node.js http2 服务器](https://nodejs.cn/api/http2.html) 上运行 hono。
¥You can run hono on a [Node.js http2 Server](https://nodejs.cn/api/http2.html).
### 未加密 http2
¥unencrypted http2
```ts
import { createServer } from 'node:http2'
const server = serve({
fetch: app.fetch,
createServer,
})
```
### 加密 http2
¥encrypted http2
```ts
import { createSecureServer } from 'node:http2'
import { readFileSync } from 'node:fs'
const server = serve({
fetch: app.fetch,
createServer: createSecureServer,
serverOptions: {
key: readFileSync('localhost-privkey.pem'),
cert: readFileSync('localhost-cert.pem'),
},
})
```
## 构建和部署
¥Building & Deployment
完成以下步骤以构建一个简单的 Hono 应用。具有前端框架的应用可能需要使用 [Hono 的 Vite 插件](https://github.com/honojs/vite-plugins)。
¥Complete the following steps to build a simple Hono app. Apps with a front-end framework may need to use [Hono's Vite plugins](https://github.com/honojs/vite-plugins).
1. 将 `"outDir": "./dist"` 添加到 `tsconfig.json` 的 `compilerOptions` 部分。
¥Add `"outDir": "./dist"` to the `compilerOptions` section `tsconfig.json`.
2. 将 `"exclude": ["node_modules"]` 添加到 `tsconfig.json`。
¥Add `"exclude": ["node_modules"]` to `tsconfig.json`.
3. 将 `"build": "tsc"` 添加到 `package.json` 的 `script` 部分。
¥Add `"build": "tsc"` to `script` section of `package.json`.
4. 运行 `npm install typescript --save-dev`。
¥Run `npm install typescript --save-dev`.
5. 将 `"type": "module"` 添加到 `package.json`。
¥Add `"type": "module"` to `package.json`.
6. 运行 `npm run build`!
¥Run `npm run build`!
### Dockerfile
以下是 Dockerfile 的示例。你必须完成上述步骤 1-5,此构建和部署过程才能工作。
¥Here is an example of a Dockerfile. You must complete steps 1-5 above before this build and deployment process will work.
```Dockerfile
FROM node:20-alpine AS base
FROM base AS builder
RUN apk add --no-cache gcompat
WORKDIR /app
COPY package*json tsconfig.json src ./
RUN npm ci && \
npm run build && \
npm prune --production
FROM base AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 hono
COPY --from=builder --chown=hono:nodejs /app/node_modules /app/node_modules
COPY --from=builder --chown=hono:nodejs /app/dist /app/dist
COPY --from=builder --chown=hono:nodejs /app/package.json /app/package.json
USER hono
EXPOSE 3000
CMD ["node", "/app/dist/index.js"]
```
# 服务工作线程
¥Service Worker
[服务工作线程](https://web.nodejs.cn/en-US/docs/Web/API/Service_Worker_API) 是一个在浏览器后台运行的脚本,用于处理缓存和推送通知等任务。使用 Service Worker 适配器,你可以在浏览器中运行使用 Hono 作为 [FetchEvent](https://web.nodejs.cn/en-US/docs/Web/API/FetchEvent) 处理程序制作的应用。
¥[Service Worker](https://web.nodejs.cn/en-US/docs/Web/API/Service_Worker_API) is a script that runs in the background of the browser to handle tasks like caching and push notifications. Using a Service Worker adapter, you can run applications made with Hono as [FetchEvent](https://web.nodejs.cn/en-US/docs/Web/API/FetchEvent) handler within the browser.
此页面显示了使用 [Vite](https://vitejs.dev/) 创建项目的示例。
¥This page shows an example of creating a project using [Vite](https://vitejs.dev/).
## 1. 设置
¥ Setup
首先,创建并移动到你的项目目录:
¥First, create and move to your project directory:
```sh
mkdir my-app
cd my-app
```
为项目创建必要的文件。使用以下内容创建 `package.json` 文件:
¥Create the necessary files for the project. Make a `package.json` file with the following:
```json
{
"name": "my-app",
"private": true,
"scripts": {
"dev": "vite dev"
},
"type": "module"
}
```
类似地,创建一个包含以下内容的 `tsconfig.json` 文件:
¥Similarly, create a `tsconfig.json` file with the following:
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM", "WebWorker"],
"moduleResolution": "bundler"
},
"include": ["./"],
"exclude": ["node_modules"]
}
```
接下来,安装必要的模块。
¥Next, install the necessary modules.
::: code-group
```sh [npm]
npm i hono
npm i -D vite
```
```sh [yarn]
yarn add hono
yarn add -D vite
```
```sh [pnpm]
pnpm add hono
pnpm add -D vite
```
```sh [bun]
bun add hono
bun add -D vite
```
:::
## 2. Hello World
编辑 `index.html`:
¥Edit `index.html`:
```html
Hello World by Service Worker
```
`main.ts` 是一个用于注册 Service Worker 的脚本:
¥`main.ts` is a script to register the Service Worker:
```ts
function register() {
navigator.serviceWorker
.register('/sw.ts', { scope: '/sw', type: 'module' })
.then(
function (_registration) {
console.log('Register Service Worker: Success')
},
function (_error) {
console.log('Register Service Worker: Error')
}
)
}
function start() {
navigator.serviceWorker
.getRegistrations()
.then(function (registrations) {
for (const registration of registrations) {
console.log('Unregister Service Worker')
registration.unregister()
}
register()
})
}
start()
```
在 `sw.ts` 中,使用 Hono 创建一个应用,并使用 Service Worker 适配器的 `handle` 函数将其注册到 `fetch` 事件。这允许 Hono 应用拦截对 `/sw` 的访问。
¥In `sw.ts`, create an application using Hono and register it to the `fetch` event with the Service Worker adapter’s `handle` function. This allows the Hono application to intercept access to `/sw`.
```ts
// To support types
// https://github.com/microsoft/TypeScript/issues/14877
declare const self: ServiceWorkerGlobalScope
import { Hono } from 'hono'
import { handle } from 'hono/service-worker'
const app = new Hono().basePath('/sw')
app.get('/', (c) => c.text('Hello World'))
self.addEventListener('fetch', handle(app))
```
## 3. 运行
¥ Run
启动开发服务器。
¥Start the development server.
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm run dev
```
```sh [bun]
bun run dev
```
:::
默认情况下,开发服务器将在端口 `5173` 上运行。在你的浏览器中访问 `http://localhost:5173/` 以完成 Service Worker 注册。然后,访问 `/sw` 以查看来自 Hono 应用的响应。
¥By default, the development server will run on port `5173`. Access `http://localhost:5173/` in your browser to complete the Service Worker registration. Then, access `/sw` to see the response from the Hono application.
# Supabase 边缘函数
¥Supabase Edge Functions
[Supabase](https://supabase.com/) 是 Firebase 的开源替代品,提供一套类似于 Firebase 功能的工具,包括数据库、身份验证、存储以及现在的无服务器功能。
¥[Supabase](https://supabase.com/) is an open-source alternative to Firebase, offering a suite of tools similar to Firebase's capabilities, including database, authentication, storage, and now, serverless functions.
Supabase Edge Functions 是服务器端 TypeScript 函数,它们分布在全球各地,更靠近你的用户运行以提高性能。这些函数是使用 [Deno](https://deno.com/) 开发的,它带来了许多好处,包括改进的安全性和现代 JavaScript/TypeScript 运行时。
¥Supabase Edge Functions are server-side TypeScript functions that are distributed globally, running closer to your users for improved performance. These functions are developed using [Deno](https://deno.com/), which brings several benefits, including improved security and a modern JavaScript/TypeScript runtime.
以下是开始使用 Supabase Edge Functions 的方法:
¥Here's how you can get started with Supabase Edge Functions:
## 1. 设置
¥ Setup
### 先决条件
¥Prerequisites
在开始之前,请确保你已安装 Supabase CLI。如果你还没有安装,请按照 [官方文档](https://supabase.com/docs/guides/cli/getting-started) 中的说明进行操作。
¥Before you begin, make sure you have the Supabase CLI installed. If you haven't installed it yet, follow the instructions in the [official documentation](https://supabase.com/docs/guides/cli/getting-started).
### 创建新的项目
¥Creating a New Project
1. 打开你的终端或命令提示符。
¥Open your terminal or command prompt.
2. 通过运行以下命令在本地计算机的目录中创建一个新的 Supabase 项目:
¥Create a new Supabase project in a directory on your local machine by running:
```bash
supabase init
```
此命令在当前目录中初始化一个新的 Supabase 项目。
¥This command initializes a new Supabase project in the current directory.
### 添加边缘函数
¥Adding an Edge Function
3. 在你的 Supabase 项目中,创建一个名为 `hello-world` 的新 Edge 函数:
¥Inside your Supabase project, create a new Edge Function named `hello-world`:
```bash
supabase functions new hello-world
```
此命令在你的项目中创建一个具有指定名称的新 Edge 函数。
¥This command creates a new Edge Function with the specified name in your project.
## 2. Hello World
通过修改文件 `supabase/functions/hello-world/index.ts` 来编辑 `hello-world` 函数:
¥Edit the `hello-world` function by modifying the file `supabase/functions/hello-world/index.ts`:
```ts
import { Hono } from 'jsr:@hono/hono'
// change this to your function name
const functionName = 'hello-world'
const app = new Hono().basePath(`/${functionName}`)
app.get('/hello', (c) => c.text('Hello from hono-server!'))
Deno.serve(app.fetch)
```
## 3. 运行
¥ Run
要在本地运行该函数,请使用以下命令:
¥To run the function locally, use the following command:
1. 使用以下命令提供该功能:
¥Use the following command to serve the function:
```bash
supabase start # start the supabase stack
supabase functions serve --no-verify-jwt # start the Functions watcher
```
`--no-verify-jwt` 标志允许你在本地开发期间绕过 JWT 验证。
¥The `--no-verify-jwt` flag allows you to bypass JWT verification during local development.
2. 使用 cURL 或 Postman 向 `http://127.0.0.1:54321/functions/v1/hello-world/hello` 发出 GET 请求:
¥Make a GET request using cURL or Postman to `http://127.0.0.1:54321/functions/v1/hello-world/hello`:
```bash
curl --location 'http://127.0.0.1:54321/functions/v1/hello-world/hello'
```
此请求应返回文本 "来自 hono-server 的问候!"。
¥This request should return the text "Hello from hono-server!".
## 4. 部署
¥ Deploy
你可以使用单个命令在 Supabase 中部署所有 Edge 函数:
¥You can deploy all of your Edge Functions in Supabase with a single command:
```bash
supabase functions deploy
```
或者,你可以通过在部署命令中指定函数名称来部署单个边缘函数:
¥Alternatively, you can deploy individual Edge Functions by specifying the name of the function in the deploy command:
```bash
supabase functions deploy hello-world
```
有关更多部署方法,请访问 Supabase 的 [部署到生产环境](https://supabase.com/docs/guides/functions/deploy) 文档。
¥For more deployment methods, visit the Supabase documentation on [Deploying to Production](https://supabase.com/docs/guides/functions/deploy).
# Vercel
Vercel 是面向前端开发者的平台,提供创新者在灵感迸发时所需的速度和可靠性。本节介绍在 Vercel 上运行的 Next.js。
¥Vercel is the platform for frontend developers, providing the speed and reliability innovators need to create at the moment of inspiration. This section introduces Next.js running on Vercel.
Next.js 是一个灵活的 React 框架,可为你提供构建块以创建快速的 Web 应用。
¥Next.js is a flexible React framework that gives you building blocks to create fast web applications.
在 Next.js 中,[边缘函数](https://vercel.com/docs/concepts/functions/edge-functions) 允许你在 Edge Runtime 上创建动态 API,例如 Vercel。使用 Hono,你可以用与其他运行时相同的语法编写 API 并使用许多中间件。
¥In Next.js, [Edge Functions](https://vercel.com/docs/concepts/functions/edge-functions) allows you to create dynamic APIs on Edge Runtime such as Vercel.
With Hono, you can write APIs with the same syntax as other runtimes and use many middleware.
## 1. 设置
¥ Setup
Next.js 的启动器可用。使用 "create-hono" 命令启动你的项目。为此示例选择 `nextjs` 模板。
¥A starter for Next.js is available.
Start your project with "create-hono" command.
Select `nextjs` template for this example.
::: code-group
```sh [npm]
npm create hono@latest my-app
```
```sh [yarn]
yarn create hono my-app
```
```sh [pnpm]
pnpm create hono my-app
```
```sh [bun]
bun create hono@latest my-app
```
```sh [deno]
deno init --npm hono my-app
```
:::
移入 `my-app` 并安装依赖。
¥Move into `my-app` and install the dependencies.
::: code-group
```sh [npm]
cd my-app
npm i
```
```sh [yarn]
cd my-app
yarn
```
```sh [pnpm]
cd my-app
pnpm i
```
```sh [bun]
cd my-app
bun i
```
:::
## 2. Hello World
如果你使用 App Router,请编辑 `app/api/[[...route]]/route.ts`。请参阅 [支持的 HTTP 方法](https://next.nodejs.cn/docs/app/building-your-application/routing/route-handlers#supported-http-methods) 部分了解更多选项。
¥If you use the App Router, Edit `app/api/[[...route]]/route.ts`. Refer to the [Supported HTTP Methods](https://next.nodejs.cn/docs/app/building-your-application/routing/route-handlers#supported-http-methods) section for more options.
```ts
import { Hono } from 'hono'
import { handle } from 'hono/vercel'
export const runtime = 'edge'
const app = new Hono().basePath('/api')
app.get('/hello', (c) => {
return c.json({
message: 'Hello Next.js!',
})
})
export const GET = handle(app)
export const POST = handle(app)
```
如果你使用 Pages Router,请编辑 `pages/api/[[...route]].ts`。
¥If you use the Pages Router, Edit `pages/api/[[...route]].ts`.
```ts
import { Hono } from 'hono'
import { handle } from 'hono/vercel'
import type { PageConfig } from 'next'
export const config: PageConfig = {
runtime: 'edge',
}
const app = new Hono().basePath('/api')
app.get('/hello', (c) => {
return c.json({
message: 'Hello Next.js!',
})
})
export default handle(app)
```
## 3. 运行
¥ Run
在本地运行开发服务器。然后,在你的 Web 浏览器中访问 `http://localhost:3000`。
¥Run the development server locally. Then, access `http://localhost:3000` in your Web browser.
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm dev
```
```sh [bun]
bun run dev
```
:::
现在,`/api/hello` 只返回 JSON,但如果你构建 React UI,则可以使用 Hono 创建一个全栈应用。
¥Now, `/api/hello` just returns JSON, but if you build React UIs, you can create a full-stack application with Hono.
## 4. 部署
¥ Deploy
如果你有 Vercel 账户,可以通过链接 Git 仓库进行部署。
¥If you have a Vercel account, you can deploy by linking the Git repository.
## Node.js
你还可以在 Node.js 运行时上运行 Next.js 上的 Hono。
¥You can also run Hono on Next.js running on the Node.js runtime.
### 应用路由
¥App Router
对于 App Router,你只需在路由处理程序中将运行时设置为 `nodejs`:
¥For the App Router, you can simply set the runtime to `nodejs` in your route handler:
```ts
import { Hono } from 'hono'
import { handle } from 'hono/vercel'
export const runtime = 'nodejs'
const app = new Hono().basePath('/api')
app.get('/hello', (c) => {
return c.json({
message: 'Hello from Hono!',
})
})
export const GET = handle(app)
export const POST = handle(app)
```
### 页面路由
¥Pages Router
对于 Pages Router,你需要先安装 Node.js 适配器:
¥For the Pages Router, you'll need to install the Node.js adapter first:
::: code-group
```sh [npm]
npm i @hono/node-server
```
```sh [yarn]
yarn add @hono/node-server
```
```sh [pnpm]
pnpm add @hono/node-server
```
```sh [bun]
bun add @hono/node-server
```
:::
然后,你可以使用从 `@hono/node-server/vercel` 导入的 `handle` 函数:
¥Then, you can utilize the `handle` function imported from `@hono/node-server/vercel`:
```ts
import { Hono } from 'hono'
import { handle } from '@hono/node-server/vercel'
import type { PageConfig } from 'next'
export const config: PageConfig = {
api: {
bodyParser: false,
},
}
const app = new Hono().basePath('/api')
app.get('/hello', (c) => {
return c.json({
message: 'Hello from Hono!',
})
})
export default handle(app)
```
为了使其与 Pages Router 配合使用,重要的是通过在项目仪表板或 `.env` 文件中设置环境变量来禁用 Vercel Node.js 助手:
¥In order for this to work with the Pages Router, it's important to disable Vercel Node.js helpers by setting up an environment variable in your project dashboard or in your `.env` file:
```text
NODEJS_HELPERS=0
```
# 基准
¥Benchmarks
基准只是基准,但对我们很重要。
¥Benchmarks are only benchmarks, but they are important to us.
## 路由
¥Routers
我们测量了一堆 JavaScript 路由的速度。例如,`find-my-way` 是 Fastify 内部使用的非常快的路由。
¥We measured the speeds of a bunch of JavaScript routers.
For example, `find-my-way` is a very fast router used inside Fastify.
* @medley/router
* find-my-way
* koa-tree-router
* trek-router
* express(包括处理)
¥express (includes handling)
* koa-router
首先,我们向每个路由注册了以下路由。这些与现实世界中使用的类似。
¥First, we registered the following routing to each of our routers.
These are similar to those used in the real world.
```ts twoslash
interface Route {
method: string
path: string
}
// ---cut---
export const routes: Route[] = [
{ method: 'GET', path: '/user' },
{ method: 'GET', path: '/user/comments' },
{ method: 'GET', path: '/user/avatar' },
{ method: 'GET', path: '/user/lookup/username/:username' },
{ method: 'GET', path: '/user/lookup/email/:address' },
{ method: 'GET', path: '/event/:id' },
{ method: 'GET', path: '/event/:id/comments' },
{ method: 'POST', path: '/event/:id/comment' },
{ method: 'GET', path: '/map/:location/events' },
{ method: 'GET', path: '/status' },
{ method: 'GET', path: '/very/deeply/nested/route/hello/there' },
{ method: 'GET', path: '/static/*' },
]
```
然后我们像下面这样将请求发送到端点。
¥Then we sent the Request to the endpoints like below.
```ts twoslash
interface Route {
method: string
path: string
}
// ---cut---
const routes: (Route & { name: string })[] = [
{
name: 'short static',
method: 'GET',
path: '/user',
},
{
name: 'static with same radix',
method: 'GET',
path: '/user/comments',
},
{
name: 'dynamic route',
method: 'GET',
path: '/user/lookup/username/hey',
},
{
name: 'mixed static dynamic',
method: 'GET',
path: '/event/abcd1234/comments',
},
{
name: 'post',
method: 'POST',
path: '/event/abcd1234/comment',
},
{
name: 'long static',
method: 'GET',
path: '/very/deeply/nested/route/hello/there',
},
{
name: 'wildcard',
method: 'GET',
path: '/static/index.html',
},
]
```
让我们看看结果。
¥Let's see the results.
### 在 Node.js 上
¥On Node.js
以下截图显示了 Node.js 上的结果。
¥The following screenshots show the results on Node.js.








### 在 Bun 上
¥On Bun
以下截图显示了 Bun 上的结果。
¥The following screenshots show the results on Bun.








## Cloudflare Workers
与 Cloudflare Workers 的其他路由相比,Hono 是最快的。
¥**Hono is the fastest**, compared to other routers for Cloudflare Workers.
* 机器:Apple MacBook Pro,32 GiB,M1 Pro
¥Machine: Apple MacBook Pro, 32 GiB, M1 Pro
* 脚本:[benchmarks/handle-event](https://github.com/honojs/hono/tree/main/benchmarks/handle-event)
¥Scripts: [benchmarks/handle-event](https://github.com/honojs/hono/tree/main/benchmarks/handle-event)
```
Hono x 402,820 ops/sec ±4.78% (80 runs sampled)
itty-router x 212,598 ops/sec ±3.11% (87 runs sampled)
sunder x 297,036 ops/sec ±4.76% (77 runs sampled)
worktop x 197,345 ops/sec ±2.40% (88 runs sampled)
Fastest is Hono
✨ Done in 28.06s.
```
## Deno
与 Deno 的其他框架相比,Hono 是最快的。
¥**Hono is the fastest**, compared to other frameworks for Deno.
* 机器:Apple MacBook Pro,32 GiB,M1 Pro,Deno v1.22.0
¥Machine: Apple MacBook Pro, 32 GiB, M1 Pro, Deno v1.22.0
* 脚本:[benchmarks/deno](https://github.com/honojs/hono/tree/main/benchmarks/deno)
¥Scripts: [benchmarks/deno](https://github.com/honojs/hono/tree/main/benchmarks/deno)
* 方法:`bombardier --fasthttp -d 10s -c 100 'http://localhost:8000/user/lookup/username/foo'`
¥Method: `bombardier --fasthttp -d 10s -c 100 'http://localhost:8000/user/lookup/username/foo'`
| 框架 | 版本 | 结果 |
| -------- | :----------: | ---------------: |
| **Hono** | 3.0.0 | **请求数/秒:136112** |
| 快速 | 4.0.0-beta.1 | 请求数/秒:103214 |
| Megalo | 0.3.0 | 请求数/秒:64597 |
| 更快 | 5.7 | 请求数/秒:54801 |
| oak | 10.5.1 | 请求数/秒:43326 |
| opine | 2.2.0 | 请求数/秒:30700 |
另一个基准测试结果:[denosaurs/bench](https://github.com/denosaurs/bench)
¥Another benchmark result: [denosaurs/bench](https://github.com/denosaurs/bench)
## Bun
Hono 是 Bun 最快的框架之一。你可以在下面看到它。
¥Hono is one of the fastest frameworks for Bun.
You can see it below.
* [SaltyAom/bun-http-framework-benchmark](https://github.com/SaltyAom/bun-http-framework-benchmark)
# 开发者体验
¥Developer Experience
要创建出色的应用,我们需要出色的开发经验。幸运的是,我们可以在 TypeScript 中为 Cloudflare Workers、Deno 和 Bun 编写应用,而无需转换为 JavaScript。Hono 用 TypeScript 编写,可以使应用类型安全。
¥To create a great application, we need great development experience.
Fortunately, we can write applications for Cloudflare Workers, Deno, and Bun in TypeScript without having the transpiling to JavaScript.
Hono is written in TypeScript and can make applications Type-safe.
# 中间件
¥Middleware
我们将返回 `Response` 的原语称为 "处理程序"。"中间件" 在 Handler 之前和之后执行,并处理 `Request` 和 `Response`。它就像一个洋葱结构。
¥We call the primitive that returns `Response` as "Handler".
"Middleware" is executed before and after the Handler and handles the `Request` and `Response`.
It's like an onion structure.

例如,我们可以编写中间件来添加 "X-Response-Time" 标头,如下所示。
¥For example, we can write the middleware to add the "X-Response-Time" header as follows.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.use(async (c, next) => {
const start = Date.now()
await next()
const end = Date.now()
c.res.headers.set('X-Response-Time', `${end - start}`)
})
```
使用这种简单的方法,我们可以编写自己的自定义中间件,并且可以使用内置或第三方中间件。
¥With this simple method, we can write our own custom middleware and we can use the built-in or third party middleware.
# 理念
¥Philosophy
在本节中,我们将讨论 Hono 的概念或哲学。
¥In this section, we talk about the concept, or philosophy, of Hono.
## 动机
¥Motivation
起初,我只是想在 Cloudflare Workers 上创建一个 Web 应用。但是,没有适用于 Cloudflare Workers 的好框架。所以,我开始构建 Hono。
¥At first, I just wanted to create a web application on Cloudflare Workers.
But, there was no good framework that works on Cloudflare Workers.
So, I started building Hono.
我认为这是一个学习如何使用 Trie 树构建路由的好机会。然后一个朋友出现了,他带来了名为 "RegExpRouter" 的超快路由。我还有一个朋友创建了 Basic 身份验证中间件。
¥I thought it would be a good opportunity to learn how to build a router using Trie trees.
Then a friend showed up with ultra crazy fast router called "RegExpRouter".
And I also have a friend who created the Basic authentication middleware.
仅使用 Web 标准 API,我们就可以使其在 Deno 和 Bun 上运行。当人们问 "Bun 有 Express 吗?" 时,我们可以回答 "没有,但是有 Hono"。(尽管 Express 现在可以在 Bun 上运行。)
¥Using only Web Standard APIs, we could make it work on Deno and Bun. When people asked "is there Express for Bun?", we could answer, "no, but there is Hono".
(Although Express works on Bun now.)
我们也有朋友制作 GraphQL 服务器、Firebase 身份验证和 Sentry 中间件。我们还有一个 Node.js 适配器。一个生态系统已经出现。
¥We also have friends who make GraphQL servers, Firebase authentication, and Sentry middleware.
And, we also have a Node.js adapter.
An ecosystem has sprung up.
换句话说,Hono 速度非常快,使很多事情成为可能,并且可以在任何地方工作。我们可以想象 Hono 可以成为 Web 标准的标准。
¥In other words, Hono is damn fast, makes a lot of things possible, and works anywhere.
We might imagine that Hono could become the **Standard for Web Standards**.
# 路由
¥Routers
路由是 Hono 最重要的功能。
¥The routers are the most important features for Hono.
Hono 有五个路由。
¥Hono has five routers.
## RegExpRouter
RegExpRouter 是 JavaScript 世界中最快的路由。
¥**RegExpRouter** is the fastest router in the JavaScript world.
虽然这被称为 "RegExp",但它不是使用 [path-to-regexp](https://github.com/pillarjs/path-to-regexp) 的 Express 式实现。它们使用线性循环。因此,将对所有路由执行正则表达式匹配,并且随着路由数量的增加,性能会下降。
¥Although this is called "RegExp" it is not an Express-like implementation using [path-to-regexp](https://github.com/pillarjs/path-to-regexp).
They are using linear loops.
Therefore, regular expression matching will be performed for all routes and the performance will be degraded as you have more routes.

Hono 的 RegExpRouter 将路由模式转换为 "一个大型正则表达式"。然后它可以通过一次匹配获得结果。
¥Hono's RegExpRouter turns the route pattern into "one large regular expression".
Then it can get the result with one-time matching.

这在大多数情况下比使用基于树的算法(如 radix-tree)的方法更快。
¥This works faster than methods that use tree-based algorithms such as radix-tree in most cases.
## TrieRouter
TrieRouter 是使用 Trie-tree 算法的路由。它不使用与 RegExpRouter 相同的线性循环。
¥**TrieRouter** is the router using the Trie-tree algorithm.
It does not use linear loops as same as RegExpRouter.

此路由不如 RegExpRouter 快,但比 Express 路由快得多。TrieRouter 支持所有模式,但 RegExpRouter 不支持。
¥This router is not as fast as the RegExpRouter, but it is much faster than the Express router.
TrieRouter supports all patterns though RegExpRouter does not.
## SmartRouter
RegExpRouter 不支持所有路由模式。因此,它通常与支持所有模式的另一个路由结合使用。
¥RegExpRouter doesn't support all routing patterns.
Therefore, it's usually used in combination with another router that does support all patterns.
SmartRouter 将通过从已注册的路由推断来选择最佳路由。Hono 默认使用 SmartRouter 和两个路由:
¥**SmartRouter** will select the best router by inferring from the registered routers.
Hono uses SmartRouter and the two routers by default:
```ts
// Inside the core of Hono.
readonly defaultRouter: Router = new SmartRouter({
routers: [new RegExpRouter(), new TrieRouter()],
})
```
当应用启动时,SmartRouter 会根据路由检测最快的路由并继续使用它。
¥When the application starts, SmartRouter detects the fastest router based on routing and continues to use it.
## LinearRouter
RegExpRouter 速度很快,但路由注册阶段可能会稍微慢一些。因此,它不适合每次请求时都初始化的环境。
¥RegExpRouter is fast, but the route registration phase can be slightly slow.
So, it's not suitable for an environment that initializes with every request.
LinearRouter 针对 "一次" 情况进行了优化。路由注册比使用 RegExpRouter 快得多,因为它使用线性方法添加路由而无需编译字符串。
¥**LinearRouter** is optimized for "one shot" situations.
Route registration is significantly faster than with RegExpRouter because it adds the route without compiling strings, using a linear approach.
以下是基准测试结果之一,其中包括路由注册阶段。
¥The following is one of the benchmark results, which includes the route registration phase.
```console
• GET /user/lookup/username/hey
----------------------------------------------------- -----------------------------
LinearRouter 1.82 µs/iter (1.7 µs … 2.04 µs) 1.84 µs 2.04 µs 2.04 µs
MedleyRouter 4.44 µs/iter (4.34 µs … 4.54 µs) 4.48 µs 4.54 µs 4.54 µs
FindMyWay 60.36 µs/iter (45.5 µs … 1.9 ms) 59.88 µs 78.13 µs 82.92 µs
KoaTreeRouter 3.81 µs/iter (3.73 µs … 3.87 µs) 3.84 µs 3.87 µs 3.87 µs
TrekRouter 5.84 µs/iter (5.75 µs … 6.04 µs) 5.86 µs 6.04 µs 6.04 µs
summary for GET /user/lookup/username/hey
LinearRouter
2.1x faster than KoaTreeRouter
2.45x faster than MedleyRouter
3.21x faster than TrekRouter
33.24x faster than FindMyWay
```
对于 Fastly Compute 等情况,最好使用带有 `hono/quick` 预设的 LinearRouter。
¥For situations like Fastly Compute, it's better to use LinearRouter with the `hono/quick` preset.
## PatternRouter
PatternRouter 是 Hono 路由中最小的路由。
¥**PatternRouter** is the smallest router among Hono's routers.
虽然 Hono 已经很紧凑,但如果你需要使其更小以适应资源有限的环境,则可以使用 PatternRouter。
¥While Hono is already compact, if you need to make it even smaller for an environment with limited resources, you can use PatternRouter.
仅使用 PatternRouter 的应用大小不到 15KB。
¥An application using only PatternRouter is under 15KB in size.
```console
$ npx wrangler deploy --minify ./src/index.ts
⛅️ wrangler 3.20.0
-------------------
Total Upload: 14.68 KiB / gzip: 5.38 KiB
```
# Hono Stacks
Hono 使简单的事情变得简单,使困难的事情变得简单。它不仅适用于返回 JSON。但它也非常适合构建包括 REST API 服务器和客户端在内的全栈应用。
¥Hono makes easy things easy and hard things easy.
It is suitable for not just only returning JSON.
But it's also great for building the full-stack application including REST API servers and the client.
## RPC
Hono 的 RPC 功能允许你在几乎不更改代码的情况下共享 API 规范。`hc` 生成的客户端将读取规范并访问端点类型安全。
¥Hono's RPC feature allows you to share API specs with little change to your code.
The client generated by `hc` will read the spec and access the endpoint Type-Safety.
以下库使之成为可能。
¥The following libraries make it possible.
* Hono - API 服务器
¥Hono - API Server
* [Zod](https://zod.nodejs.cn) - 验证器
¥[Zod](https://zod.nodejs.cn) - Validator
* [Zod 验证器中间件](https://github.com/honojs/middleware/tree/main/packages/zod-validator)
¥[Zod Validator Middleware](https://github.com/honojs/middleware/tree/main/packages/zod-validator)
* `hc` - HTTP 客户端
¥`hc` - HTTP Client
我们可以将这些组件的集合称为 Hono Stack。现在让我们用它创建一个 API 服务器和一个客户端。
¥We can call the set of these components the **Hono Stack**.
Now let's create an API server and a client with it.
## 编写 API
¥Writing API
首先,编写一个接收 GET 请求并返回 JSON 的端点。
¥First, write an endpoint that receives a GET request and returns JSON.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
app.get('/hello', (c) => {
return c.json({
message: `Hello!`,
})
})
```
## 使用 Zod 进行验证
¥Validation with Zod
使用 Zod 验证以接收查询参数的值。
¥Validate with Zod to receive the value of the query parameter.

```ts
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
app.get(
'/hello',
zValidator(
'query',
z.object({
name: z.string(),
})
),
(c) => {
const { name } = c.req.valid('query')
return c.json({
message: `Hello! ${name}`,
})
}
)
```
## 共享类型
¥Sharing the Types
要发出端点规范,请导出其类型。
¥To emit an endpoint specification, export its type.
::: warning 警告
为了使 RPC 正确推断路由,必须链接所有包含的方法,并且必须从声明的变量中推断出端点或应用类型。有关更多信息,请参阅 [RPC 的最佳实践](https://hono.nodejs.cn/docs/guides/best-practices#if-you-want-to-use-rpc-features)。
¥For the RPC to infer routes correctly, all inlcuded methods must be chained, and the endpoint or app type must be inferred from a declared variable. For more, see [Best Practices for RPC](https://hono.nodejs.cn/docs/guides/best-practices#if-you-want-to-use-rpc-features).
:::
```ts{1,17}
const route = app.get(
'/hello',
zValidator(
'query',
z.object({
name: z.string(),
})
),
(c) => {
const { name } = c.req.valid('query')
return c.json({
message: `Hello! ${name}`,
})
}
)
export type AppType = typeof route
```
## 客户端
¥Client
下一步。客户端实现。通过将 AppType 类型作为泛型传递给 `hc` 来创建客户端对象。然后,神奇的是,完成工作并建议端点路径和请求类型。
¥Next. The client-side implementation.
Create a client object by passing the AppType type to `hc` as generics.
Then, magically, completion works and the endpoint path and request type are suggested.

```ts
import { AppType } from './server'
import { hc } from 'hono/client'
const client = hc('/api')
const res = await client.hello.$get({
query: {
name: 'Hono',
},
})
```
`Response` 与 fetch API 兼容,但可以用 `json()` 检索的数据具有类型。
¥The `Response` is compatible with the fetch API, but the data that can be retrieved with `json()` has a type.

```ts
const data = await res.json()
console.log(`${data.message}`)
```
共享 API 规范意味着你可以了解服务器端的变化。
¥Sharing API specifications means that you can be aware of server-side changes.

## 使用 React
¥With React
你可以使用 React 在 Cloudflare Pages 上创建应用。
¥You can create applications on Cloudflare Pages using React.
API 服务器。
¥The API server.
```ts
// functions/api/[[route]].ts
import { Hono } from 'hono'
import { handle } from 'hono/cloudflare-pages'
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'
const app = new Hono()
const schema = z.object({
id: z.string(),
title: z.string(),
})
type Todo = z.infer
const todos: Todo[] = []
const route = app
.post('/todo', zValidator('form', schema), (c) => {
const todo = c.req.valid('form')
todos.push(todo)
return c.json({
message: 'created!',
})
})
.get((c) => {
return c.json({
todos,
})
})
export type AppType = typeof route
export const onRequest = handle(app, '/api')
```
带有 React 和 React Query 的客户端。
¥The client with React and React Query.
```tsx
// src/App.tsx
import {
useQuery,
useMutation,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'
import { AppType } from '../functions/api/[[route]]'
import { hc, InferResponseType, InferRequestType } from 'hono/client'
const queryClient = new QueryClient()
const client = hc('/api')
export default function App() {
return (
)
}
const Todos = () => {
const query = useQuery({
queryKey: ['todos'],
queryFn: async () => {
const res = await client.todo.$get()
return await res.json()
},
})
const $post = client.todo.$post
const mutation = useMutation<
InferResponseType,
Error,
InferRequestType['form']
>({
mutationFn: async (todo) => {
const res = await $post({
form: todo,
})
return await res.json()
},
onSuccess: async () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
onError: (error) => {
console.log(error)
},
})
return (
{query.data?.todos.map((todo) => (
{todo.title}
))}
)
}
```
# Web 标准
¥Web Standards
Hono 仅使用 Fetch 等 Web 标准。它们最初用于 `fetch` 函数,由处理 HTTP 请求和响应的基本对象组成。除了 `Requests` 和 `Responses`,还有 `URL`、`URLSearchParam`、`Headers` 等。
¥Hono uses only **Web Standards** like Fetch.
They were originally used in the `fetch` function and consist of basic objects that handle HTTP requests and responses.
In addition to `Requests` and `Responses`, there are `URL`, `URLSearchParam`, `Headers` and others.
Cloudflare Workers、Deno 和 Bun 也基于 Web 标准构建。例如,返回 "Hello World" 的服务器可以按如下方式编写。这可以在 Cloudflare Workers 和 Bun 上运行。
¥Cloudflare Workers, Deno, and Bun also build upon Web Standards.
For example, a server that returns "Hello World" could be written as below. This could run on Cloudflare Workers and Bun.
```ts twoslash
export default {
async fetch() {
return new Response('Hello World')
},
}
```
Hono 仅使用 Web 标准,这意味着 Hono 可以在支持它们的任何运行时上运行。此外,我们还有一个 Node.js 适配器。Hono 在这些运行时上运行:
¥Hono uses only Web Standards, which means that Hono can run on any runtime that supports them.
In addition, we have a Node.js adapter. Hono runs on these runtimes:
* Cloudflare Workers (`workerd`)
* Deno
* Bun
* Fastly 计算
¥Fastly Compute
* AWS Lambda
* Node.js
* Vercel (edge-light)
它也适用于 Netlify 和其他平台。相同的代码在所有平台上运行。
¥It also works on Netlify and other platforms.
The same code runs on all platforms.
Cloudflare Workers、Deno、Shopify 和其他公司推出了 [WinterCG](https://wintercg.org),以讨论使用 Web 标准启用 "web-interoperability" 的可能性。Hono 将遵循他们的步骤并采用 Web 标准的标准。
¥Cloudflare Workers, Deno, Shopify, and others launched [WinterCG](https://wintercg.org) to discuss the possibility of using the Web Standards to enable "web-interoperability".
Hono will follow their steps and go for **the Standard of the Web Standards**.
# 上下文
¥Context
要处理请求和响应,可以使用 `Context` 对象。
¥To handle Request and Response, you can use `Context` object.
## req
`req` 是 HonoRequest 的实例。有关更多详细信息,请参阅 [HonoRequest](/docs/api/request)。
¥`req` is the instance of HonoRequest. For more details, see [HonoRequest](/docs/api/request).
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/hello', (c) => {
const userAgent = c.req.header('User-Agent')
// ...
// ---cut-start---
return c.text(`Hello, ${userAgent}`)
// ---cut-end---
})
```
## body()
返回 HTTP 响应。
¥Return the HTTP response.
你可以使用 `c.header()` 设置标头,并使用 `c.status` 设置 HTTP 状态代码。这也可以在 `c.text()`、`c.json()` 等中设置。
¥You can set headers with `c.header()` and set HTTP status code with `c.status`.
This can also be set in `c.text()`, `c.json()` and so on.
::: info 信息
注意:返回文本或 HTML 时,建议使用 `c.text()` 或 `c.html()`。
¥**Note**: When returning Text or HTML, it is recommended to use `c.text()` or `c.html()`.
:::
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/welcome', (c) => {
// Set headers
c.header('X-Message', 'Hello!')
c.header('Content-Type', 'text/plain')
// Set HTTP status code
c.status(201)
// Return the response body
return c.body('Thank you for coming')
})
```
你还可以编写以下内容。
¥You can also write the following.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/welcome', (c) => {
return c.body('Thank you for coming', 201, {
'X-Message': 'Hello!',
'Content-Type': 'text/plain',
})
})
```
响应与下面相同。
¥The Response is the same as below.
```ts twoslash
new Response('Thank you for coming', {
status: 201,
headers: {
'X-Message': 'Hello!',
'Content-Type': 'text/plain',
},
})
```
## text()
将文本渲染为 `Content-Type:text/plain`。
¥Render text as `Content-Type:text/plain`.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/say', (c) => {
return c.text('Hello!')
})
```
## json()
将 JSON 渲染为 `Content-Type:application/json`。
¥Render JSON as `Content-Type:application/json`.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/api', (c) => {
return c.json({ message: 'Hello!' })
})
```
## html()
将 HTML 渲染为 `Content-Type:text/html`。
¥Render HTML as `Content-Type:text/html`.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/', (c) => {
return c.html('
Hello! Hono!
')
})
```
## notFound()
返回 `Not Found` 响应。
¥Return the `Not Found` Response.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/notfound', (c) => {
return c.notFound()
})
```
## redirect()
重定向,默认状态代码为 `302`。
¥Redirect, default status code is `302`.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/redirect', (c) => {
return c.redirect('/')
})
app.get('/redirect-permanently', (c) => {
return c.redirect('/', 301)
})
```
## res
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
// Response object
app.use('/', async (c, next) => {
await next()
c.res.headers.append('X-Debug', 'Debug message')
})
```
## set() / get()
获取和设置任意键值对,具有当前请求的生命周期。这允许在中间件之间或从中间件到路由处理程序传递特定值。
¥Get and set arbitrary key-value pairs, with a lifetime of the current request. This allows passing specific values between middleware or from middleware to route handlers.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono<{ Variables: { message: string } }>()
// ---cut---
app.use(async (c, next) => {
c.set('message', 'Hono is cool!!')
await next()
})
app.get('/', (c) => {
const message = c.get('message')
return c.text(`The message is "${message}"`)
})
```
将 `Variables` 作为泛型传递给 `Hono` 的构造函数,使其类型安全。
¥Pass the `Variables` as Generics to the constructor of `Hono` to make it type-safe.
```ts twoslash
import { Hono } from 'hono'
// ---cut---
type Variables = {
message: string
}
const app = new Hono<{ Variables: Variables }>()
```
`c.set`/`c.get` 的值仅在同一请求中保留。它们不能在不同的请求之间共享或持久化。
¥The value of `c.set` / `c.get` are retained only within the same request. They cannot be shared or persisted across different requests.
## var
你还可以使用 `c.var` 访问变量的值。
¥You can also access the value of a variable with `c.var`.
```ts twoslash
import type { Context } from 'hono'
declare const c: Context
// ---cut---
const result = c.var.client.oneMethod()
```
如果你想要创建提供自定义方法的中间件,请像下面这样写:
¥If you want to create the middleware which provides a custom method,
write like the following:
```ts twoslash
import { Hono } from 'hono'
import { createMiddleware } from 'hono/factory'
// ---cut---
type Env = {
Variables: {
echo: (str: string) => string
}
}
const app = new Hono()
const echoMiddleware = createMiddleware(async (c, next) => {
c.set('echo', (str) => str)
await next()
})
app.get('/echo', echoMiddleware, (c) => {
return c.text(c.var.echo('Hello!'))
})
```
如果要在多个处理程序中使用中间件,可以使用 `app.use()`。然后,你必须将 `Env` 作为泛型传递给 `Hono` 的构造函数以使其类型安全。
¥If you want to use the middleware in multiple handlers, you can use `app.use()`.
Then, you have to pass the `Env` as Generics to the constructor of `Hono` to make it type-safe.
```ts twoslash
import { Hono } from 'hono'
import type { MiddlewareHandler } from 'hono/types'
declare const echoMiddleware: MiddlewareHandler
type Env = {
Variables: {
echo: (str: string) => string
}
}
// ---cut---
const app = new Hono()
app.use(echoMiddleware)
app.get('/echo', (c) => {
return c.text(c.var.echo('Hello!'))
})
```
## render() / setRenderer()
你可以在自定义中间件中使用 `c.setRenderer()` 设置布局。
¥You can set a layout using `c.setRenderer()` within a custom middleware.
```tsx twoslash
/** @jsx jsx */
/** @jsxImportSource hono/jsx */
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.use(async (c, next) => {
c.setRenderer((content) => {
return c.html(
{content}
)
})
await next()
})
```
然后,你可以利用 `c.render()` 在此布局中创建响应。
¥Then, you can utilize `c.render()` to create responses within this layout.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/', (c) => {
return c.render('Hello!')
})
```
其输出将是:
¥The output of which will be:
```html
Hello!
```
此外,此功能提供了自定义参数的灵活性。为了确保类型安全,类型可以定义为:
¥Additionally, this feature offers the flexibility to customize arguments.
To ensure type safety, types can be defined as:
```ts
declare module 'hono' {
interface ContextRenderer {
(
content: string | Promise,
head: { title: string }
): Response | Promise
}
}
```
以下是如何使用它的示例:
¥Here's an example of how you can use this:
```ts
app.use('/pages/*', async (c, next) => {
c.setRenderer((content, head) => {
return c.html(
{head.title}{head.title}