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 用于基准测试。 |
| [Clerk](https://clerk.com) | Cloudflare Workers | 一个开源用户管理平台。Hono 用于 API 服务器。 |
以及以下内容。
¥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).
# 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
extensionMap?: Record
plugins?: SSGPlugin[]
}
```
* `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.
* `plugins` 是一个 SSG 插件数组,用于扩展静态站点生成过程的功能。
¥`plugins` is an array of SSG plugins that extend the functionality of the static site generation process.
### 输出
¥Output
`toSSG` 以以下 Result 类型返回结果。
¥`toSSG` returns the result in the following Result type.
```ts
export interface ToSSGResult {
success: boolean
files: string[]
error?: Error
}
```
## 生成文件
¥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
))
```
## 插件
¥Plugins
插件允许你扩展静态站点生成过程的功能。它们使用钩子在不同阶段自定义生成过程。
¥Plugins allow you to extend the functionality of the static site generation process. They use hooks to customize the generation process at different stages.
### 默认插件
¥Default Plugin
默认情况下,`toSSG` 使用 `defaultPlugin`,它会跳过非 200 状态响应(例如重定向、错误或 404)。这可以防止为不成功的响应生成文件。
¥By default, `toSSG` uses `defaultPlugin` which skips non-200 status responses (like redirects, errors, or 404s). This prevents generating files for unsuccessful responses.
```ts
import { toSSG, defaultPlugin } from 'hono/ssg'
// defaultPlugin is automatically applied when no plugins specified
toSSG(app, fs)
// Equivalent to:
toSSG(app, fs, { plugins: [defaultPlugin] })
```
如果你指定了自定义插件,`defaultPlugin` 不会自动包含在内。要在添加自定义插件时保留默认行为,请明确包含它:
¥If you specify custom plugins, `defaultPlugin` is **not** automatically included. To keep the default behavior while adding custom plugins, explicitly include it:
```ts
toSSG(app, fs, {
plugins: [defaultPlugin, myCustomPlugin],
})
```
### 钩子类型
¥Hook Types
插件可以使用以下钩子来自定义 `toSSG` 流程:
¥Plugins can use the following hooks to customize the `toSSG` process:
```ts
export type BeforeRequestHook = (req: Request) => Request | false
export type AfterResponseHook = (res: Response) => Response | false
export type AfterGenerateHook = (
result: ToSSGResult
) => void | Promise
```
* BeforeRequestHook:处理每个请求前调用。返回 `false` 可跳过路由。
¥**BeforeRequestHook**: Called before processing each request. Return `false` to skip the route.
* AfterResponseHook:收到每个响应后调用。返回 `false` 可跳过文件生成。
¥**AfterResponseHook**: Called after receiving each response. Return `false` to skip file generation.
* AfterGenerateHook:整个生成过程完成后调用。
¥**AfterGenerateHook**: Called after the entire generation process completes.
### 插件界面
¥Plugin Interface
```ts
export interface SSGPlugin {
beforeRequestHook?: BeforeRequestHook | BeforeRequestHook[]
afterResponseHook?: AfterResponseHook | AfterResponseHook[]
afterGenerateHook?: AfterGenerateHook | AfterGenerateHook[]
}
```
### 基本插件示例
¥Basic Plugin Examples
仅过滤 GET 请求:
¥Filter only GET requests:
```ts
const getOnlyPlugin: SSGPlugin = {
beforeRequestHook: (req) => {
if (req.method === 'GET') {
return req
}
return false
},
}
```
按状态码过滤:
¥Filter by status code:
```ts
const statusFilterPlugin: SSGPlugin = {
afterResponseHook: (res) => {
if (res.status === 200 || res.status === 500) {
return res
}
return false
},
}
```
日志生成的文件:
¥Log generated files:
```ts
const logFilesPlugin: SSGPlugin = {
afterGenerateHook: (result) => {
if (result.files) {
result.files.forEach((file) => console.log(file))
}
},
}
```
### 高级插件示例
¥Advanced Plugin Example
以下是创建生成 `sitemap.xml` 文件的站点地图插件的示例:
¥Here's an example of creating a sitemap plugin that generates a `sitemap.xml` file:
```ts
// plugins.ts
import fs from 'node:fs/promises'
import path from 'node:path'
import type { SSGPlugin } from 'hono/ssg'
import { DEFAULT_OUTPUT_DIR } from 'hono/ssg'
export const sitemapPlugin = (baseURL: string): SSGPlugin => {
return {
afterGenerateHook: (result, fsModule, options) => {
const outputDir = options?.dir ?? DEFAULT_OUTPUT_DIR
const filePath = path.join(outputDir, 'sitemap.xml')
const urls = result.files.map((file) =>
new URL(file, baseURL).toString()
)
const siteMapText = `
${urls.map((url) => `${url}`).join('\n')}
`
fsModule.writeFile(filePath, siteMapText)
},
}
}
```
应用插件:
¥Applying plugins:
```ts
import app from './index'
import { toSSG } from 'hono/ssg'
import { sitemapPlugin } from './plugins'
toSSG(app, fs, {
plugins: [
getOnlyPlugin,
statusFilterPlugin,
logFilesPlugin,
sitemapPlugin('https://example.com'),
],
})
```
# 接受助手
¥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.
# 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.

# 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 {
deleteCookie,
getCookie,
getSignedCookie,
setCookie,
setSignedCookie,
generateCookie,
generateSignedCookie,
} from 'hono/cookie'
```
## 用法
¥Usage
### 常规 Cookie
¥Regular cookies
```ts
app.get('/cookie', (c) => {
setCookie(c, 'cookie_name', 'cookie_value')
const yummyCookie = getCookie(c, 'cookie_name')
deleteCookie(c, 'cookie_name')
const allCookies = getCookie(c)
// ...
})
```
### 签名 Cookie
¥Signed cookies
注意:由于 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
app.get('/signed-cookie', (c) => {
const secret = 'secret' // make sure it's a large enough string to be secure
await setSignedCookie(c, 'cookie_name0', 'cookie_value', secret)
const fortuneCookie = await getSignedCookie(
c,
secret,
'cookie_name0'
)
deleteCookie(c, 'cookie_name0')
// `getSignedCookie` will return `false` for a specified cookie if the signature was tampered with or is invalid
const allSignedCookies = await getSignedCookie(c, secret)
// ...
})
```
### Cookie 生成
¥Cookie Generation
`generateCookie` 和 `generateSignedCookie` 函数允许你直接创建 Cookie 字符串,而无需在响应头中设置它们。
¥`generateCookie` and `generateSignedCookie` functions allow you to create cookie strings directly without setting them in the response headers.
#### `generateCookie`
```ts
// Basic cookie generation
const cookie = generateCookie('delicious_cookie', 'macha')
// Returns: 'delicious_cookie=macha; Path=/'
// Cookie with options
const cookie = generateCookie('delicious_cookie', 'macha', {
path: '/',
secure: true,
httpOnly: true,
domain: 'example.com',
})
```
#### `generateSignedCookie`
```ts
// Basic signed cookie generation
const signedCookie = await generateSignedCookie(
'delicious_cookie',
'macha',
'secret chocolate chips'
)
// Signed cookie with options
const signedCookie = await generateSignedCookie(
'delicious_cookie',
'macha',
'secret chocolate chips',
{
path: '/',
secure: true,
httpOnly: true,
}
)
```
注意:与 `setCookie` 和 `setSignedCookie` 不同,这些函数仅生成 Cookie 字符串。如果需要,你需要在标题中手动设置它们。
¥**Note**: Unlike `setCookie` and `setSignedCookie`, these functions only generate the cookie strings. You need to manually set them in headers if needed.
## 选项
¥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.
# 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 { upgradeWebSocket, websocket } from 'hono/bun'
// ...
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 { upgradeWebSocket, websocket } from 'hono/bun'
import { html } from 'hono/html'
const app = new Hono()
app.get('/', (c) => {
return c.html(
{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,
}
```
# 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';
issuer?: string | RegExp;
): 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.
#### issuer:`string | RegExp`
用于 JWT 验证的预期发行者。
¥The expected issuer used for JWT verification.
## `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.
* `iss`:检查令牌以确保其由受信任的发行者发行。
¥`iss`: The token is checked to ensure it has been issued by a trusted issuer.
如果你打算在验证期间执行这些检查,请确保你的 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.
* `JwtTokenIssuer`:表示令牌中的 "iss" 声明不正确。
¥`JwtTokenIssuer`: Indicates that the "iss" 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
# 开发助手
¥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.
# 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
}
```
# 测试助手
¥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 应用的路由类型确定的对象,类似于 [Hono 客户端](/docs/guides/rpc#client)。这允许你以类型安全的方式调用你定义的路由,并在测试中使用编辑器自动补齐功能。
¥The `testClient()` function takes an instance of Hono as its first argument and returns an object typed according to your Hono application's routes, similar to the [Hono Client](/docs/guides/rpc#client). This allows you to call your defined routes in a type-safe manner with editor autocompletion within your tests.
**关于类型推断的重要说明:**
¥**Important Note on Type Inference:**
为了让 `testClient` 正确推断路由类型并提供自动补全功能,你必须直接在 `Hono` 实例上使用链式方法定义路由。
¥For the `testClient` to correctly infer the types of your routes and provide autocompletion, **you must define your routes using chained methods directly on the `Hono` instance**.
类型推断依赖于流经 `.get()`、`.post()` 等链式调用的类型。如果你在创建 Hono 实例后单独定义路由(如 "Hello World" 示例中所示的常见模式:`const app = new Hono(); app.get(...)`),`testClient` 将不具备特定路由所需的类型信息,你将无法获得类型安全的客户端功能。
¥The type inference relies on the type flowing through the chained `.get()`, `.post()`, etc., calls. If you define routes separately after creating the Hono instance (like the common pattern shown in the "Hello World" example: `const app = new Hono(); app.get(...)`), the `testClient` will not have the necessary type information for specific routes, and you won't get the type-safe client features.
**示例:**
¥**Example:**
此示例有效,因为 `.get()` 方法直接链接到 `new Hono()` 调用:
¥This example works because the `.get()` method is chained directly onto the `new Hono()` call:
```ts
// index.ts
const app = new Hono().get('/search', (c) => {
const query = c.req.query('q')
return c.json({ query: query, results: ['result1', 'result2'] })
})
export default app
```
```ts
// index.test.ts
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
import { describe, it, expect } from 'vitest' // Or your preferred test runner
import app from './app'
describe('Search Endpoint', () => {
// Create the test client from the app instance
const client = testClient(app)
it('should return search results', async () => {
// Call the endpoint using the typed client
// Notice the type safety for query parameters (if defined in the route)
// and the direct access via .$get()
const res = await client.search.$get({
query: { q: 'hono' },
})
// Assertions
expect(res.status).toBe(200)
expect(await res.json()).toEqual({
query: 'hono',
results: ['result1', 'result2'],
})
})
})
```
要在测试中包含标头,请将其作为调用中的第二个参数传递。第二个参数也可以将 `init` 属性作为 `RequestInit` 对象,从而允许你设置标头、方法、正文等。了解更多关于 `init` 属性 [此处](/docs/guides/rpc#init-option) 的信息。
¥To include headers in your test, pass them as the second parameter in the call. The second parameter can also take an `init` property as a `RequestInit` object, allowing you to set headers, method, body, etc. Learn more about the `init` property [here](/docs/guides/rpc#init-option).
```ts
// index.test.ts
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
import { describe, it, expect } from 'vitest' // Or your preferred test runner
import app from './app'
describe('Search Endpoint', () => {
// Create the test client from the app instance
const client = testClient(app)
it('should return search results', async () => {
// Include the token in the headers and set the content type
const token = 'this-is-a-very-clean-token'
const res = await client.search.$get(
{
query: { q: 'hono' },
},
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': `application/json`,
},
}
)
// Assertions
expect(res.status).toBe(200)
expect(await res.json()).toEqual({
query: 'hono',
results: ['result1', 'result2'],
})
})
})
```
# 流式助手
¥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.
:::
# 工厂助手
¥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()
// ...
})
```
# 适配器助手
¥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.com/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
# 代理助手
¥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')
},
})
})
```
你可以使用 `customFetch` 选项覆盖默认的全局 `fetch` 函数:
¥You can override the default global `fetch` function with the `customFetch` option:
```ts
app.get('/proxy', (c) => {
return proxy('https://example.com/', {
customFetch,
})
})
```
### 连接头处理
¥Connection Header Processing
默认情况下,`proxy()` 会忽略 `Connection` 标头,以防止逐跳标头注入攻击。你可以使用 `strictConnectionProcessing` 选项启用严格的 RFC 9110 合规性:
¥By default, `proxy()` ignores the `Connection` header to prevent Hop-by-Hop Header Injection attacks. You can enable strict RFC 9110 compliance with the `strictConnectionProcessing` option:
```ts
// Default behavior (recommended for untrusted clients)
app.get('/proxy/:path', (c) => {
return proxy(`http://${originServer}/${c.req.param('path')}`, c.req)
})
// Strict RFC 9110 compliance (use only in trusted environments)
app.get('/internal-proxy/:path', (c) => {
return proxy(`http://${internalServer}/${c.req.param('path')}`, {
...c.req,
strictConnectionProcessing: true,
})
})
```
### `ProxyFetch`
`proxy()` 的类型定义为 `ProxyFetch`,如下所示
¥The type of `proxy()` is defined as `ProxyFetch` and is as follows
```ts
interface ProxyRequestInit extends Omit {
raw?: Request
customFetch?: (request: Request) => Promise
strictConnectionProcessing?: boolean
headers?:
| HeadersInit
| [string, string][]
| Record
| Record
}
interface ProxyFetch {
(
input: string | URL | Request,
init?: ProxyRequestInit
): Promise
}
```
# 路由助手
¥Route Helper
路由助手为调试和中间件开发提供了增强的路由信息。它允许你访问有关匹配路由和当前正在处理的路由的详细信息。
¥The Route Helper provides enhanced routing information for debugging and middleware development. It allows you to access detailed information about matched routes and the current route being processed.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import {
matchedRoutes,
routePath,
baseRoutePath,
basePath,
} from 'hono/route'
```
## 用法
¥Usage
### 基本路由信息
¥Basic route information
```ts
const app = new Hono()
app.get('/posts/:id', (c) => {
const currentPath = routePath(c) // '/posts/:id'
const routes = matchedRoutes(c) // Array of matched routes
return c.json({
path: currentPath,
totalRoutes: routes.length,
})
})
```
### 与子应用协同工作
¥Working with sub-applications
```ts
const app = new Hono()
const apiApp = new Hono()
apiApp.get('/posts/:id', (c) => {
return c.json({
routePath: routePath(c), // '/posts/:id'
baseRoutePath: baseRoutePath(c), // '/api'
basePath: basePath(c), // '/api' (with actual params)
})
})
app.route('/api', apiApp)
```
## `matchedRoutes()`
返回与当前请求匹配的所有路由(包括中间件)的数组。
¥Returns an array of all routes that matched the current request, including middleware.
```ts
app.all('/api/*', (c, next) => {
console.log('API middleware')
return next()
})
app.get('/api/users/:id', (c) => {
const routes = matchedRoutes(c)
// Returns: [
// { method: 'ALL', path: '/api/*', handler: [Function] },
// { method: 'GET', path: '/api/users/:id', handler: [Function] }
// ]
return c.json({ routes: routes.length })
})
```
## `routePath()`
返回为当前处理程序注册的路由路径模式。
¥Returns the route path pattern registered for the current handler.
```ts
app.get('/posts/:id', (c) => {
console.log(routePath(c)) // '/posts/:id'
return c.text('Post details')
})
```
### 使用索引参数
¥Using with index parameter
你可以选择传递一个索引参数,以获取特定位置的路由路径,类似于 `Array.prototype.at()`。
¥You can optionally pass an index parameter to get the route path at a specific position, similar to `Array.prototype.at()`.
```ts
app.all('/api/*', (c, next) => {
return next()
})
app.get('/api/users/:id', (c) => {
console.log(routePath(c, 0)) // '/api/*' (first matched route)
console.log(routePath(c, -1)) // '/api/users/:id' (last matched route)
return c.text('User details')
})
```
## `baseRoutePath()`
返回路由中指定的当前路由的基本路径模式。
¥Returns the base path pattern of the current route as specified in routing.
```ts
const subApp = new Hono()
subApp.get('/posts/:id', (c) => {
return c.text(baseRoutePath(c)) // '/:sub'
})
app.route('/:sub', subApp)
```
### 使用索引参数
¥Using with index parameter
你可以选择传递一个索引参数,以获取特定位置的基本路由路径,类似于 `Array.prototype.at()`。
¥You can optionally pass an index parameter to get the base route path at a specific
position, similar to `Array.prototype.at()`.
```ts
app.all('/api/*', (c, next) => {
return next()
})
const subApp = new Hono()
subApp.get('/users/:id', (c) => {
console.log(baseRoutePath(c, 0)) // '/' (first matched route)
console.log(baseRoutePath(c, -1)) // '/api' (last matched route)
return c.text('User details')
})
app.route('/api', subApp)
```
## `basePath()`
返回实际请求中嵌入参数的基本路径。
¥Returns the base path with embedded parameters from the actual request.
```ts
const subApp = new Hono()
subApp.get('/posts/:id', (c) => {
return c.text(basePath(c)) // '/api' (for request to '/api/posts/123')
})
app.route('/:sub', subApp)
```
# 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)
# 路由
¥Routing
Hono 的路由灵活且直观。让我们看一看。
¥Routing of Hono is flexible and intuitive.
Let's take a look.
## 基本
¥Basic
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
// HTTP Methods
app.get('/', (c) => c.text('GET /'))
app.post('/', (c) => c.text('POST /'))
app.put('/', (c) => c.text('PUT /'))
app.delete('/', (c) => c.text('DELETE /'))
// Wildcard
app.get('/wild/*/card', (c) => {
return c.text('GET /wild/*/card')
})
// Any HTTP methods
app.all('/hello', (c) => c.text('Any Method /hello'))
// Custom HTTP method
app.on('PURGE', '/cache', (c) => c.text('PURGE Method /cache'))
// Multiple Method
app.on(['PUT', 'DELETE'], '/post', (c) =>
c.text('PUT or DELETE /post')
)
// Multiple Paths
app.on('GET', ['/hello', '/ja/hello', '/en/hello'], (c) =>
c.text('Hello')
)
```
## 路径参数
¥Path Parameter
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/user/:name', async (c) => {
const name = c.req.param('name')
// ^?
// ...
})
```
或一次所有参数:
¥or all parameters at once:
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/posts/:id/comment/:comment_id', async (c) => {
const { id, comment_id } = c.req.param()
// ^?
// ...
})
```
## 可选参数
¥Optional Parameter
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
// Will match `/api/animal` and `/api/animal/:type`
app.get('/api/animal/:type?', (c) => c.text('Animal!'))
```
## 正则表达式
¥Regexp
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', async (c) => {
const { date, title } = c.req.param()
// ^?
// ...
})
```
## 包括斜线
¥Including slashes
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/posts/:filename{.+\\.png}', async (c) => {
//...
})
```
## 链式路由
¥Chained route
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app
.get('/endpoint', (c) => {
return c.text('GET /endpoint')
})
.post((c) => {
return c.text('POST /endpoint')
})
.delete((c) => {
return c.text('DELETE /endpoint')
})
```
## 分组
¥Grouping
你可以使用 Hono 实例对路由进行分组,并使用路由方法将它们添加到主应用。
¥You can group the routes with the Hono instance and add them to the main app with the route method.
```ts twoslash
import { Hono } from 'hono'
// ---cut---
const book = new Hono()
book.get('/', (c) => c.text('List Books')) // GET /book
book.get('/:id', (c) => {
// GET /book/:id
const id = c.req.param('id')
return c.text('Get Book: ' + id)
})
book.post('/', (c) => c.text('Create Book')) // POST /book
const app = new Hono()
app.route('/book', book)
```
## 不改变基础的分组
¥Grouping without changing base
你还可以在保留基础的同时对多个实例进行分组。
¥You can also group multiple instances while keeping base.
```ts twoslash
import { Hono } from 'hono'
// ---cut---
const book = new Hono()
book.get('/book', (c) => c.text('List Books')) // GET /book
book.post('/book', (c) => c.text('Create Book')) // POST /book
const user = new Hono().basePath('/user')
user.get('/', (c) => c.text('List Users')) // GET /user
user.post('/', (c) => c.text('Create User')) // POST /user
const app = new Hono()
app.route('/', book) // Handle /book
app.route('/', user) // Handle /user
```
## 基本路径
¥Base path
你可以指定基本路径。
¥You can specify the base path.
```ts twoslash
import { Hono } from 'hono'
// ---cut---
const api = new Hono().basePath('/api')
api.get('/book', (c) => c.text('List Books')) // GET /api/book
```
## 使用主机名进行路由
¥Routing with hostname
如果它包含主机名,它可以正常工作。
¥It works fine if it includes a hostname.
```ts twoslash
import { Hono } from 'hono'
// ---cut---
const app = new Hono({
getPath: (req) => req.url.replace(/^https?:\/([^?]+).*$/, '$1'),
})
app.get('/www1.example.com/hello', (c) => c.text('hello www1'))
app.get('/www2.example.com/hello', (c) => c.text('hello www2'))
```
## 路由使用 `host` 标头值
¥Routing with `host` Header value
如果在 Hono 构造函数中设置 `getPath()` 函数,Hono 可以处理 `host` 标头值。
¥Hono can handle the `host` header value if you set the `getPath()` function in the Hono constructor.
```ts twoslash
import { Hono } from 'hono'
// ---cut---
const app = new Hono({
getPath: (req) =>
'/' +
req.headers.get('host') +
req.url.replace(/^https?:\/\/[^/]+(\/[^?]*).*/, '$1'),
})
app.get('/www1.example.com/hello', (c) => c.text('hello www1'))
// A following request will match the route:
// new Request('http://www1.example.com/hello', {
// headers: { host: 'www1.example.com' },
// })
```
例如,通过应用此功能,你可以通过 `User-Agent` 标头更改路由。
¥By applying this, for example, you can change the routing by `User-Agent` header.
## 路由优先级
¥Routing priority
处理程序或中间件将按注册顺序执行。
¥Handlers or middleware will be executed in registration order.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/book/a', (c) => c.text('a')) // a
app.get('/book/:slug', (c) => c.text('common')) // common
```
```
GET /book/a ---> `a`
GET /book/b ---> `common`
```
执行处理程序时,该过程将停止。
¥When a handler is executed, the process will be stopped.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('*', (c) => c.text('common')) // common
app.get('/foo', (c) => c.text('foo')) // foo
```
```
GET /foo ---> `common` // foo will not be dispatched
```
如果你有要执行的中间件,请在处理程序上方编写代码。
¥If you have the middleware that you want to execute, write the code above the handler.
```ts twoslash
import { Hono } from 'hono'
import { logger } from 'hono/logger'
const app = new Hono()
// ---cut---
app.use(logger())
app.get('/foo', (c) => c.text('foo'))
```
如果你想要有一个 "fallback" 处理程序,请在另一个处理程序下方编写代码。
¥If you want to have a "*fallback*" handler, write the code below the other handler.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/bar', (c) => c.text('bar')) // bar
app.get('*', (c) => c.text('fallback')) // fallback
```
```
GET /bar ---> `bar`
GET /foo ---> `fallback`
```
## 分组顺序
¥Grouping ordering
请注意,分组路由的错误很难注意到。`route()` 函数从第二个参数(例如 `three` 或 `two`)获取存储的路由并将其添加到其自己的(`two` 或 `app`)路由中。
¥Note that the mistake of grouping routings is hard to notice.
The `route()` function takes the stored routing from the second argument (such as `three` or `two`) and adds it to its own (`two` or `app`) routing.
```ts
three.get('/hi', (c) => c.text('hi'))
two.route('/three', three)
app.route('/two', two)
export default app
```
它将返回 200 个响应。
¥It will return 200 response.
```
GET /two/three/hi ---> `hi`
```
但是,如果它们的顺序错误,它将返回 404。
¥However, if they are in the wrong order, it will return a 404.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
const two = new Hono()
const three = new Hono()
// ---cut---
three.get('/hi', (c) => c.text('hi'))
app.route('/two', two) // `two` does not have routes
two.route('/three', three)
export default app
```
```
GET /two/three/hi ---> 404 Not Found
```
# 上下文
¥Context
`Context` 对象会为每个请求实例化,并一直保存到响应返回为止。你可以在其中输入值,设置标头和要返回的状态码,以及访问 HonoRequest 和 Response 对象。
¥The `Context` object is instantiated for each request and kept until the response is returned. You can put values in it, set headers and a status code you want to return, and access HonoRequest and Response objects.
## req
`req` 是 HonoRequest 的一个实例。有关更多详细信息,请参阅 [HonoRequest](/docs/api/request)。
¥`req` is an 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---
})
```
## status()
你可以使用 `c.status()` 设置 HTTP 状态码。默认为 `200`。如果代码是 `200`,则无需使用 `c.status()`。
¥You can set an HTTP status code with `c.status()`. The default is `200`. You don't have to use `c.status()` if the code is `200`.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/posts', (c) => {
// Set HTTP status code
c.status(201)
return c.text('Your post is created!')
})
```
## header()
你可以为响应设置 HTTP 标头。
¥You can set HTTP Headers for the response.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/', (c) => {
// Set headers
c.header('X-Message', 'My custom message')
return c.text('HellO!')
})
```
## body()
返回 HTTP 响应。
¥Return an HTTP response.
::: 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) => {
c.header('Content-Type', 'text/plain')
// 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',
})
})
```
响应与下面的 `Response` 对象相同。
¥The response is the same `Response` object 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` 响应。你可以使用 [`app.notFound()`](/docs/api/hono#not-found) 进行自定义。
¥Return a `Not Found` Response. You can customize it with [`app.notFound()`](/docs/api/hono#not-found).
```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
你可以访问将要返回的 Response 对象。
¥You can access the Response object that will be returned.
```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}
, {
title: 'My hobbies',
})
})
```
## executionCtx
你可以访问 Cloudflare Workers 特定的 [ExecutionContext](https://developers.cloudflare.com/workers/runtime-apis/context/)。
¥You can access Cloudflare Workers' specific [ExecutionContext](https://developers.cloudflare.com/workers/runtime-apis/context/).
```ts twoslash
import { Hono } from 'hono'
const app = new Hono<{
Bindings: {
KV: any
}
}>()
declare const key: string
declare const data: string
// ---cut---
// ExecutionContext object
app.get('/foo', async (c) => {
c.executionCtx.waitUntil(c.env.KV.put(key, data))
// ...
})
```
## event
你可以访问 Cloudflare Workers 特定的 `FetchEvent`。这在 "服务工作线程" 语法中使用。但是,现在不推荐使用。
¥You can access Cloudflare Workers' specific `FetchEvent`. This was used in "Service Worker" syntax. But, it is not recommended now.
```ts twoslash
import { Hono } from 'hono'
declare const key: string
declare const data: string
type KVNamespace = any
// ---cut---
// Type definition to make type inference
type Bindings = {
MY_KV: KVNamespace
}
const app = new Hono<{ Bindings: Bindings }>()
// FetchEvent object (only set when using Service Worker syntax)
app.get('/foo', async (c) => {
c.event.waitUntil(c.env.MY_KV.put(key, data))
// ...
})
```
## env
在 Cloudflare Workers 中,绑定到 worker 的环境变量、密钥、KV 命名空间、D1 数据库、R2 bucket 等称为绑定。无论类型如何,绑定始终可用作全局变量,并可通过上下文 `c.env.BINDING_KEY` 访问。
¥In Cloudflare Workers Environment variables, secrets, KV namespaces, D1 database, R2 bucket etc. that are bound to a worker are known as bindings.
Regardless of type, bindings are always available as global variables and can be accessed via the context `c.env.BINDING_KEY`.
```ts twoslash
import { Hono } from 'hono'
type KVNamespace = any
// ---cut---
// Type definition to make type inference
type Bindings = {
MY_KV: KVNamespace
}
const app = new Hono<{ Bindings: Bindings }>()
// Environment object for Cloudflare Workers
app.get('/', async (c) => {
c.env.MY_KV.get('my-key')
// ...
})
```
## error
如果 Handler 抛出错误,则错误对象将放置在 `c.error` 中。你可以在中间件中访问它。
¥If the Handler throws an error, the error object is placed in `c.error`.
You can access it in your middleware.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.use(async (c, next) => {
await next()
if (c.error) {
// do something...
}
})
```
## ContextVariableMap
例如,如果你希望在使用特定中间件时向变量添加类型定义,则可以扩展 `ContextVariableMap`。例如:
¥For instance, if you wish to add type definitions to variables when a specific middleware is used, you can extend `ContextVariableMap`. For example:
```ts
declare module 'hono' {
interface ContextVariableMap {
result: string
}
}
```
然后,你可以在中间件中使用它:
¥You can then utilize this in your middleware:
```ts twoslash
import { createMiddleware } from 'hono/factory'
// ---cut---
const mw = createMiddleware(async (c, next) => {
c.set('result', 'some values') // result is a string
await next()
})
```
在处理程序中,变量被推断为正确的类型:
¥In a handler, the variable is inferred as the proper type:
```ts twoslash
import { Hono } from 'hono'
const app = new Hono<{ Variables: { result: string } }>()
// ---cut---
app.get('/', (c) => {
const val = c.get('result') // val is a string
// ...
return c.json({ result: val })
})
```
# API
Hono 的 API 很简单。仅由来自 Web 标准的扩展对象组成。因此,你可以很快理解它。
¥Hono's API is simple.
Just composed by extended objects from Web Standards.
So, you can understand it well quickly.
在本节中,我们将介绍 Hono 的 API,如下所示。
¥In this section, we introduce API of Hono like below.
* Hono 对象
¥Hono object
* 关于路由
¥About routing
* 上下文对象
¥Context object
* 关于中间件
¥About middleware
# HTTPException
当发生致命错误时,Hono(以及许多生态系统中间件)可能会抛出 `HTTPException` 错误。这是一个自定义的 Hono `Error`,它简化了 [返回错误响应](#handling-httpexceptions)。
¥When a fatal error occurs, Hono (and many ecosystem middleware) may throw an `HTTPException`. This is a custom Hono `Error` that simplifies [returning error responses](#handling-httpexceptions).
## 抛出 HTTPExceptions
¥Throwing HTTPExceptions
你可以通过指定状态码以及消息或自定义响应来抛出你自己的 HTTPException。
¥You can throw your own HTTPExceptions by specifying a status code, and either a message or a custom response.
### 自定义消息
¥Custom Message
对于基本的 `text` 响应,只需设置错误 `message`。
¥For basic `text` responses, just set a the error `message`.
```ts twoslash
import { HTTPException } from 'hono/http-exception'
throw new HTTPException(401, { message: 'Unauthorized' })
```
### 自定义响应
¥Custom Response
对于其他响应类型,或要设置响应标头,请使用 `res` 选项。请注意,传递给构造函数的状态是用于创建响应的状态。
¥For other response types, or to set response headers, use the `res` option. *Note that the status passed to the constructor is the one used to create responses.*
```ts twoslash
import { HTTPException } from 'hono/http-exception'
const errorResponse = new Response('Unauthorized', {
status: 401, // this gets ignored
headers: {
Authenticate: 'error="invalid_token"',
},
})
throw new HTTPException(401, { res: errorResponse })
```
### 原因
¥Cause
无论哪种情况,你都可以使用 [`cause`](https://web.nodejs.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) 选项向 HTTPException 添加任意数据。
¥In either case, you can use the [`cause`](https://web.nodejs.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) option to add arbitrary data to the HTTPException.
```ts twoslash
import { Hono, Context } from 'hono'
import { HTTPException } from 'hono/http-exception'
const app = new Hono()
declare const message: string
declare const authorize: (c: Context) => Promise
// ---cut---
app.post('/login', async (c) => {
try {
await authorize(c)
} catch (cause) {
throw new HTTPException(401, { message, cause })
}
return c.redirect('/')
})
```
## 处理 HTTPExceptions
¥Handling HTTPExceptions
你可以使用 [`app.onError`](/docs/api/hono#error-handling) 处理未捕获的 HTTPException。它们包含一个 `getResponse` 方法,该方法返回一个由错误 `status` 创建的新 `Response`,以及错误 `message` 或抛出错误时设置的 [自定义响应](#custom-response)。
¥You can handle uncaught HTTPExceptions with [`app.onError`](/docs/api/hono#error-handling). They include a `getResponse` method that returns a new `Response` created from the error `status`, and either the error `message`, or the [custom response](#custom-response) set when the error was thrown.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
import { HTTPException } from 'hono/http-exception'
// ...
app.onError((error, c) => {
if (error instanceof HTTPException) {
console.error(error.cause)
// Get the custom response
return error.getResponse()
}
// ...
// ---cut-start---
return c.text('Unexpected error')
// ---cut-end---
})
```
::: warning 警告
`HTTPException.getResponse` 无法感知 `Context`。要包含已在 `Context` 中设置的标头,你必须将它们应用于新的 `Response`。
¥**`HTTPException.getResponse` is not aware of `Context`**. To include headers already set in `Context`, you must apply them to a new `Response`.
:::
# HonoRequest
`HonoRequest` 是一个可以从 `c.req` 中获取的对象,它封装了一个 [请求](https://web.nodejs.cn/en-US/docs/Web/API/Request) 对象。
¥The `HonoRequest` is an object that can be taken from `c.req` which wraps a [Request](https://web.nodejs.cn/en-US/docs/Web/API/Request) object.
## param()
获取路径参数的值。
¥Get the values of path parameters.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
// Captured params
app.get('/entry/:id', async (c) => {
const id = c.req.param('id')
// ^?
// ...
})
// Get all params at once
app.get('/entry/:id/comment/:commentId', async (c) => {
const { id, commentId } = c.req.param()
// ^?
})
```
## query()
获取查询字符串参数。
¥Get querystring parameters.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
// Query params
app.get('/search', async (c) => {
const query = c.req.query('q')
// ^?
})
// Get all params at once
app.get('/search', async (c) => {
const { q, limit, offset } = c.req.query()
// ^?
})
```
## queries()
获取多个查询字符串参数值,例如 `/search?tags=A&tags=B`
¥Get multiple querystring parameter values, e.g. `/search?tags=A&tags=B`
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/search', async (c) => {
// tags will be string[]
const tags = c.req.queries('tags')
// ^?
// ...
})
```
## header()
获取请求标头值。
¥Get the request header value.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/', (c) => {
const userAgent = c.req.header('User-Agent')
// ^?
return c.text(`Your user agent is ${userAgent}`)
})
```
::: warning 警告
当不带参数调用 `c.req.header()` 时,返回记录中的所有键均为小写。
¥When `c.req.header()` is called with no arguments, all keys in the returned record are **lowercase**.
如果你想要获取带有大写名称的标头的值,请使用 `c.req.header(“X-Foo”)`。
¥If you want to get the value of a header with an uppercase name,
use `c.req.header(“X-Foo”)`.
```ts
// ❌ Will not work
const headerRecord = c.req.header()
const foo = headerRecord['X-Foo']
// ✅ Will work
const foo = c.req.header('X-Foo')
```
:::
## parseBody()
解析类型为 `multipart/form-data` 或 `application/x-www-form-urlencoded` 的请求主体
¥Parse Request body of type `multipart/form-data` or `application/x-www-form-urlencoded`
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.parseBody()
// ...
})
```
`parseBody()` 支持以下行为。
¥`parseBody()` supports the following behaviors.
**单个文件**
¥**Single file**
```ts twoslash
import { Context } from 'hono'
declare const c: Context
// ---cut---
const body = await c.req.parseBody()
const data = body['foo']
// ^?
```
`body['foo']` 是 `(string | File)`。
¥`body['foo']` is `(string | File)`.
如果上传了多个文件,将使用最后一个文件。
¥If multiple files are uploaded, the last one will be used.
### 多个文件
¥Multiple files
```ts twoslash
import { Context } from 'hono'
declare const c: Context
// ---cut---
const body = await c.req.parseBody()
body['foo[]']
```
`body['foo[]']` 始终是 `(string | File)[]`。
¥`body['foo[]']` is always `(string | File)[]`.
需要 `[]` 后缀。
¥`[]` postfix is required.
### 多个同名文件或字段
¥Multiple files or fields with same name
如果你的输入字段允许多个 `` 或多个复选框具有相同名称 ``。
¥If you have a input field that allows multiple `` or multiple checkboxes with the same name ``.
```ts twoslash
import { Context } from 'hono'
declare const c: Context
// ---cut---
const body = await c.req.parseBody({ all: true })
body['foo']
```
`all` 选项默认禁用。
¥`all` option is disabled by default.
* 如果 `body['foo']` 是多个文件,它将被解析为 `(string | File)[]`。
¥If `body['foo']` is multiple files, it will be parsed to `(string | File)[]`.
* 如果 `body['foo']` 是单个文件,它将被解析为 `(string | File)`。
¥If `body['foo']` is single file, it will be parsed to `(string | File)`.
### 点符号
¥Dot notation
如果你设置 `dot` 选项 `true`,则返回值基于点符号构造。
¥If you set the `dot` option `true`, the return value is structured based on the dot notation.
想象一下接收以下数据:
¥Imagine receiving the following data:
```ts twoslash
const data = new FormData()
data.append('obj.key1', 'value1')
data.append('obj.key2', 'value2')
```
你可以通过设置 `dot` 选项 `true` 来获取结构化值:
¥You can get the structured value by setting the `dot` option `true`:
```ts twoslash
import { Context } from 'hono'
declare const c: Context
// ---cut---
const body = await c.req.parseBody({ dot: true })
// body is `{ obj: { key1: 'value1', key2: 'value2' } }`
```
## json()
解析 `application/json` 类型的请求主体
¥Parses the request body of type `application/json`
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.json()
// ...
})
```
## text()
解析 `text/plain` 类型的请求主体
¥Parses the request body of type `text/plain`
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.text()
// ...
})
```
## arrayBuffer()
将请求主体解析为 `ArrayBuffer`
¥Parses the request body as an `ArrayBuffer`
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.arrayBuffer()
// ...
})
```
## blob()
将请求主体解析为 `Blob`。
¥Parses the request body as a `Blob`.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.blob()
// ...
})
```
## formData()
将请求主体解析为 `FormData`。
¥Parses the request body as a `FormData`.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.formData()
// ...
})
```
## valid()
获取经过验证的数据。
¥Get the validated data.
```ts
app.post('/posts', async (c) => {
const { title, body } = c.req.valid('form')
// ...
})
```
可用目标如下。
¥Available targets are below.
* `form`
* `json`
* `query`
* `header`
* `cookie`
* `param`
有关使用示例,请参阅 [验证部分](/docs/guides/validation)。
¥See the [Validation section](/docs/guides/validation) for usage examples.
## routePath
::: warning 警告
v4.8.0 中已弃用:此属性已弃用。改用 [路由助手](/docs/helpers/route) 中的 `routePath()`。
¥**Deprecated in v4.8.0**: This property is deprecated. Use `routePath()` from [Route Helper](/docs/helpers/route) instead.
:::
你可以像这样在处理程序中检索已注册的路径:
¥You can retrieve the registered path within the handler like this:
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/posts/:id', (c) => {
return c.json({ path: c.req.routePath })
})
```
如果你访问 `/posts/123`,它将返回 `/posts/:id`:
¥If you access `/posts/123`, it will return `/posts/:id`:
```json
{ "path": "/posts/:id" }
```
## matchedRoutes
::: warning 警告
v4.8.0 中已弃用:此属性已弃用。改用 [路由助手](/docs/helpers/route) 中的 `matchedRoutes()`。
¥**Deprecated in v4.8.0**: This property is deprecated. Use `matchedRoutes()` from [Route Helper](/docs/helpers/route) instead.
:::
它在处理程序中返回匹配的路由,这对于调试很有用。
¥It returns matched routes within the handler, which is useful for debugging.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.use(async function logger(c, next) {
await next()
c.req.matchedRoutes.forEach(({ handler, method, path }, i) => {
const name =
handler.name ||
(handler.length < 2 ? '[handler]' : '[middleware]')
console.log(
method,
' ',
path,
' '.repeat(Math.max(10 - path.length, 0)),
name,
i === c.req.routeIndex ? '<- respond from here' : ''
)
})
})
```
## path
请求路径名。
¥The request pathname.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/about/me', async (c) => {
const pathname = c.req.path // `/about/me`
// ...
})
```
## url
请求 URL 字符串。
¥The request url strings.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/about/me', async (c) => {
const url = c.req.url // `http://localhost:8787/about/me`
// ...
})
```
## method
请求的方法名称。
¥The method name of the request.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/about/me', async (c) => {
const method = c.req.method // `GET`
// ...
})
```
## raw
原始 [`Request`](https://web.nodejs.cn/en-US/docs/Web/API/Request) 对象。
¥The raw [`Request`](https://web.nodejs.cn/en-US/docs/Web/API/Request) object.
```ts
// For Cloudflare Workers
app.post('/', async (c) => {
const metadata = c.req.raw.cf?.hostMetadata?
// ...
})
```
## cloneRawRequest()
从 HonoRequest 克隆原始请求对象。即使请求正文已被验证器或 HonoRequest 方法使用,它仍然有效。
¥Clones the raw Request object from a HonoRequest. Works even after the request body has been consumed by validators or HonoRequest methods.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
import { cloneRawRequest } from 'hono/request'
import { validator } from 'hono/validator'
app.post(
'/forward',
validator('json', (data) => data),
async (c) => {
// Clone after validation
const clonedReq = await cloneRawRequest(c.req)
// Does not throw the error
await clonedReq.json()
// ...
}
)
```
# 应用 - Hono
¥App - Hono
`Hono` 是主要对象。它将首先被导入并一直使用到最后。
¥`Hono` is the primary object.
It will be imported first and used until the end.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
//...
export default app // for Cloudflare Workers or Bun
```
## 方法
¥Methods
`Hono` 的一个实例具有以下方法。
¥An instance of `Hono` has the following methods.
* app.HTTP_METHOD([path,]handler|middleware...)
* app.all([path,]handler|middleware...)
* app.on(method|method[], path|path[], handler|middleware...)
* app.use([path,]middleware)
* app.route(path, [app])
* app.basePath(path)
* app.notFound(handler)
* app.onError(err, handler)
* app.mount(path, anotherApp)
* app.fire()
* app.fetch(request, env, event)
* app.request(path, options)
其中前半部分用于路由,请参考 [路由部分](/docs/api/routing)。
¥The first part of them is used for routing, please refer to the [routing section](/docs/api/routing).
## 未找到
¥Not Found
`app.notFound` 允许你自定义未找到响应。
¥`app.notFound` allows you to customize a Not Found Response.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.notFound((c) => {
return c.text('Custom 404 Message', 404)
})
```
:::warning 警告
`notFound` 方法仅从顶层应用调用。更多信息,请参阅此 [issue](https://github.com/honojs/hono/issues/3465#issuecomment-2381210165)。
¥The `notFound` method is only called from the top-level app. For more information, see this [issue](https://github.com/honojs/hono/issues/3465#issuecomment-2381210165).
:::
## 错误处理
¥Error Handling
`app.onError` 允许你处理未捕获的错误并返回自定义响应。
¥`app.onError` allows you to handle uncaught errors and return a custom Response.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.onError((err, c) => {
console.error(`${err}`)
return c.text('Custom Error Message', 500)
})
```
::: info 信息
如果父应用及其路由都具有 `onError` 处理程序,则路由级处理程序将获得优先级。
¥If both a parent app and its routes have `onError` handlers, the route-level handlers get priority.
:::
## fire()
::: warning 警告
`app.fire()` 已弃用。改用 `hono/service-worker` 中的 `fire()`。详情请参阅 [Service Worker 文档](/docs/getting-started/service-worker)。
¥**`app.fire()` is deprecated**. Use `fire()` from `hono/service-worker` instead. See the [Service Worker documentation](/docs/getting-started/service-worker) for details.
:::
`app.fire()` 自动添加全局 `fetch` 事件监听器。
¥`app.fire()` automatically adds a global `fetch` event listener.
这对于遵守 [Service Worker API](https://web.nodejs.cn/en-US/docs/Web/API/Service_Worker_API) 的环境(例如 [非 ES 模块 Cloudflare Workers](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/))很有用。
¥This can be useful for environments that adhere to the [Service Worker API](https://web.nodejs.cn/en-US/docs/Web/API/Service_Worker_API), such as [non-ES module Cloudflare Workers](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/).
`app.fire()` 为你执行以下操作:
¥`app.fire()` executes the following for you:
```ts
addEventListener('fetch', (event: FetchEventLike): void => {
event.respondWith(this.dispatch(...))
})
```
## fetch()
`app.fetch` 将成为应用的入口点。
¥`app.fetch` will be entry point of your application.
对于 Cloudflare Workers,你可以使用以下内容:
¥For Cloudflare Workers, you can use the following:
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
type Env = any
type ExecutionContext = any
// ---cut---
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
return app.fetch(request, env, ctx)
},
}
```
或者直接这样做:
¥or just do:
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
export default app
```
Bun:
```ts
export default app // [!code --]
export default { // [!code ++]
port: 3000, // [!code ++]
fetch: app.fetch, // [!code ++]
} // [!code ++]
```
## request()
`request` 是一种有用的测试方法。
¥`request` is a useful method for testing.
你可以传递 URL 或路径名来发送 GET 请求。`app` 将返回 `Response` 对象。
¥You can pass a URL or pathname to send a GET request.
`app` will return a `Response` object.
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
declare const test: (name: string, fn: () => void) => void
declare const expect: (value: any) => any
// ---cut---
test('GET /hello is ok', async () => {
const res = await app.request('/hello')
expect(res.status).toBe(200)
})
```
你还可以传递 `Request` 对象:
¥You can also pass a `Request` object:
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
declare const test: (name: string, fn: () => void) => void
declare const expect: (value: any) => any
// ---cut---
test('POST /message is ok', async () => {
const req = new Request('Hello!', {
method: 'POST',
})
const res = await app.request(req)
expect(res.status).toBe(201)
})
```
## mount()
`mount()` 允许你将使用其他框架构建的应用挂载到你的 Hono 应用中。
¥The `mount()` allows you to mount applications built with other frameworks into your Hono application.
```ts
import { Router as IttyRouter } from 'itty-router'
import { Hono } from 'hono'
// Create itty-router application
const ittyRouter = IttyRouter()
// Handle `GET /itty-router/hello`
ittyRouter.get('/hello', () => new Response('Hello from itty-router'))
// Hono application
const app = new Hono()
// Mount!
app.mount('/itty-router', ittyRouter.handle)
```
## 严格模式
¥strict mode
严格模式默认为 `true` 并区分以下路由。
¥Strict mode defaults to `true` and distinguishes the following routes.
* `/hello`
* `/hello/`
`app.get('/hello')` 与 `GET /hello/` 不匹配。
¥`app.get('/hello')` will not match `GET /hello/`.
通过将严格模式设置为 `false`,两个路径将被平等对待。
¥By setting strict mode to `false`, both paths will be treated equally.
```ts twoslash
import { Hono } from 'hono'
// ---cut---
const app = new Hono({ strict: false })
```
## 路由选项
¥router option
`router` 选项指定要使用的路由。默认路由为 `SmartRouter`。如果要使用 `RegExpRouter`,请将其传递给新的 `Hono` 实例:
¥The `router` option specifies which router to use. The default router is `SmartRouter`. If you want to use `RegExpRouter`, pass it to a new `Hono` instance:
```ts twoslash
import { Hono } from 'hono'
// ---cut---
import { RegExpRouter } from 'hono/router/reg-exp-router'
const app = new Hono({ router: new RegExpRouter() })
```
## 泛型
¥Generics
你可以传递泛型来指定 `c.set`/`c.get` 中使用的 Cloudflare Workers Bindings 和变量的类型。
¥You can pass Generics to specify the types of Cloudflare Workers Bindings and variables used in `c.set`/`c.get`.
```ts twoslash
import { Hono } from 'hono'
type User = any
declare const user: User
// ---cut---
type Bindings = {
TOKEN: string
}
type Variables = {
user: User
}
const app = new Hono<{
Bindings: Bindings
Variables: Variables
}>()
app.use('/auth/*', async (c, next) => {
const token = c.env.TOKEN // token is `string`
// ...
c.set('user', user) // user should be `User`
await next()
})
```
# 预设
¥Presets
Hono 有多个路由,每个路由都为特定目的而设计。你可以在 Hono 的构造函数中指定要使用的路由。
¥Hono has several routers, each designed for a specific purpose.
You can specify the router you want to use in the constructor of Hono.
为常见用例提供了预设,因此你不必每次都指定路由。从所有预设导入的 `Hono` 类是相同的,唯一的区别是路由。因此,你可以互换使用它们。
¥**Presets** are provided for common use cases, so you don't have to specify the router each time.
The `Hono` class imported from all presets is the same, the only difference being the router.
Therefore, you can use them interchangeably.
## `hono`
用法:
¥Usage:
```ts twoslash
import { Hono } from 'hono'
```
路由:
¥Routers:
```ts
this.router = new SmartRouter({
routers: [new RegExpRouter(), new TrieRouter()],
})
```
## `hono/quick`
用法:
¥Usage:
```ts twoslash
import { Hono } from 'hono/quick'
```
路由:
¥Router:
```ts
this.router = new SmartRouter({
routers: [new LinearRouter(), new TrieRouter()],
})
```
## `hono/tiny`
用法:
¥Usage:
```ts twoslash
import { Hono } from 'hono/tiny'
```
路由:
¥Router:
```ts
this.router = new PatternRouter()
```
## 我应该使用哪个预设?
¥Which preset should I use?
| 预设 | 适用平台 |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `hono` | 对于大多数用例,强烈建议这样做。虽然注册阶段可能比 `hono/quick` 慢,但一旦启动,它就会表现出高性能。它是使用 Deno、Bun 或 Node.js 构建的长寿命服务器的理想选择。它也适用于 Fastly Compute,因为在该平台上,路由注册发生在应用构建阶段。对于使用 v8 隔离的环境(例如 Cloudflare Workers、Deno Deploy),此预设也适用。因为隔离在启动后会持续一段时间。 |
| `hono/quick` | 此预设专为应用为每个请求初始化的环境而设计。 |
| `hono/tiny` | 这是最小的路由包,适用于资源有限的环境。 |
# 开发者体验
¥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 need to transpile it to JavaScript.
Hono is written in TypeScript and can make applications type-safe.
# 路由
¥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.
但是,RegExpRouter 并不支持所有路由模式,因此它通常与下面支持所有路由模式的其他路由结合使用。
¥However, RegExpRouter doesn't support all routing patterns, so it's usually used in combination with one of the other routers below that support all routing patterns.
## TrieRouter
TrieRouter 是使用 Trie-tree 算法的路由。与 RegExpRouter 类似,它不使用线性循环。
¥**TrieRouter** is the router using the Trie-tree algorithm.
Like RegExpRouter, it does not use linear loops.

此路由不如 RegExpRouter 快,但比 Express 路由快得多。TrieRouter 支持所有路由模式。
¥This router is not as fast as the RegExpRouter, but it is much faster than the Express router.
TrieRouter supports all patterns.
## SmartRouter
当你使用多个路由时,SmartRouter 非常有用。它会根据已注册的路由推断出最佳路由。Hono 默认使用 SmartRouter、RegExpRouter 和 TrieRouter:
¥**SmartRouter** is useful when you're using multiple routers. It selects the best router by inferring from the registered routers.
Hono uses SmartRouter, RegExpRouter, and TrieRouter 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
```
## 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, 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
```
# 基准
¥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)
# 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 included 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}
))}
)
}
```
# 中间件
¥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 = performance.now()
await next()
const end = performance.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.
# 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)
* WebAssembly(通过 [`wasi:http`][wasi-http] 使用 [WebAssembly 系统接口 (WASI)][wasi])
¥WebAssembly (w/ [WebAssembly System Interface (WASI)][wasi] via [`wasi:http`][wasi-http])
它也适用于 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**.
[wasi]: https://github.com/WebAssembly/wasi
[wasi-http]: https://github.com/WebAssembly/wasi-http
# 理念
¥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**.
# 示例
¥Examples
查看 [示例部分](/examples/)。
¥See the [Examples section](/examples/).
# 最佳实践
¥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
export type AppType = typeof app
```
如果你将 `app` 的类型传递给 `hc`,它将获得正确的类型。
¥If you pass the type of the `app` to `hc`, it will get the correct type.
```ts
import type { AppType } 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).
# 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] 为了使 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 type { 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)
}
```
### Cookies
要使客户端在每次请求时都发送 Cookie,请在创建客户端时将 `{ 'init': { 'credentials": 'include' } }` 添加到选项中。
¥To make the client send cookies with every request, add `{ 'init': { 'credentials": 'include' } }` to the options when you're creating the client.
```ts
// client.ts
const client = hc('http://localhost:8787/', {
init: {
credentials: 'include',
},
})
// This request will now include any cookies you might have set
const res = await client.posts.$get({
query: {
id: '123',
},
})
```
## 状态代码
¥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 = await getPost(id)
if (!post) {
return c.json({ error: 'not found' }, 404) // Specify 404
}
return c.json({ post }, 200) // Specify 200
}
)
```
或者,你可以使用模块扩展来扩展 `NotFoundResponse` 接口。这使得 `c.notFound()` 可以返回类型化的响应:
¥Alternatively, you can use module augmentation to extend `NotFoundResponse` interface. This allows `c.notFound()` to return a typed response:
```ts
// server.ts
import { Hono, TypedResponse } from 'hono'
declare module 'hono' {
interface NotFoundResponse
extends Response,
TypedResponse<{ error: string }, 404, 'json'> {}
}
const app = new Hono()
.get('/posts/:id', async (c) => {
const post = await getPost(c.req.param('id'))
if (!post) {
return c.notFound()
}
return c.json({ post }, 200)
})
.notFound((c) => c.json({ error: 'not found' }, 404))
export type AppType = typeof app
```
现在客户端可以正确推断 404 响应类型了。
¥Now the client can correctly infer the 404 response type.
## 路径参数
¥Path parameters
你还可以处理包含路径参数或查询值的路由。
¥You can also handle routes that include path parameters or query values.
```ts
const route = app.get(
'/posts/:id',
zValidator(
'query',
z.object({
page: z.coerce.number().optional(), // coerce to convert to number
})
),
(c) => {
// ...
return c.json({
title: 'Night',
body: 'Time to sleep',
})
}
)
```
路径参数和查询值都必须作为 `string` 传递,即使其底层值类型不同。
¥Both path parameters and query values **must** be passed as `string`, even if the underlying value is of a different type.
使用 `param` 指定要包含在路径中的字符串,使用 `query` 指定任何查询值。
¥Specify the string you want to include in the path with `param`, and any query values with `query`.
```ts
const res = await client.posts[':id'].$get({
param: {
id: '123',
},
query: {
page: '1', // `string`, converted by the validator to `number`
},
})
```
### 包含斜线
¥Include slashes
`hc` 函数不会对 `param` 的值进行 URL 编码。要在参数中包含斜杠,请使用 [正则表达式](/docs/api/routing#regexp)。
¥`hc` function does not URL-encode the values of `param`. To include slashes in parameters, use [regular expressions](/docs/api/routing#regexp).
```ts
// client.ts
// Requests /posts/123/456
const res = await client.posts[':id'].$get({
param: {
id: '123/456',
},
})
// server.ts
const route = app.get(
'/posts/:id{.+}',
zValidator(
'param',
z.object({
id: z.string(),
})
),
(c) => {
// id: 123/456
const { id } = c.req.valid('param')
// ...
}
)
```
> [!NOTE] 不含正则表达式的基本路径参数不匹配斜杠。如果你使用 hc 函数传递包含斜杠的 `param`,服务器可能无法按预期路由。建议使用 `encodeURIComponent` 对参数进行编码,以确保路由正确。
>
> ¥[!NOTE]
> Basic path parameters without regular expressions do not match slashes. If you pass a `param` containing slashes using the hc function, the server might not route as intended. Encoding the parameters using `encodeURIComponent` is the recommended approach to ensure correct routing.
## 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`
```
### Typed URL
你可以将基本 URL 作为第二个类型参数传递给 `hc`,以获取更精确的 URL 类型:
¥You can pass the base URL as the second type parameter to `hc` to get more precise URL types:
```ts
const client = hc(
'http://localhost:8787/'
)
const url = client.api.posts.$url()
// url is TypedURL with precise type information
// including protocol, host, and path
```
当你想要将 URL 用作 SWR 等库的类型安全键时,这非常有用。
¥This is useful when you want to use the URL as a type-safe key for libraries like SWR.
## 文件上传
¥File Uploads
你可以使用表单正文上传文件:
¥You can upload files using a form body:
```ts
// client
const res = await client.user.picture.$put({
form: {
file: new File([fileToUpload], filename, {
type: fileToUpload.type,
}),
},
})
```
```ts
// server
const route = app.put(
'/user/picture',
zValidator(
'form',
z.object({
file: z.instanceof(File),
})
)
// ...
)
```
## 自定义 `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('http://localhost', {
fetch: c.env.AUTH.fetch.bind(c.env.AUTH),
})
```
## Custom query serializer
你可以使用 `buildSearchParams` 选项自定义查询参数的序列化方式。当你需要数组或其他自定义格式的方括号表示法时,这非常有用:
¥You can customize how query parameters are serialized using the `buildSearchParams` option. This is useful when you need bracket notation for arrays or other custom formats:
```ts
const client = hc('http://localhost', {
buildSearchParams: (query) => {
const searchParams = new URLSearchParams()
for (const [k, v] of Object.entries(query)) {
if (v === undefined) {
continue
}
if (Array.isArray(v)) {
v.forEach((item) => searchParams.append(`${k}[]`, item))
} else {
searchParams.set(k, v)
}
}
return searchParams
},
})
```
## 推断
¥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
```
## 使用类型安全助手解析响应
¥Parsing a Response with type-safety helper
你可以使用 `parseResponse()` 助手轻松地解析来自 `hc` 的响应,并确保类型安全。
¥You can use `parseResponse()` helper to easily parse a Response from `hc` with type-safety.
```ts
import { parseResponse, DetailedError } from 'hono/client'
// result contains the parsed response body (automatically parsed based on Content-Type)
const result = await parseResponse(client.hello.$get()).catch(
(e: DetailedError) => {
console.error(e)
}
)
// parseResponse automatically throws an error if response is not ok
```
## 使用 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 type { 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
export type Client = ReturnType>
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.
# 助手工具
¥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)
# 验证
¥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` 或 `form` 时,请求必须包含匹配的 `content-type` 标头(例如,`json` 必须包含 `Content-Type: application/json`)。否则,请求正文将不会被解析,你将在回调中收到一个空对象 (`{}`) 作为值。
¥When you validate `json` or `form`, the request *must* contain a matching `content-type` header (e.g. `Content-Type: application/json` for `json`). Otherwise, the request body will not be parsed and you will receive an empty object (`{}`) as value in the callback.
使用 [`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) // {}
// ✅ 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 new 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 new 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 * as 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
}
)
```
## Standard Schema Validator Middleware
[标准模式](https://standardschema.dev/) 是一个规范,为 TypeScript 验证库提供通用接口。由 Zod、Valibot 和 ArkType 的维护者创建,旨在使生态系统工具能够与任何验证库配合使用,而无需自定义适配器。
¥[Standard Schema](https://standardschema.dev/) is a specification that provides a common interface for TypeScript validation libraries. It was created by the maintainers of Zod, Valibot, and ArkType to allow ecosystem tools to work with any validation library without needing custom adapters.
[Standard Schema Validator Middleware](https://github.com/honojs/middleware/tree/main/packages/standard-validator) 允许你将任何与标准模式兼容的验证库与 Hono 配合使用,从而让你可以灵活地选择所需的验证器,同时保持类型安全性的一致性。
¥The [Standard Schema Validator Middleware](https://github.com/honojs/middleware/tree/main/packages/standard-validator) lets you use any Standard Schema-compatible validation library with Hono, giving you the flexibility to choose your preferred validator while maintaining consistent type safety.
::: code-group
```sh [npm]
npm i @hono/standard-validator
```
```sh [yarn]
yarn add @hono/standard-validator
```
```sh [pnpm]
pnpm add @hono/standard-validator
```
```sh [bun]
bun add @hono/standard-validator
```
:::
从包中导入 `sValidator`:
¥Import `sValidator` from the package:
```ts
import { sValidator } from '@hono/standard-validator'
```
### 使用 Zod
¥With Zod
你可以将 Zod 与标准 Schema 验证器一起使用:
¥You can use Zod with the Standard Schema validator:
::: code-group
```sh [npm]
npm i zod
```
```sh [yarn]
yarn add zod
```
```sh [pnpm]
pnpm add zod
```
```sh [bun]
bun add zod
```
:::
```ts
import { z } from 'zod'
import { sValidator } from '@hono/standard-validator'
const schema = z.object({
name: z.string(),
age: z.number(),
})
app.post('/author', sValidator('json', schema), (c) => {
const data = c.req.valid('json')
return c.json({
success: true,
message: `${data.name} is ${data.age}`,
})
})
```
### 使用 Valibot
¥With Valibot
[Valibot](https://valibot.dev/) 是 Zod 的一个轻量级替代方案,采用模块化设计:
¥[Valibot](https://valibot.dev/) is a lightweight alternative to Zod with a modular design:
::: code-group
```sh [npm]
npm i valibot
```
```sh [yarn]
yarn add valibot
```
```sh [pnpm]
pnpm add valibot
```
```sh [bun]
bun add valibot
```
:::
```ts
import * as v from 'valibot'
import { sValidator } from '@hono/standard-validator'
const schema = v.object({
name: v.string(),
age: v.number(),
})
app.post('/author', sValidator('json', schema), (c) => {
const data = c.req.valid('json')
return c.json({
success: true,
message: `${data.name} is ${data.age}`,
})
})
```
### 使用 ArkType
¥With ArkType
[ArkType](https://arktype.io/) 提供 TypeScript 原生语法,用于运行时验证:
¥[ArkType](https://arktype.io/) offers TypeScript-native syntax for runtime validation:
::: code-group
```sh [npm]
npm i arktype
```
```sh [yarn]
yarn add arktype
```
```sh [pnpm]
pnpm add arktype
```
```sh [bun]
bun add arktype
```
:::
```ts
import { type } from 'arktype'
import { sValidator } from '@hono/standard-validator'
const schema = type({
name: 'string',
age: 'number',
})
app.post('/author', sValidator('json', schema), (c) => {
const data = c.req.valid('json')
return c.json({
success: true,
message: `${data.name} is ${data.age}`,
})
})
```
# 中间件
¥Middleware
中间件在端点 `Handler` 之前/之后运行。我们可以在分发之前获取 `Request`,或在分发之后操作 `Response`。
¥Middleware works before/after the endpoint `Handler`. We can get the `Request` before dispatching or manipulate the `Response` after dispatching.
## 中间件定义
¥Definition of Middleware
* 处理程序 - 应该返回 `Response` 对象。只会调用一个处理程序。
¥Handler - should return `Response` object. Only one handler will be called.
* 中间件 - 应该返回 `await next()` 而不返回任何值以调用下一个中间件,或者返回 `Response` 以提前退出。
¥Middleware - should `await next()` and return nothing to call the next Middleware, **or** return a `Response` to early-exit.
用户可以使用 `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
```
请注意,如果处理程序或任何中间件抛出异常,hono 会捕获该异常,并将其传递给 [你的 app.onError() 回调](/docs/api/hono#error-handling) 或自动将其转换为 500 响应,然后再将其返回到中间件链。这意味着 next() 永远不会抛出异常,因此无需将其封装在 try/catch/finally 中。
¥Note that if the handler or any middleware throws, hono will catch it and either pass it to [your app.onError() callback](/docs/api/hono#error-handling) or automatically convert it to a 500 response before returning it up the chain of middleware. This means that next() will never throw, so there is no need to wrap it in a try/catch/finally.
## 内置中间件
¥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.
我们可以探索各种 [第三方中间件](https://hono.nodejs.cn/docs/middleware/third-party)。例如,我们有 GraphQL Server Middleware、Sentry Middleware、Firebase Auth Middleware 等。
¥We can explore a variety of [third-party middleware](https://hono.nodejs.cn/docs/middleware/third-party).
For example, we have GraphQL Server Middleware, Sentry Middleware, Firebase Auth Middleware, and others.
# 常见问题
¥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.
# 杂项
¥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)
# 测试
¥Testing
[Vitest]: https://vitest.dev/
测试很重要。实际上,测试 Hono 的应用很容易。创建测试环境的方式因每个运行时而异,但基本步骤相同。在本节中,我们将使用 Cloudflare Workers 和 [Vitest][Vitest] 进行测试。
¥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 [Vitest].
::: tip 提示
Cloudflare 建议将 [Vitest][Vitest] 与 [@cloudflare/vitest-pool-workers](https://www.npmjs.com/package/@cloudflare/vitest-pool-workers) 一起使用。更多详细信息,请参阅 Cloudflare Workers 文档中的 [Vitest 集成](https://developers.cloudflare.com/workers/testing/vitest-integration/)。
¥Cloudflare recommends using [Vitest] with [@cloudflare/vitest-pool-workers](https://www.npmjs.com/package/@cloudflare/vitest-pool-workers). For more details, please refer to [Vitest integration](https://developers.cloudflare.com/workers/testing/vitest-integration/) in the Cloudflare Workers docs.
:::
## 请求和响应
¥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)
})
```
# 客户端组件
¥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"
}
}
```
或者,你可以在 `vite.config.ts` 中的 esbuild 转换选项中指定 `hono/jsx/dom`。
¥Alternatively, you can specify `hono/jsx/dom` in the esbuild transform options in `vite.config.ts`.
```ts
import { defineConfig } from 'vite'
export default defineConfig({
esbuild: {
jsxImportSource: 'hono/jsx/dom',
},
})
```
# Create-hono
`create-hono` 支持的命令行选项 - 运行 `npm create hono@latest`、`npx create-hono@latest` 或 `pnpm create hono@latest` 时运行的项目初始化程序。
¥Command-line options supported by `create-hono` - the project initializer that runs when you run `npm create hono@latest`, `npx create-hono@latest`, or `pnpm create hono@latest`.
> [!NOTE] 为什么是此页面?安装/快速入门示例通常显示最简的 `npm create hono@latest my-app` 命令。`create-hono` 支持多个有用的标志,你可以传递这些标志来自动化和自定义项目创建(选择模板、跳过提示、选择包管理器、使用本地缓存等等)。
>
> ¥[!NOTE]
> **Why this page?** The installation / quick-start examples often show a minimal `npm create hono@latest my-app` command. `create-hono` supports several useful flags you can pass to automate and customize project creation (select templates, skip prompts, pick a package manager, use local cache, and more).
## 传递参数:
¥Passing arguments:
使用 `npm create`(或 `npx`)时,用于初始化脚本的参数必须放在 `--` 之后。`--` 之后的任何内容都会转发到初始化程序。
¥When you use `npm create` (or `npx`) arguments intended for the initializer script must be placed **after** `--`. Anything after `--` is forwarded to the initializer.
::: code-group
```sh [npm]
# Forwarding arguments to create-hono (npm requires `--`)
npm create hono@latest my-app -- --template cloudflare-workers
```
```sh [yarn]
# "--template cloudflare-workers" selects the Cloudflare Workers template
yarn create hono my-app --template cloudflare-workers
```
```sh [pnpm]
# "--template cloudflare-workers" selects the Cloudflare Workers template
pnpm create hono@latest my-app --template cloudflare-workers
```
```sh [bun]
# "--template cloudflare-workers" selects the Cloudflare Workers template
bun create hono@latest my-app --template cloudflare-workers
```
```sh [deno]
# "--template cloudflare-workers" selects the Cloudflare Workers template
deno init --npm hono@latest my-app --template cloudflare-workers
```
:::
## 常用参数
¥Commonly used arguments
| 参数 | 描述 | 示例 |
| :---------------------- | :----------------------------------------------------------------- | :------------------------------ |
| `--template ` | 选择一个入门模板并跳过交互式模板提示。模板可以包含 `bun`、`cloudflare-workers`、`vercel` 等名称。 | `--template cloudflare-workers` |
| `--install` | 模板创建后自动安装依赖。 | `--install` |
| `--pm ` | 指定安装依赖时要运行的包管理器。常用值:`npm`, `pnpm`, `yarn`. | `--pm pnpm` |
| `--offline` | 使用本地缓存/模板,而不是获取最新的远程模板。适用于离线环境或确定性本地运行。 | `--offline` |
> [!NOTE] 确切的模板和可用选项集由 `create-hono` 项目维护。此文档页面总结了最常用的标志 - 请参阅下面链接的存储库以获取完整、权威的参考。
>
> ¥[!NOTE]
> The exact set of templates and available options is maintained by the `create-hono` project. This docs page summarizes the most-used flags — see the linked repository below for the full, authoritative reference.
## 示例流程
¥Example flows
### 最小,交互式
¥Minimal, interactive
```bash
npm create hono@latest my-app
```
这会提示你输入模板和选项。
¥This prompts you for template and options.
### 非交互式,选择模板和包管理器
¥Non-interactive, pick template and package manager
```bash
npm create hono@latest my-app -- --template vercel --pm npm --install
```
这将使用 `vercel` 模板创建 `my-app`,使用 `npm` 安装依赖,并跳过交互式提示。
¥This creates `my-app` using the `vercel` template, installs dependencies using `npm`, and skips the interactive prompts.
### 使用离线缓存(无网络)
¥Use offline cache (no network)
```bash
pnpm create hono@latest my-app --template deno --offline
```
## 故障排除和提示
¥Troubleshooting & tips
* 如果某个选项似乎无法识别,请确保在使用 `npm create`/`npx` 时使用 `--` 转发该选项。
¥If an option appears not to be recognized, make sure you're forwarding it with `--` when using `npm create` / `npx` .
* 要查看最新的模板和标志列表,请查阅 `create-hono` 存储库或在本地运行初始化程序并遵循其帮助输出。
¥To see the most current list of templates and flags, consult the `create-hono` repository or run the initializer locally and follow its help output.
## 链接和参考
¥Links & references
* `create-hono` 仓库:[create-hono](https://github.com/honojs/create-hono)
¥`create-hono` repository : [create-hono](https://github.com/honojs/create-hono)
# 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
:::info 信息
如果你直接使用 [快速入门](/docs/#quick-start),则主文件扩展名为 `.ts`。 - 你需要将其更改为 `.tsx` - 否则,你将根本无法运行该应用。你还需要修改 `package.json`(如果你使用的是 Deno,则修改为 `deno.json`)以反映该更改(例如,在开发脚本中,你应该使用 `bun run --hot src/index.tsx`,而不是 `bun run --hot src/index.ts`)。
¥If you are coming straight from the [Quick Start](/docs/#quick-start), the main file has a `.ts` extension - you need to change it to `.tsx` - otherwise you will not be able to run the application at all. You should additionally modify the `package.json` (or `deno.json` if you are using Deno) to reflect that change (e.g. instead of having `bun run --hot src/index.ts` in dev script, you should have `bun run --hot src/index.tsx`).
:::
`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
```
## 元数据提升
¥Metadata hoisting
你可以在组件内部直接编写文档元数据标签,例如 ``、`` 和 ``。这些标签将自动提升到文档的 `` 部分。当 `` 元素的渲染位置远离确定相应元数据的组件时,这尤其有用。
¥You can write document metadata tags such as ``, ``, and `` directly inside your components. These tags will be automatically hoisted to the `` section of the document. This is especially useful when the `` element is rendered far from the component that determines the appropriate metadata.
```tsx
import { Hono } from 'hono'
const app = new Hono()
app.use('*', async (c, next) => {
c.setRenderer((content) => {
return c.html(
{content}
)
})
await next()
})
app.get('/about', (c) => {
return c.render(
<>
About Page
about page content
>
)
})
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...}>
)
})
```
## StreamingContext
¥StreamingContext
你可以使用 `StreamingContext` 为 `Suspense` 和 `ErrorBoundary` 等流式组件提供配置。这对于将 nonce 值添加到这些组件为内容安全策略 (CSP) 生成的脚本标签中非常有用。
¥You can use `StreamingContext` to provide configuration for streaming components like `Suspense` and `ErrorBoundary`. This is useful for adding nonce values to script tags generated by these components for Content Security Policy (CSP).
```tsx
import { Suspense, StreamingContext } 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',
'Content-Security-Policy':
"script-src 'nonce-random-nonce-value'",
},
})
})
```
`scriptNonce` 值将自动添加到 `Suspense` 和 `ErrorBoundary` 组件生成的任何 `
```
`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))
```
### 使用 `fire()`
¥Using `fire()`
`fire()` 函数会自动为你调用 `addEventListener('fetch', handle(app))`,使代码更简洁。
¥The `fire()` function automatically calls `addEventListener('fetch', handle(app))` for you, making the code more concise.
```ts
import { Hono } from 'hono'
import { fire } from 'hono/service-worker'
const app = new Hono().basePath('/sw')
app.get('/', (c) => c.text('Hello World'))
fire(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.
# Google Cloud Run
[Google Cloud Run](https://cloud.google.com/run) 是由 Google Cloud 构建的无服务器平台。你可以运行代码来响应事件,Google 会自动为你管理底层计算资源。
¥[Google Cloud Run](https://cloud.google.com/run) is a serverless platform built by Google Cloud. You can run your code in response to events and Google automatically manages the underlying compute resources for you.
Google Cloud Run 使用容器来运行你的服务。这意味着你可以通过提供 Dockerfile 来使用任何你喜欢的运行时(例如,Deno 或 Bun)。如果未提供 Dockerfile,Google Cloud Run 将使用默认的 Nodejs 构建包。
¥Google Cloud Run uses containers to run your service. This means you can use any runtime you like (E.g., Deno or Bun) by providing a Dockerfile. If no Dockerfile is provided Google Cloud Run will use the default Nodejs buildpack.
本指南假设你已拥有 Google Cloud 账户和结算账户。
¥This guide assumes you already have a Google Cloud account and a billing account.
## 1. 安装 CLI
¥ Install the CLI
使用 Google Cloud Platform 时,最简单的方法是使用 [gcloud CLI](https://cloud.google.com/sdk/docs/install)。
¥When working with Google Cloud Platform it is easiest to work with the [gcloud CLI](https://cloud.google.com/sdk/docs/install).
例如,在 MacOS 上使用 Homebrew:
¥For example, on MacOS using Homebrew:
```sh
brew install --cask gcloud-cli
```
使用 CLI 进行身份验证。
¥Authenticate with the CLI.
```sh
gcloud auth login
```
## 2. 项目设置
¥ Project setup
创建一个项目。在提示符下接受自动生成的项目 ID。
¥Create a project. Accept the auto-generated project ID at the prompt.
```sh
gcloud projects create --set-as-default --name="my app"
```
为你的项目 ID 和项目编号创建环境变量,以便于重复使用。项目成功返回 `gcloud projects list` 命令可能需要大约 30 秒。
¥Create environment variables for your project ID and project number for easy reuse. It may take ~30 seconds before the project successfully returns with the `gcloud projects list` command.
```sh
PROJECT_ID=$(gcloud projects list \
--format='value(projectId)' \
--filter='name="my app"')
PROJECT_NUMBER=$(gcloud projects list \
--format='value(projectNumber)' \
--filter='name="my app"')
echo $PROJECT_ID $PROJECT_NUMBER
```
查找你的结算账户 ID。
¥Find your billing account ID.
```sh
gcloud billing accounts list
```
将上一个命令中的结算账户添加到项目中。
¥Add your billing account from the prior command to the project.
```sh
gcloud billing projects link $PROJECT_ID \
--billing-account=[billing_account_id]
```
启用所需的 API。
¥Enable the required APIs.
```sh
gcloud services enable run.googleapis.com \
cloudbuild.googleapis.com
```
更新服务账户权限以访问 Cloud Build。
¥Update the service account permissions to have access to Cloud Build.
```sh
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member=serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
--role=roles/run.builder
```
## 3. Hello World
使用 "create-hono" 命令启动你的项目。选择 `nodejs`。
¥Start your project with "create-hono" command. Select `nodejs`.
```sh
npm create hono@latest my-app
```
移至 `my-app` 并安装依赖。
¥Move to `my-app` and install the dependencies.
```sh
cd my-app
npm i
```
将 `src/index.ts` 中的端口更新为 `8080`。
¥Update the port in `src/index.ts` to be `8080`.
```ts
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => {
return c.text('Hello Hono!')
})
serve({
fetch: app.fetch,
port: 3000 // [!code --]
port: 8080 // [!code ++]
}, (info) => {
console.log(`Server is running on http://localhost:${info.port}`)
})
```
在本地运行开发服务器。然后,在你的 Web 浏览器中访问 [http://localhost:8080](http://localhost:8080)。
¥Run the development server locally. Then, access [http://localhost:8080](http://localhost:8080) in your Web browser.
```sh
npm run dev
```
## 4. 部署
¥ Deploy
启动部署并按照交互式提示操作(例如,选择区域)。
¥Start the deployment and follow the interactive prompts (E.g., select a region).
```sh
gcloud run deploy my-app --source . --allow-unauthenticated
```
## 更改运行时
¥Changing runtimes
如果你想使用 Deno 或 Bun 运行时(或自定义的 Nodejs 容器)进行部署,请添加一个包含你所需环境的 `Dockerfile`(以及可选的 `.dockerignore`)。
¥If you want to deploy using Deno or Bun runtimes (or a customised Nodejs container), add a `Dockerfile` (and optionally `.dockerignore`) with your desired environment.
有关容器化的信息,请参阅:
¥For information on containerizing please refer to:
* [Nodejs](/docs/getting-started/nodejs#building-deployment)
* [Bun](https://bun.com/guides/ecosystem/docker)
* [Deno](https://docs.deno.com/examples/google_cloud_run_tutorial)
# Next.js
Next.js 是一个灵活的 React 框架,它为你提供构建快速 Web 应用的构建块。
¥Next.js is a flexible React framework that gives you building blocks to create fast web applications.
你可以在使用 Node.js 运行时在 Next.js 上运行 Hono。在 Vercel 上,使用 Vercel 函数可以轻松地将 Hono 与 Next.js 一起部署。
¥You can run Hono on Next.js when using the Node.js runtime.\ On Vercel, deploying Hono with Next.js is easy by using Vercel Functions.
## 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'
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)
```
## 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.
## 页面路由
¥Pages Router
如果你使用 Pages Router,则需要先安装 Node.js 适配器。
¥If you use 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
```
:::
然后,你可以在 `pages/api/[[...route]].ts` 中使用从 `@hono/node-server/vercel` 导入的 `handle` 函数。
¥Then, you can utilize the `handle` function imported from `@hono/node-server/vercel` in `pages/api/[[...route]].ts`.
```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 Next.js!',
})
})
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
```
# 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/getting_started/installation/)。
¥First, install `deno` command.
Please refer to [the official document](https://docs.deno.com/runtime/getting_started/installation/).
## 2. 设置
¥ Setup
Deno 的启动器可用。使用 [`deno init`](https://docs.deno.com/runtime/reference/cli/init/) 命令启动你的项目。
¥A starter for Deno is available.
Start your project with the [`deno init`](https://docs.deno.com/runtime/reference/cli/init/) command.
```sh
deno init --npm hono --template=deno my-app
```
移入 `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
编辑 `main.ts`:
¥Edit `main.ts`:
```ts [main.ts]
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Deno!'))
Deno.serve(app.fetch)
```
## 4. 运行
¥ Run
在本地运行开发服务器。然后,在你的 Web 浏览器中访问 `http://localhost:8000`。
¥Run the development server locally. Then, access `http://localhost:8000` in your Web browser.
```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/deno` 导入的 `serveStatic`。
¥To serve static files, use `serveStatic` imported from `hono/deno`.
```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 是一个用于在云端运行 JavaScript 和 TypeScript 应用的无服务器平台。它通过 GitHub 部署等集成,为部署和运行应用提供了一个管理面板。
¥Deno Deploy is a serverless platform for running JavaScript and TypeScript applications in the cloud.
It provides a management plane for deploying and running applications through integrations like GitHub deployment.
Hono 也适用于 Deno Deploy。请参考 [官方文档](https://docs.deno.com/deploy/manual/)。
¥Hono also works on 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 [hello.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 和 JSR
¥npm and JSR
Hono 在 [npm](https://www.npmjs.com/package/hono) 和 [JSR](https://jsr.io/@hono/hono)(JavaScript 注册表)上均可用。你可以在 `deno.json` 中使用 `npm:hono` 或 `jsr:@hono/hono`:
¥Hono is available on both [npm](https://www.npmjs.com/package/hono) and [JSR](https://jsr.io/@hono/hono) (the JavaScript Registry). You can use either `npm:hono` or `jsr:@hono/hono` in your `deno.json`:
```json
{
"imports": {
"hono": "jsr:@hono/hono" // [!code --]
"hono": "npm:hono" // [!code ++]
}
}
```
要使用中间件,你需要在导入时使用 [Deno 目录](https://docs.deno.com/runtime/fundamentals/configuration/#custom-path-mappings) 语法。
¥To use middleware you need to use the [Deno directory](https://docs.deno.com/runtime/fundamentals/configuration/#custom-path-mappings) syntaxt in the import
```json
{
"imports": {
"hono/": "npm:/hono/"
}
}
```
使用第三方中间件时,你可能需要使用与中间件相同的注册表中的 Hono 来进行正确的 TypeScript 类型推断。例如,如果你使用 npm 中的中间件,则还应该使用 npm 中的 Hono:
¥When using third-party middleware, you may need to use Hono from the same registry as the middleware for proper TypeScript type inference. For example, if using the middleware from npm, you should also use Hono from npm:
```json
{
"imports": {
"hono": "npm:hono",
"zod": "npm:zod",
"@hono/zod-validator": "npm:@hono/zod-validator"
}
}
```
我们还在 [JSR](https://jsr.io/@hono) 上提供了许多第三方中间件包。在 JSR 上使用中间件时,请使用 JSR 中的 Hono:
¥We also provide many third-party middleware packages on [JSR](https://jsr.io/@hono). When using the middleware on JSR, use Hono from JSR:
```json
{
"imports": {
"hono": "jsr:@hono/hono",
"zod": "npm:zod",
"@hono/zod-validator": "jsr:@hono/zod-validator"
}
}
```
# Vercel
Vercel 是 AI 云,提供开发者工具和云基础设施,以构建、扩展和保护更快、更个性化的 Web 服务。
¥Vercel is the AI cloud, providing the developer tools and cloud infrastructure to build, scale, and secure a faster, more personalized web.
Hono 无需配置即可部署到 Vercel。
¥Hono can be deployed to Vercel with zero-configuration.
## 1. 设置
¥ Setup
Vercel 的启动器已提供。使用 "create-hono" 命令启动你的项目。为此示例选择 `vercel` 模板。
¥A starter for Vercel is available.
Start your project with "create-hono" command.
Select `vercel` 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
```
:::
下一步,我们将使用 Vercel CLI 在本地运行该应用。如果你尚未安装,请在 [Vercel CLI 文档](https://vercel.com/docs/cli) 之后全局安装。
¥We will use Vercel CLI to work on the app locally in the next step. If you haven't already, install it globally following [the Vercel CLI documentation](https://vercel.com/docs/cli).
## 2. Hello World
在项目的 `index.ts` 或 `src/index.ts` 中,将 Hono 应用导出为默认导出。
¥In the `index.ts` or `src/index.ts` of your project, export the Hono application as a default export.
```ts
import { Hono } from 'hono'
const app = new Hono()
const welcomeStrings = [
'Hello Hono!',
'To learn more about Hono on Vercel, visit https://vercel.com/docs/frameworks/backend/hono',
]
app.get('/', (c) => {
return c.text(welcomeStrings.join('\n\n'))
})
export default app
```
如果你从 `vercel` 模板开始,则已为你设置完毕。
¥If you started with the `vercel` template, this is already set up for you.
## 3. 运行
¥ Run
要在本地运行开发服务器:
¥To run the development server locally:
```sh
vercel dev
```
访问 `localhost:3000` 将返回文本响应。
¥Visiting `localhost:3000` will respond with a text response.
## 4. 部署
¥ Deploy
使用 `vc deploy` 部署到 Vercel。
¥Deploy to Vercel using `vc deploy`.
```sh
vercel deploy
```
## 延伸阅读
¥Further reading
[在 Vercel 文档中了解更多关于 Hono 的信息](https://vercel.com/docs/frameworks/backend/hono)。
¥[Learn more about Hono in the Vercel documentation](https://vercel.com/docs/frameworks/backend/hono).
# 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)
```
# 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/cdk/v2/guide/home.html) 可用于设置 IAM 角色、API 网关等功能。
¥When creating the application on AWS Lambda,
[CDK](https://docs.aws.amazon.com/cdk/v2/guide/home.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
npm i -D esbuild
mkdir lambda
touch lambda/index.ts
```
```sh [yarn]
mkdir my-app
cd my-app
cdk init app -l typescript
yarn add hono
yarn add -D esbuild
mkdir lambda
touch lambda/index.ts
```
```sh [pnpm]
mkdir my-app
cd my-app
cdk init app -l typescript
pnpm add hono
pnpm add -D esbuild
mkdir lambda
touch lambda/index.ts
```
```sh [bun]
mkdir my-app
cd my-app
cdk init app -l typescript
bun add hono
bun add -D esbuild
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/my-app-stack.ts`。
¥Edit `lib/my-app-stack.ts`.
```ts
import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
import * as lambda from 'aws-cdk-lib/aws-lambda'
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_22_X,
})
const fnUrl = fn.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.NONE,
})
new cdk.CfnOutput(this, 'lambdaUrl', {
value: fnUrl.url!,
})
}
}
```
最后,运行命令进行部署:
¥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'
import { streamText } from 'hono/streaming'
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)
}
})
})
export const handler = streamHandle(app)
```
# 阿里云函数计算
¥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
```
# 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!
## 将 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` 文件或 `.env` 文件。这些文件应使用 [dotenv](https://hexdocs.pm/dotenvy/dotenv-file-format.html) 语法进行格式化。例如:
¥To configure the environment variables for local development, create a `.dev.vars` file or a `.env` file in the root directory of the project.
These files should be formatted using the [dotenv](https://hexdocs.pm/dotenvy/dotenv-file-format.html) syntax. For example:
```
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.*` 获取代码中的环境变量。
¥Then we use the `c.env.*` to get the environment variables in our code.
::: info 信息
默认情况下,`process.env` 在 Cloudflare Workers 中不可用,因此建议从 `c.env` 获取环境变量。如果你想使用它,则需要启用 [`nodejs_compat_populate_process_env`](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#enable-auto-populating-processenv) 标志。你也可以从 `cloudflare:workers` 导入 `env`。详情请参阅 [如何在 Cloudflare 文档中访问 `env`](https://developers.cloudflare.com/workers/runtime-apis/bindings/#how-to-access-env)
¥By default, `process.env` is not available in Cloudflare Workers, so it is recommended to get environment variables from `c.env`. If you want to use it, you need to enable [`nodejs_compat_populate_process_env`](https://developers.cloudflare.com/workers/configuration/compatibility-flags/#enable-auto-populating-processenv) flag. You can also import `env` from `cloudflare:workers`. For details, please see [How to access `env` on Cloudflare docs](https://developers.cloudflare.com/workers/runtime-apis/bindings/#how-to-access-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)
# 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().basePath('/api')
app.get('/hello', (c) => {
return c.json({
message: `Hello, ${c.env.eventContext.data.user}!`, // 'Joe'
})
})
export const onRequest = handle(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
```
# 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)
```
如果你想优雅地关闭服务器,请像这样编写:
¥If you want to gracefully shut down the server, write it like this:
```ts
const server = serve(app)
// graceful shutdown
process.on('SIGINT', () => {
server.close()
process.exit(0)
})
process.on('SIGTERM', () => {
server.close((err) => {
if (err) {
console.error(err)
process.exit(1)
}
process.exit(0)
})
})
```
## 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 a request to the path `/static/*` comes in and you want to return 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 a request to the path `/hello.txt` or `/image.png` comes in and you want to return 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
::: code-group
```sh [npm]
npm run build
```
```sh [yarn]
yarn run build
```
```sh [pnpm]
pnpm run build
```
```sh [bun]
bun run build
```
::: info 信息
具有前端框架的应用可能需要使用 [Hono 的 Vite 插件](https://github.com/honojs/vite-plugins)。
¥Apps with a front-end framework may need to use [Hono's Vite plugins](https://github.com/honojs/vite-plugins).
:::
### Dockerfile
以下是 nodejs Dockerfile 的示例。
¥Here is an example of a nodejs Dockerfile.
```Dockerfile
FROM node:22-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"]
```
# 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).
# WebAssembly (w/ WASI)
[WebAssembly][wasm-core] 是一个安全、沙盒化、可移植的运行时环境,可在 Web 浏览器内外运行。
¥[WebAssembly][wasm-core] is a secure, sandboxed, portable runtime that runs inside and outside web browsers.
实践:
¥In practice:
* 各种语言(例如 JavaScript)会编译成 WebAssembly(`.wasm` 文件)。
¥Languages (like Javascript) *compile to* WebAssembly (`.wasm` files)
* WebAssembly 运行时(例如 [`wasmtime`][wasmtime] 或 [`jco`][jco])支持运行 WebAssembly 二进制文件。
¥WebAssembly runtimes (like [`wasmtime`][wasmtime] or [`jco`][jco]) enable *running* WebAssembly binaries
虽然核心 WebAssembly 无法访问本地文件系统或套接字等资源,但 [WebAssembly 系统接口][wasi] 可以介入,从而在 WebAssembly 工作负载下定义平台。
¥While core WebAssembly has *no* access to things like the local filesystem or sockets, the [WebAssembly System Interface][wasi]
steps in to enable defining a platform under WebAssebly workloads.
这意味着借助 WASI,WebAssembly 可以操作文件、套接字等等。
¥This means that *with* WASI, WebAssembly can operate on files, sockets, and much more.
::: info 信息
想亲自查看 WASI 接口吗?检出 [`wasi:http`][wasi-http]
¥Want to peek at the WASI interface yourself? check out [`wasi:http`][wasi-http]
:::
JS 中对 WebAssembly(含 WASI)的支持由 [StarlingMonkey][sm] 提供支持。由于 StarlingMonkey 和 Hono 都专注于 Web 标准,Hono 可以开箱即用地与支持 WASI 的 WebAssembly 生态系统协同工作。
¥Support for WebAssembly w/ WASI in JS is powered by [StarlingMonkey][sm], and thanks to the focus on Web standards in
both StarlingMonkey and Hono, **Hono works *out of the box with WASI-enabled WebAssembly ecosystems.**
[sm]: https://github.com/bytecodealliance/StarlingMonkey
[wasm-core]: https://webassembly.org/
[wasi]: https://wasi.dev/
[bca]: https://bytecodealliance.org/
[wasi-http]: https://github.com/WebAssembly/wasi-http
## 1. 设置
¥ Setup
WebAssembly JS 生态系统提供了一系列工具,方便用户快速上手构建支持 WASI 的 WebAssembly 组件:
¥The WebAssembly JS ecosystem provides tooling to make it easy to get started building WASI-enabled WebAssembly components:
* [StarlingMonkey][sm] 是 [SpiderMonkey][spidermonkey] 的一个分支,它编译成 WebAssembly 并支持组件。
¥[StarlingMonkey][sm] is a fork of [SpiderMonkey][spidermonkey] that compiles to WebAssembly and enables components
* [`componentize-js`][componentize-js] 将 JavaScript ES 模块转换为 WebAssembly 组件
¥[`componentize-js`][componentize-js] turns Javascript ES modules into WebAssembly components
* [`jco`][jco] 是一个多功能工具,可以构建组件、生成类型,并在 NodeJS 或浏览器等环境中运行组件。
¥[`jco`][jco] is a multi-tool that builds components, generates types, and runs components in environments like NodeJS or the browser
::: info 信息
WebAssembly 拥有开放的生态系统,并且是开源的,其核心项目主要由 [Bytecode Alliance][bca] 及其成员维护。
¥Webassembly has an open ecosystem and is open source, with core projects stewarded primarily by the [Bytecode Alliance][bca] and it's members.
我们始终欢迎新功能、问题、拉取请求和其他类型的贡献。
¥New features, issues, pull requests and other types of contributions are always welcome.
:::
虽然目前还没有基于 WebAssembly 的 Hono 入门项目,但你可以像创建其他项目一样创建一个 WebAssembly Hono 项目:
¥While a starter for Hono on WebAssembly is not yet available, you can start a WebAssembly Hono project just
like any other:
::: code-group
```sh [npm]
mkdir my-app
cd my-app
npm init
npm i hono
npm i -D @bytecodealliance/jco @bytecodealliance/componentize-js @bytecodealliance/jco-std
npm i -D rolldown
```
````sh [yarn]
mkdir my-app
cd my-app
npm init
yarn add hono
yarn add -D @bytecodealliance/jco @bytecodealliance/componentize-js @bytecodealliance/jco-std
yarn add -D rolldown
G```
```sh [pnpm]
mkdir my-app
cd my-app
pnpm init --init-type module
pnpm add hono
pnpm add -D @bytecodealliance/jco @bytecodealliance/componentize-js @bytecodealliance/jco-std
pnpm add -D rolldown
````
```sh [bun]
mkdir my-app
cd my-app
npm init
bun add hono
bun add -D @bytecodealliance/jco @bytecodealliance/componentize-js @bytecodealliance/jco-std
```
:::
::: info 信息
要确保你的项目使用 ES 模块,请确保在 `package.json` 中将 `type` 设置为 `"module"`。
¥To ensure your project uses ES modules, ensure `type` is set to `"module"` in `package.json`
:::
进入 `my-app` 文件夹后,安装依赖并进行初始化。 TypeScript:
¥After entering the `my-app` folder, install dependencies, and initialize Typescript:
::: code-group
```sh [npm]
npm i
npx tsc --init
```
```sh [yarn]
yarn
yarn tsc --init
```
```sh [pnpm]
pnpm i
pnpm exec --init
```
```sh [bun]
bun i
```
:::
有了基本的 TypeScript 配置文件 (`tsconfig.json`) 后,请确保它包含以下配置:
¥Once you have a basic typescript configuration file (`tsconfig.json`), please ensure it has the following configuration:
* 将 `compilerOptions.module` 设置为 `"nodenext"`
¥`compilerOptions.module` set to `"nodenext"`
由于 `componentize-js`(以及重用它的 `jco`)仅支持单个 JS 文件,因此需要进行打包,可以使用 [`rolldown`][rolldown] 创建单个文件包。
¥Since `componentize-js` (and `jco` which re-uses it) supports only single JS files,
bundling is necessary, so [`rolldown`][rolldown] can be used to create a single file bundle.
可以使用如下的下拉配置 (`rolldown.config.mjs`):
¥A Rolldown configuration (`rolldown.config.mjs`) like the following can be used:
```js
import { defineConfig } from 'rolldown'
export default defineConfig({
input: 'src/component.ts',
external: /wasi:.*/,
output: {
file: 'dist/component.js',
format: 'esm',
},
})
```
::: info 信息
你可以随意使用任何你更熟悉的打包工具(例如 `rolldown`、`esbuild`、`rollup` 等)。
¥Feel free to use any other bundlers that you're more comfortable with (`rolldown`, `esbuild`, `rollup`, etc)
:::
[jco]: https://github.com/bytecodealliance/jco
[componentize-js]: https://github.com/bytecodealliance/componentize-js
[rolldown]: https://rolldown.nodejs.cn
[spidermonkey]: https://spidermonkey.dev/
## 2. Setup WIT interface & dependencies
[WebAssembly 接口类型 (WIT)][wit] 是一种接口定义语言 ("IDL"),它规定了 WebAssembly 组件使用的功能 ("imports") 以及它提供的功能 ("exports")。
¥[WebAssembly Inteface Types (WIT)][wit] is an Interface Definition Language ("IDL") that governs what functionality
a WebAssembly component uses ("imports"), and what it provides ("exports").
在标准化的 WIT 接口中,[`wasi:http`][wasi-http] 用于处理 HTTP 请求(无论是接收还是发送请求)。由于我们打算创建一个 Web 服务器,因此我们的组件必须在其 [WIT 世界][wit-world] 中声明使用 `wasi:http/incoming-handler`:
¥Amongst the standardized WIT interfaces, [`wasi:http`][wasi-http] is for dealing with HTTP requests (whether it's
receiving them or sending them out), and since we intend to make a web server, our component must declare the use
of `wasi:http/incoming-handler` in it's [WIT world][wit-world]:
首先,让我们在名为 `wit/component.wit` 的文件中设置组件的 WIT 世界:
¥First, let's set up the component's WIT world in a file called `wit/component.wit`:
```txt
package example:hono;
world component {
export wasi:http/incoming-handler@0.2.6;
}
```
简而言之,上面的 WIT 文件表示我们的组件 "providers" 具备 "receiving"/"处理传入请求" HTTP 请求的功能。
¥Put simply, the WIT file above means that our component "providers" the functionality of "receiving"/"handling incoming"
HTTP requests.
`wasi:http/incoming-handler` 接口依赖于上游标准化的 WIT 接口(例如请求结构规范)。
¥The `wasi:http/incoming-handler` interface relies on upstream standardized WIT interfaces (specifications
on how requests are structured, etc).
要引入第三方(由字节码联盟维护)WIT 接口,我们可以使用 [`wkg`][wkg]:
¥To pull those third party (Bytecode Alliance maintained) WIT interaces, one tool we can use is [`wkg`][wkg]:
```sh
wkg wit fetch
```
`wkg` 运行完毕后,你应该会在 `wit` 文件夹中看到一个新的 `deps` 文件夹,与 `component.wit` 文件夹并列:
¥Once `wkg` has finished running, you should find your `wit` folder populated with a new `deps` folder alongside `component.wit`:
```
wit
├── component.wit
└── deps
├── wasi-cli-0.2.6
│ └── package.wit
├── wasi-clocks-0.2.6
│ └── package.wit
├── wasi-http-0.2.6
│ └── package.wit
├── wasi-io-0.2.6
│ └── package.wit
└── wasi-random-0.2.6
└── package.wit
```
[wkg]: https://github.com/bytecodealliance/wasm-pkg-tools
[wit-world]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#wit-worlds
[wit]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md
## 3. Hello Wasm
要在 WebAssembly 中构建 HTTP 服务器,我们可以使用 [`jco-std`][jco-std] 项目,该项目包含一些辅助函数,使用户体验与标准的 Hono 体验非常相似。
¥To build a HTTP server in WebAssembly, we can make use of the [`jco-std`][jco-std] project, which
contains helpers that make the experience very similar to the standard Hono experience.
让我们用一个名为 `src/component.ts` 的文件,将一个基本的 Hono 应用作为 WebAssembly 组件,来构建我们的 `component` 世界:
¥Let's fulfill our `component` world with a basic Hono application as a WebAssembly component in
a file called `src/component.ts`:
```ts
import { Hono } from 'hono'
import { fire } from '@bytecodealliance/jco-std/wasi/0.2.6/http/adapters/hono/server'
const app = new Hono()
app.get('/hello', (c) => {
return c.json({ message: 'Hello from WebAssembly!' })
})
fire(app)
// Although we've called `fire()` with wasi HTTP configured for use above,
// we still need to actually export the `wasi:http/incoming-handler` interface object,
// as jco and componentize-js will be looking for the ES module export that matches the WASI interface.
export { incomingHandler } from '@bytecodealliance/jco-std/wasi/0.2.6/http/adapters/hono/server'
```
## 4. Build
由于我们使用了 Rolldown(并且它已配置为处理 TypeScript 编译),我们可以用它来构建和打包:
¥Since we're using Rolldown (and it's configured to handle Typescript compilation), we can use it to build and bundle:
::: code-group
```sh [npm]
npx rolldown -c
```
```sh [yarn]
yarn rolldown -c
```
```sh [pnpm]
pnpm exec rolldown -c
```
```sh [bun]
bun build --target=bun --outfile=dist/component.js ./src/component.ts
```
:::
::: info 信息
打包步骤是必要的,因为 WebAssembly JS 生态系统工具目前仅支持单个 JS 文件,而我们希望包含 Hono 及其相关库。
¥The bundling step is necessary because WebAssembly JS ecosystem tooling only currently supports a single JS file,
and we'd like to include Hono along with related libraries.
对于需求较为简单的组件,打包工具并非必需。
¥For components with simpler requirements, bundlers are not necessary.
:::
要构建你的 WebAssembly 组件,请使用 `jco`(并间接使用 `componentize-js`):
¥To build your WebAssembly component, use `jco` (and indirectly `componentize-js`):
::: code-group
```sh [npm]
npx jco componentize -w wit -o dist/component.wasm dist/component.js
```
```sh [yarn]
yarn jco componentize -w wit -o dist/component.wasm dist/component.js
```
```sh [pnpm]
pnpm exec jco componentize -w wit -o dist/component.wasm dist/component.js
```
```sh [bun]
bun run jco componentize -w wit -o dist/component.wasm dist/component.js
```
:::
## 3. 运行
¥ Run
要运行你的 Hono WebAssembly HTTP 服务器,你可以使用任何支持 WASI 的 WebAssembly 运行时:
¥To run your Hono WebAssembly HTTP server, you can use any WASI-enabled WebAssembly runtime:
* [`wasmtime`][wasmtime]
* `jco`(运行于 NodeJS)
¥`jco` (runs in NodeJS)
在本指南中,我们将使用 `jco serve`,因为它已预先安装。
¥In this guide, we'll use `jco serve` since it's already installed.
::: warning 警告
`jco serve` 主要用于开发,不建议用于生产环境。
¥`jco serve` is meant for development, and is not recommended for production use.
:::
[wasmtime]: https://wasmtime.dev
::: code-group
```sh [npm]
npx jco serve dist/component.wasm
```
```sh [yarn]
yarn jco serve dist/component.wasm
```
```sh [pnpm]
pnpm exec jco serve dist/component.wasm
```
```sh [bun]
bun run jco serve dist/component.wasm
```
:::
你应该看到类似以下的输出:
¥You should see output like the following:
```
$ npx jco serve dist/component.wasm
Server listening @ localhost:8000...
```
向 `localhost:8000/hello` 发送请求将生成你在 Hono 应用中指定的 JSON 输出。
¥Sending a request to `localhost:8000/hello` will produce the JSON output you've specified in your Hono application.
你应该看到类似以下的输出:
¥You should see output like the following:
```json
{ "message": "Hello from WebAssembly!" }
```
::: info 信息
`jco serve` 的工作原理是将 WebAssembly 组件转换为基本的 WebAssembly 核心模块,使其能够在 NodeJS 和浏览器等运行时环境中运行。
¥`jco serve` works by converting the WebAssembly component into a basic WebAssembly coremodule,
so that it can be run in runtimes like NodeJS and the browser.
此过程通常通过 `jco transpile` 运行,它允许我们使用 NodeJS 等 JS 引擎和浏览器(浏览器可能使用 V8 或其他 JavaScript 引擎)作为 WebAssembly 组件运行时。
¥This process is normally run via `jco transpile`, and is the way we can use JS engines like NodeJS
and the browser (which may use V8 or other Javascript engines) as WebAssembly Component runtimes.
关于 `jco transpile` 如何超出本指南的范围,你可以在 [Jco 书籍][jco-book] 中了解更多信息。
¥How `jco transpile` is outside the scope of this guide, you can read more about it in [the Jco book][jco-book]
:::
## More information
要了解更多关于 WASI、WebAssembly 组件等信息,请参阅以下资源:
¥To learn moreabout WASI, WebAssembly components and more, see the following resources:
* [BytecodeAlliance 组件模型手册][cm-book]
¥[BytecodeAlliance Component Model book][cm-book]
* [`jco` 代码库][jco]
¥[`jco` codebase][jco]
* [`jco` 示例组件][jco-example-components](特别是 [Hono 示例][jco-example-component-hono])
¥[`jco` example components][jco-example-components] (in particular the [Hono example][jco-example-component-hono])
* [Jco 手册][jco-book]
¥[Jco book][jco-book]
* [`componentize-js` 代码库][componentize-js]
¥[`componentize-js` codebase][componentize-js]
* [StarlingMonkey 代码库][sm]
¥[StarlingMonkey codebase][sm]
[cm-book]: https://component-model.bytecodealliance.org/
[jco-book]: https://bytecodealliance.github.io/jco/
[jco-example-components]: https://github.com/bytecodealliance/jco/tree/main/examples/components
[jco-example-component-hono]: https://github.com/bytecodealliance/jco/tree/main/examples/components/http-server-hono
# Fastly 计算
¥Fastly Compute
[Fastly 计算](https://www.fastly.com/products/edge-compute) 是一个高级的边缘计算系统,它允许你使用自己喜欢的语言在 Fastly 的全球边缘网络上运行代码。Hono 也适用于 Fastly Compute。
¥[Fastly Compute](https://www.fastly.com/products/edge-compute) is an advanced edge computing system that runs your code, in your favorite language, on Fastly's global edge network. Hono also works on Fastly Compute.
你可以使用 [Fastly CLI](https://www.fastly.com/documentation/reference/tools/cli/) 在本地开发应用,并通过几个命令将其发布。[Fastly CLI](https://www.fastly.com/documentation/reference/tools/cli/) 作为模板的一部分自动安装在本地。
¥You can develop the application locally and publish it with a few commands using [Fastly CLI](https://www.fastly.com/documentation/reference/tools/cli/), which is installed locally automatically as part of the template.
## 1. 设置
¥ 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
```
:::
## 2. Hello World
编辑 `src/index.ts`:
¥Edit `src/index.ts`:
```ts
// src/index.ts
import { Hono } from 'hono'
import { fire } from '@fastly/hono-fastly-compute'
const app = new Hono()
app.get('/', (c) => c.text('Hello Fastly!'))
fire(app)
```
> [!NOTE] 当你在应用顶层使用 `@fastly/hono-fastly-compute'` 中的 `fire`(或 `buildFire()`)时,建议使用 `'hono'` 中的 `Hono` 而不是 `'hono/quick'`,因为 `fire` 会导致其路由在应用初始化阶段构建内部数据。
>
> ¥[!NOTE]
> When using `fire` (or `buildFire()`) from `@fastly/hono-fastly-compute'` at the top level of your application, it is suitable to use `Hono` from `'hono'` rather than `'hono/quick'`, because `fire` causes its router to build its internal data during the application initialization phase.
## 3. 运行
¥ 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 start
```
```sh [yarn]
yarn start
```
```sh [pnpm]
pnpm run start
```
```sh [bun]
bun run start
```
:::
## 4. 部署
¥ Deploy
要构建你的应用并将其部署到你的 Fastly 账户,请输入以下命令。首次部署应用时,系统将提示你在账户中创建新服务。
¥To build and deploy your application to your Fastly account, type the following command. The first time you deploy the application, you will be prompted to create a new service in your account.
如果你还没有账户,则必须拥有 [创建你的 Fastly 账户](https://www.fastly.com/signup/)。
¥If you don't have an account yet, you must [create your Fastly account](https://www.fastly.com/signup/).
::: code-group
```sh [npm]
npm run deploy
```
```sh [yarn]
yarn deploy
```
```sh [pnpm]
pnpm run deploy
```
```sh [bun]
bun run deploy
```
:::
## 绑定
¥Bindings
在 Fastly Compute 中,你可以绑定 Fastly 平台资源,例如键值存储、配置存储、密钥存储、后端、访问控制列表、命名日志流和环境变量。你可以通过 `c.env` 访问它们,并且它们将拥有各自的 SDK 类型。
¥In Fastly Compute, you can bind Fastly platform resources, such as KV Stores, Config Stores, Secret Stores, Backends, Access Control Lists, Named Log Streams, and Environment Variables. You can access them through `c.env`, and will have their individual SDK types.
要使用这些绑定,请从 `@fastly/hono-fastly-compute` 中导入 `buildFire` 而不是 `fire`。定义你的 [bindings](https://github.com/fastly/compute-js-context?tab=readme-ov-file#typed-bindings-with-buildcontextproxy) 并将其传递给 [`buildFire()`](https://github.com/fastly/hono-fastly-compute?tab=readme-ov-file#basic-example) 以获取 `fire`。然后在构建 `Hono` 时,使用 `fire.Bindings` 定义 `Env` 类型。
¥To use these bindings, import `buildFire` instead of `fire` from `@fastly/hono-fastly-compute`. Define your [bindings](https://github.com/fastly/compute-js-context?tab=readme-ov-file#typed-bindings-with-buildcontextproxy) and pass them to [`buildFire()`](https://github.com/fastly/hono-fastly-compute?tab=readme-ov-file#basic-example) to obtain `fire`. Then use `fire.Bindings` to define your `Env` type as you construct `Hono`.
```ts
// src/index.ts
import { buildFire } from '@fastly/hono-fastly-compute'
const fire = buildFire({
siteData: 'KVStore:site-data', // I have a KV Store named "site-data"
})
const app = new Hono<{ Bindings: typeof fire.Bindings }>()
app.put('/upload/:key', async (c, next) => {
// e.g., Access the KV Store
const key = c.req.param('key')
await c.env.siteData.put(key, c.req.body)
return c.text(`Put ${key} successfully!`)
})
fire(app)
```
# 第三方中间件
¥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)
* [Casbin](https://github.com/honojs/middleware/tree/main/packages/casbin)
* [Clerk Auth](https://github.com/honojs/middleware/tree/main/packages/clerk-auth)
* [Cloudflare Access](https://github.com/honojs/middleware/tree/main/packages/cloudflare-access)
* [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)
* [Stytch Auth](https://github.com/honojs/middleware/tree/main/packages/stytch-auth)
### 验证器
¥Validators
* [Ajv 验证器](https://github.com/honojs/middleware/tree/main/packages/ajv-validator)
¥[Ajv Validator](https://github.com/honojs/middleware/tree/main/packages/ajv-validator)
* [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/class-validator)
¥[Class Validator](https://github.com/honojs/middleware/tree/main/packages/class-validator)
* [Conform 验证器](https://github.com/honojs/middleware/tree/main/packages/conform-validator)
¥[Conform Validator](https://github.com/honojs/middleware/tree/main/packages/conform-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)
* [标量](https://github.com/scalar/scalar/tree/main/integrations/hono)
¥[Scalar](https://github.com/scalar/scalar/tree/main/integrations/hono)
* [Swagger UI](https://github.com/honojs/middleware/tree/main/packages/swagger-ui)
* [Swagger 编辑器](https://github.com/honojs/middleware/tree/main/packages/swagger-editor)
¥[Swagger Editor](https://github.com/honojs/middleware/tree/main/packages/swagger-editor)
* [Hono OpenAPI](https://github.com/rhinobase/hono-openapi)
* [hono-zod-openapi](https://github.com/paolostyle/hono-zod-openapi)
### Development
* [ESLint 配置](https://github.com/honojs/middleware/tree/main/packages/eslint-config)
¥[ESLint Config](https://github.com/honojs/middleware/tree/main/packages/eslint-config)
* [SSG 插件基础版](https://github.com/honojs/middleware/tree/main/packages/ssg-plugins-essential)
¥[SSG Plugin Essential](https://github.com/honojs/middleware/tree/main/packages/ssg-plugins-essential)
### Monitoring / Tracing
* [Apitally(API 监控和分析)](https://docs.apitally.io/frameworks/hono)
¥[Apitally (API monitoring & analytics)](https://docs.apitally.io/frameworks/hono)
* [Highlight.io](https://www.highlight.io/docs/getting-started/backend-sdk/js/hono)
* [OpenTelemetry](https://github.com/honojs/middleware/tree/main/packages/otel)
* [Prometheus 指标](https://github.com/honojs/middleware/tree/main/packages/prometheus)
¥[Prometheus Metrics](https://github.com/honojs/middleware/tree/main/packages/prometheus)
* [Sentry](https://github.com/honojs/middleware/tree/main/packages/sentry)
### Server / Adapter
* [GraphQL 服务器](https://github.com/honojs/middleware/tree/main/packages/graphql-server)
¥[GraphQL Server](https://github.com/honojs/middleware/tree/main/packages/graphql-server)
* [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)
* [tRPC 服务器](https://github.com/honojs/middleware/tree/main/packages/trpc-server)
¥[tRPC Server](https://github.com/honojs/middleware/tree/main/packages/trpc-server)
### Transpiler
* [Bun Transpiler](https://github.com/honojs/middleware/tree/main/packages/bun-transpiler)
* [esbuild Transpiler](https://github.com/honojs/middleware/tree/main/packages/esbuild-transpiler)
### UI / Renderer
* [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)
### Utilities
* [Bun Compress](https://github.com/honojs/middleware/tree/main/packages/bun-compress)
* [Cap Checkpoint](https://capjs.js.org/guide/middleware/hono.html)
* [事件触发器](https://github.com/honojs/middleware/tree/main/packages/event-emitter)
¥[Event Emitter](https://github.com/honojs/middleware/tree/main/packages/event-emitter)
* [Geo](https://github.com/ktkongtong/hono-geo-middleware/tree/main/packages/middleware)
* [Hono 速率限制器](https://github.com/rhinobase/hono-rate-limiter)
¥[Hono Rate Limiter](https://github.com/rhinobase/hono-rate-limiter)
* [Hono 简单 DI](https://github.com/maou-shonen/hono-simple-DI)
¥[Hono Simple DI](https://github.com/maou-shonen/hono-simple-DI)
* [jsonv-ts(验证器、OpenAPI、MCP)](https://github.com/dswbx/jsonv-ts)
¥[jsonv-ts (Validator, OpenAPI, MCP)](https://github.com/dswbx/jsonv-ts)
* [MCP](https://github.com/honojs/middleware/tree/main/packages/mcp)
* [RONIN(数据库)](https://github.com/ronin-co/hono-client)
¥[RONIN (Database)](https://github.com/ronin-co/hono-client)
* [会话](https://github.com/honojs/middleware/tree/main/packages/session)
¥[Session](https://github.com/honojs/middleware/tree/main/packages/session)
* [tsyringe](https://github.com/honojs/middleware/tree/main/packages/tsyringe)
* [基于用户代理的拦截器](https://github.com/honojs/middleware/tree/main/packages/ua-blocker)
¥[User Agent based Blocker](https://github.com/honojs/middleware/tree/main/packages/ua-blocker)
# 语言中间件
¥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
})
```
# 压缩中间件
¥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.
:::
## 导入
¥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.
# 服务器计时中间件
¥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,
wrapTime,
} 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');
// ...or you can also just wrap a Promise using this function:
const data = await wrapTime(c, 'db', db.findMany(...));
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).
# 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)
}
)
)
```
# 主体限制中间件
¥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
})
```
# 基本身份验证中间件
¥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)
)
)
```
# JWT Auth 中间件
¥JWT Auth Middleware
JWT Auth 中间件通过使用 JWT 验证令牌来提供身份验证。如果未设置 `cookie` 选项,中间件将检查 `Authorization` 标头。你可以使用 `headerName` 选项自定义标头名称。
¥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. You can customize the header name using the `headerName` option.
:::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',
issuer: 'my-trusted-issuer',
})
)
app.get('/auth/page', (c) => {
const payload = c.get('jwtPayload')
return c.json(payload) // eg: { "sub": "1234567890", "name": "John Doe", "iat": 1516239022, "iss": "my-trusted-issuer" }
})
```
::: 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`.
### 标头名称:`string`
¥ headerName: `string`
用于查找 JWT 令牌的标头名称。默认为 `Authorization`。
¥The name of the header to look for the JWT token. The default is `Authorization`.
```ts
app.use(
'/auth/*',
jwt({
secret: 'it-is-very-secret',
headerName: 'x-custom-auth-header',
})
)
```
### verifyOptions:`VerifyOptions`
控制令牌验证的选项。
¥Options controlling verification of the token.
#### verifyOptions.iss:`string | RexExp`
用于令牌验证的预期发行者。如果未设置,则不会检查 `iss` 声明。
¥The expected issuer used for token verification. The `iss` claim will **not** be checked if this isn't set.
#### verifyOptions.nbf:`boolean`
如果存在 `nbf`(不早于)声明,则会对其进行验证,并将其设置为 `true`。默认为 `true`。
¥The `nbf` (not before) claim will be verified if present and this is set to `true`. The default is `true`.
#### verifyOptions.iat:`boolean`
如果存在 `iat`(不早于)声明,则会对其进行验证,并将其设置为 `true`。默认为 `true`。
¥The `iat` (not before) claim will be verified if present and this is set to `true`. The default is `true`.
#### verifyOptions.exp:`boolean`
如果存在 `exp`(不早于)声明,则会对其进行验证,并将其设置为 `true`。默认为 `true`。
¥The `exp` (not before) claim will be verified if present and this is set to `true`. The default is `true`.
# 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',
}
)
})
```
# 漂亮的 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`.
### force:`boolean`
当设置为 `true` 时,无论查询参数如何,JSON 响应始终会被美化。默认为 `false`。
¥When set to `true`, JSON responses are always prettified regardless of the query parameter. The default is `false`.
# 请求 ID 中间件
¥Request ID Middleware
请求 ID 中间件为每个请求生成一个唯一的 ID,你可以在处理程序中使用该 ID。
¥Request ID Middleware generates a unique ID for each request, which you can use in your handlers.
::: info 信息
Node.js:此中间件使用 `crypto.randomUUID()` 生成 ID。全局 `crypto` 是在 Node.js 20 或更高版本中引入的。因此,在此之前的版本中可能会出现错误。在这种情况下,请指定 `generator`。但是,如果你使用 [Node.js 适配器](https://github.com/honojs/node-server),它会自动全局设置 `crypto`,因此无需执行此操作。
¥**Node.js**: This middleware uses `crypto.randomUUID()` to generate IDs. The global `crypto` was introduced in Node.js version 20 or later. Therefore, errors may occur in versions earlier than that. In that case, please specify `generator`. However, if you are using [the Node.js adapter](https://github.com/honojs/node-server), it automatically sets `crypto` globally, so this is not necessary.
:::
## 导入
¥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
}>()
```
### 设置请求 ID
¥Set Request ID
你在标头中设置了一个自定义请求 ID(默认值:`X-Request-Id`),中间件将使用该值,而不是生成新的 ID:
¥You set a custom request ID in the header (default: `X-Request-Id`), the middleware will use that value instead of generating a new one:
```ts
const app = new Hono()
app.use('*', requestId())
app.get('/', (c) => {
return c.text(`${c.get('requestId')}`)
})
const res = await app.request('/', {
headers: {
'X-Request-Id': 'your-custom-id',
},
})
console.log(await res.text()) // your-custom-id
```
如果你想禁用此功能,请将 [`headerName` 选项](#headername-string) 设置为空字符串。
¥If you want to disable this feature, set [`headerName` option](#headername-string) to an empty string.
## 选项
¥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()`.
## 特定平台的请求 ID
¥Platform specific Request IDs
某些平台(例如 AWS Lambda)已为每个请求生成自己的请求 ID。无需任何其他配置,此中间件不会感知这些特定的请求 ID,并会生成一个新的请求 ID。这可能会导致查看应用日志时出现混淆。
¥Some platform (such as AWS Lambda) already generate their own Request IDs per request.
Without any additional configuration, this middleware is unaware of these specific Request IDs
and generates a new Request ID. This can lead to confusion when looking at your application logs.
要统一这些 ID,请使用 `generator` 函数捕获特定于平台的请求 ID 并在此中间件中使用它。
¥To unify these IDs, use the `generator` function to capture the platform specific Request ID and to use it in this middleware.
### 特定平台的链接
¥Platform specific links
* AWS Lambda
* [AWS 文档:上下文对象](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-context.html)
¥[AWS documentation: Context object](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-context.html)
* [Hono:访问 AWS Lambda 对象](/docs/getting-started/aws-lambda#access-aws-lambda-object)
¥[Hono: Access AWS Lambda Object](/docs/getting-started/aws-lambda#access-aws-lambda-object)
* Cloudflare
* [Cloudflare Ray ID](https://developers.cloudflare.com/fundamentals/reference/cloudflare-ray-id/)
* Deno
* [Deno 博客上的请求 ID](https://deno.com/blog/zero-config-debugging-deno-opentelemetry#:~:text=s%20automatically%20have-,unique%20request%20IDs,-associated%20with%20them)
¥[Request ID on the Deno Blog](https://deno.com/blog/zero-config-debugging-deno-opentelemetry#:~:text=s%20automatically%20have-,unique%20request%20IDs,-associated%20with%20them)
* Fastly
* [Fastly 文档:req.xid](https://www.fastly.com/documentation/reference/vcl/variables/client-request/req-xid/)
¥[Fastly documentation: req.xid](https://www.fastly.com/documentation/reference/vcl/variables/client-request/req-xid/)
# 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 accommodates 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.
### noAuthenticationHeader:`object`
自定义请求缺少身份验证标头时的错误响应。
¥Customizes the error response when the request does not have an authentication header.
* `wwwAuthenticateHeader`:`string | object | MessageFunction` - 自定义 WWW-Authenticate 标头的值。
¥`wwwAuthenticateHeader`: `string | object | MessageFunction` - Customizes the WWW-Authenticate header value.
* `message`:`string | object | MessageFunction` - 响应体的自定义消息。
¥`message`: `string | object | MessageFunction` - The custom message for the response body.
`MessageFunction` 是 `(c: Context) => string | object | Promise`。
¥`MessageFunction` is `(c: Context) => string | object | Promise`.
### invalidAuthenticationHeader:`object`
自定义身份验证标头格式无效时的错误响应。
¥Customizes the error response when the authentication header format is invalid.
* `wwwAuthenticateHeader`:`string | object | MessageFunction` - 自定义 WWW-Authenticate 标头的值。
¥`wwwAuthenticateHeader`: `string | object | MessageFunction` - Customizes the WWW-Authenticate header value.
* `message`:`string | object | MessageFunction` - 响应体的自定义消息。
¥`message`: `string | object | MessageFunction` - The custom message for the response body.
### invalidToken:`object`
自定义令牌无效时的错误响应。
¥Customizes the error response when the token is invalid.
* `wwwAuthenticateHeader`:`string | object | MessageFunction` - 自定义 WWW-Authenticate 标头的值。
¥`wwwAuthenticateHeader`: `string | object | MessageFunction` - Customizes the WWW-Authenticate header value.
* `message`:`string | object | MessageFunction` - 响应体的自定义消息。
¥`message`: `string | object | MessageFunction` - The custom message for the response body.
# 合并中间件
¥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 })))
```
# 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'
},
})
)
```
基于源的动态允许方法:
¥Dynamic allowed methods based on origin:
```ts
app.use(
'/api5/*',
cors({
origin: (origin) =>
origin === 'https://example.com' ? origin : '*',
// `c` is a `Context` object
allowMethods: (origin, c) =>
origin === 'https://example.com'
? ['GET', 'HEAD', 'POST', 'PATCH', 'DELETE']
: ['GET', 'HEAD'],
})
)
```
## 选项
¥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[]` | `(origin:string, c:Context) => string[]`
"Access-Control-Allow-Methods" CORS 标头的值。你还可以传递回调函数,以根据来源动态确定允许的方法。默认为 `['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH']`。
¥The value of "*Access-Control-Allow-Methods*" CORS header. You can also pass a callback function to dynamically determine allowed methods based on the origin. 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)
})
```
## 与 Vite 配合使用
¥Using with Vite
当将 Hono 与 Vite 一起使用时,你应该通过在 `vite.config.ts` 中将 `server.cors` 设置为 `false` 来禁用 Vite 内置的 CORS 功能。这可以避免与 Hono 的 CORS 中间件发生冲突。
¥When using Hono with Vite, you should disable Vite's built-in CORS feature by setting `server.cors` to `false` in your `vite.config.ts`. This prevents conflicts with Hono's CORS middleware.
```ts
// vite.config.ts
import { cloudflare } from '@cloudflare/vite-plugin'
import { defineConfig } from 'vite'
export default defineConfig({
server: {
cors: false, // disable Vite's built-in CORS setting
},
plugins: [cloudflare()],
})
```
# CSRF 保护
¥CSRF Protection
此中间件通过检查 `Origin` 标头和 `Sec-Fetch-Site` 标头来防止 CSRF 攻击。如果任一验证通过,则允许该请求。
¥This middleware protects against CSRF attacks by checking both the `Origin` header and the `Sec-Fetch-Site` header. The request is allowed if either validation passes.
中间件仅验证以下请求:
¥The middleware only validates requests that:
* 使用不安全的 HTTP 方法(例如 GET、HEAD 或 OPTIONS)
¥Use unsafe HTTP methods (not GET, HEAD, or OPTIONS)
* 具有可通过 HTML 表单发送的内容类型(`application/x-www-form-urlencoded`、`multipart/form-data` 或 `text/plain`)
¥Have content types that can be sent by HTML forms (`application/x-www-form-urlencoded`, `multipart/form-data`, or `text/plain`)
不发送 `Origin` 标头的旧浏览器,或使用反向代理删除这些标头的环境,可能无法正常工作。在这样的环境中,请使用其他 CSRF 令牌方法。
¥Old browsers that do not send `Origin` headers, or environments that use reverse proxies to remove these headers, may not work well. In such environments, use other CSRF token methods.
## 导入
¥Import
```ts
import { Hono } from 'hono'
import { csrf } from 'hono/csrf'
```
## 用法
¥Usage
```ts
const app = new Hono()
// Default: both origin and sec-fetch-site validation
app.use(csrf())
// Allow specific origins
app.use(csrf({ origin: 'https://myapp.example.com' }))
// Allow multiple origins
app.use(
csrf({
origin: [
'https://myapp.example.com',
'https://development.myapp.example.com',
],
})
)
// Allow specific sec-fetch-site values
app.use(csrf({ secFetchSite: 'same-origin' }))
app.use(csrf({ secFetchSite: ['same-origin', 'none'] }))
// Dynamic origin validation
// 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),
})
)
// Dynamic sec-fetch-site validation
app.use(
csrf({
secFetchSite: (secFetchSite, c) => {
// Always allow same-origin
if (secFetchSite === 'same-origin') return true
// Allow cross-site for webhook endpoints
if (
secFetchSite === 'cross-site' &&
c.req.path.startsWith('/webhook/')
) {
return true
}
return false
},
})
)
```
## 选项
¥Options
### origin:`string` | `string[]` | `Function`
指定允许的来源,以进行 CSRF 保护。
¥Specify allowed origins for CSRF protection.
* `string`:单个允许来源(例如,`'https://example.com'`)
¥**`string`**: Single allowed origin (e.g., `'https://example.com'`)
* `string[]`:允许来源数组
¥**`string[]`**: Array of allowed origins
* `Function`:自定义处理程序 `(origin: string, context: Context) => boolean`,用于灵活的来源验证和旁路逻辑
¥**`Function`**: Custom handler `(origin: string, context: Context) => boolean` for flexible origin validation and bypass logic
默认值:仅与请求 URL 同源
¥**Default**: Only same origin as the request URL
函数处理程序接收请求的 `Origin` 标头值和请求上下文,从而允许基于请求属性(例如路径、标头或其他上下文数据)进行动态验证。
¥The function handler receives the request's `Origin` header value and the request context, allowing for dynamic validation based on request properties like path, headers, or other context data.
### secFetchSite:`string` | `string[]` | `Function`
使用 [获取元数据](https://web.dev/articles/fetch-metadata) 指定允许的 Sec-Fetch-Site 标头值,以进行 CSRF 保护。
¥Specify allowed Sec-Fetch-Site header values for CSRF protection using [Fetch Metadata](https://web.dev/articles/fetch-metadata).
* `string`:单个允许值(例如,`'same-origin'`)
¥**`string`**: Single allowed value (e.g., `'same-origin'`)
* `string[]`:允许值数组(例如 `['same-origin', 'none']`)
¥**`string[]`**: Array of allowed values (e.g., `['same-origin', 'none']`)
* `Function`:自定义处理程序 `(secFetchSite: string, context: Context) => boolean`,用于灵活的验证
¥**`Function`**: Custom handler `(secFetchSite: string, context: Context) => boolean` for flexible validation
默认值:仅允许 `'same-origin'`
¥**Default**: Only allows `'same-origin'`
标准 Sec-Fetch-Site 值:
¥Standard Sec-Fetch-Site values:
* `same-origin`:来自相同来源的请求
¥`same-origin`: Request from same origin
* `same-site`:来自同一站点(不同子域名)的请求
¥`same-site`: Request from same site (different subdomain)
* `cross-site`:来自不同站点的请求
¥`cross-site`: Request from different site
* `none`:非来自网页(例如,浏览器地址栏、书签)的请求
¥`none`: Request not from a web page (e.g., browser address bar, bookmark)
函数处理程序接收请求的 `Sec-Fetch-Site` 标头值和请求上下文,从而基于请求属性启用动态验证。
¥The function handler receives the request's `Sec-Fetch-Site` header value and the request context, enabling dynamic validation based on request properties.
# 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'
import { verifyWithJwks } from 'hono/jwt'
```
## 用法
¥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 }
})
```
匿名访问:
¥Anonymous access:
```ts
const app = new Hono()
app.use(
'/auth/*',
jwk({
jwks_uri: (c) =>
`https://${c.env.authServer}/.well-known/jwks.json`,
allow_anon: true,
})
)
app.get('/auth/page', (c) => {
const payload = c.get('jwtPayload')
return c.json(payload ?? { message: 'hello anon' })
})
```
## 在中间件之外使用 `verifyWithJwks`
¥Using `verifyWithJwks` outside of middleware
`verifyWithJwks` 实用函数可用于在 Hono 中间件上下文之外验证 JWT 令牌,例如在 SvelteKit SSR 页面或其他服务器端环境中:
¥The `verifyWithJwks` utility function can be used to verify JWT tokens outside of Hono's middleware context, such as in SvelteKit SSR pages or other server-side environments:
```ts
const id_payload = await verifyWithJwks(
id_token,
{
jwks_uri: 'https://your-auth-server/.well-known/jwks.json',
},
{
cf: { cacheEverything: true, cacheTtl: 3600 },
}
)
```
## 选项
¥Options
### 键:`HonoJsonWebKey[] | (c: Context) => Promise`
¥ keys: `HonoJsonWebKey[] | (c: Context) => Promise`
你的公钥的值,或返回它们的函数。该函数接收 Context 对象。
¥The values of your public keys, or a function that returns them. The function receives the Context object.
### jwks\_uri:`string` | `(c: Context) => Promise`
如果设置了此值,则尝试从此 URI 获取 JWK,期望 JSON 响应带有 `keys`,这些响应将添加到提供的 `keys` 选项中。你还可以传递一个回调函数,使用 Context 动态确定 JWKS URI。
¥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. You can also pass a callback function to dynamically determine the JWKS URI using the Context.
### allow\_anon:`boolean`
如果将此值设置为 `true`,则允许没有有效令牌的请求通过中间件。使用 `c.get('jwtPayload')` 检查请求是否已通过身份验证。默认为 `false`。
¥If this value is set to `true`, requests without a valid token will be allowed to pass through the middleware. Use `c.get('jwtPayload')` to check if the request is authenticated. The default is `false`.
### 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.
### 标头名称:`string`
¥ headerName: `string`
用于查找 JWT 令牌的标头名称。默认为 `Authorization`。
¥The name of the header to look for the JWT token. The default is `Authorization`.
# 上下文存储中间件
¥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,
tryGetContext,
} 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)
}
```
## tryGetContext
`tryGetContext()` 的工作方式与 `getContext()` 类似,但当上下文不可用时,它会返回 `undefined` 而不是抛出错误:
¥`tryGetContext()` works like `getContext()`, but returns `undefined` instead of throwing an error when the context is not available:
```ts
const context = tryGetContext()
if (context) {
// Context is available
console.log(context.var.message)
}
```
# 缓存中间件
¥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`.
### cacheableStatusCodes:`number[]`
应缓存的状态码数组。默认为 `[200]`。使用此选项可以缓存具有特定状态代码的响应。
¥An array of status codes that should be cached. The default is `[200]`. Use this option to cache responses with specific status codes.
```ts
app.get(
'*',
cache({
cacheName: 'my-app',
cacheControl: 'max-age=3600',
cacheableStatusCodes: [200, 404, 412],
})
)
```
# 超时中间件
¥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.
# 日志器中间件
¥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
})
```
# 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.
# 安全标头中间件
¥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) | 无设置 |
| trustedTypes | [Trusted 类型](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/trusted-types) | 用法:[设置内容安全策略](#setting-content-security-policy) | 无设置 |
| requireTrustedTypesFor | [Require Trusted Types For](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/require-trusted-types-for) | 用法:[设置内容安全策略](#setting-content-security-policy) | 无设置 |
| crossOriginEmbedderPolicy | [Cross-Origin-Embedder-Policy](https://web.nodejs.cn/en-US/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://learn.microsoft.com/en-us/archive/blogs/ie/ie8-security-part-v-comprehensive-protection#mime-handling-force-save) | 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',
reportUri: '/csp-report',
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")
},
})
)
```
# 尾部斜杠中间件
¥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`.
# 方法覆盖中间件
¥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.