This is the tiny 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。
快速,但不止于快速。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hono!'))
export default app
```
## 快速开始
只需运行此命令:
::: 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
```
:::
## 特性
- **超快** 🚀 - 路由器 `RegExpRouter` 非常快。不使用线性循环。快。
- **轻量级** 🪶 - `hono/tiny` 预设小于 14kB。Hono 零依赖,仅使用 Web 标准。
- **多运行时** 🌍 - 适用于 Cloudflare Workers、Fastly Compute、Deno、Bun、AWS Lambda 或 Node.js。相同的代码可在所有平台上运行。
- **功能齐全** 🔋 - Hono 拥有内置中间件、自定义中间件、第三方中间件和辅助函数。功能齐全。
- **愉快的开发体验** 😃 - 超级清晰的 API。一流的 TypeScript 支持。现在,我们拥有了“类型”。
## 用例
Hono 是一个类似于 Express 的简单 Web 应用框架,不含前端。
但它运行在 CDN 边缘,并且结合中间件允许你构建更大的应用程序。
以下是一些用例示例。
- 构建 Web API
- 后端服务器代理
- CDN 前端
- 边缘应用
- 库的基础服务器
- 全栈应用
## 谁在使用 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) | Local AI Server | 带有记忆的无服务器 AI 代理管道。一个用于 Web 的开源代理 AI 框架。_使用 Hono 的 API 服务器_。 |
| [Unkey](https://unkey.dev) | Cloudflare Workers | 一个开源的 API 认证和授权平台。_Hono 用于 API 服务器_。 |
| [OpenStatus](https://openstatus.dev) | Bun | 一个开源的网站和 API 监控平台。_Hono 用于 API 服务器_。 |
| [Deno Benchmarks](https://deno.com/benchmarks) | Deno | 一个基于 V8 构建的安全 TypeScript 运行时。_Hono 用于基准测试_。 |
| [Clerk](https://clerk.com) | Cloudflare Workers | 一个开源的用户管理平台。_Hono 用于 API 服务器_。 |
以及以下项目。
- [Drivly](https://driv.ly/) - Cloudflare Workers
- [repeat.dev](https://repeat.dev/) - Cloudflare Workers
想看更多?参见 [谁在生产环境中使用 Hono?](https://github.com/orgs/honojs/discussions/1510)。
## 1 分钟了解 Hono
使用 Hono 为 Cloudflare Workers 创建应用程序的演示。

## 超快
**Hono 是最快的**,相比其他 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)。
## 轻量级
**Hono 非常小**。使用 `hono/tiny` 预设,其最小化后的大小**小于 14KB**。有许多中间件和适配器,但它们仅在使用时才会被打包。作为参考,Express 的大小为 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
```
## 多种路由器
**Hono 拥有多种路由器**。
**RegExpRouter** 是 JavaScript 世界中最快的路由器。它使用在调度前创建的单个大型正则表达式来匹配路由。配合 **SmartRouter**,它支持所有路由模式。
**LinearRouter** 注册路由非常快,因此适合每次初始化应用程序的环境。**PatternRouter** 简单地添加和匹配模式,使其小巧。
参见 [关于路由的更多信息](/docs/concepts/routers)。
## Web 标准
得益于使用 **Web 标准**,Hono 可在许多平台上运行。
- Cloudflare Workers
- Cloudflare Pages
- Fastly Compute
- Deno
- Bun
- Vercel
- AWS Lambda
- Lambda@Edge
- 其他
并且通过使用 [Node.js 适配器](https://github.com/honojs/node-server),Hono 可在 Node.js 上运行。
参见 [关于 Web 标准的更多信息](/docs/concepts/web-standard)。
## 中间件与辅助函数
**Hono 拥有许多中间件和辅助函数**。这使得“少写代码,多做事情”成为现实。
开箱即用,Hono 提供以下中间件和辅助函数:
- [基本认证](/docs/middleware/builtin/basic-auth)
- [Bearer 认证](/docs/middleware/builtin/bearer-auth)
- [Body 限制](/docs/middleware/builtin/body-limit)
- [缓存](/docs/middleware/builtin/cache)
- [压缩](/docs/middleware/builtin/compress)
- [上下文存储](/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)
- [日志](/docs/middleware/builtin/logger)
- [语言](/docs/middleware/builtin/language)
- [美化 JSON](/docs/middleware/builtin/pretty-json)
- [安全 Headers](/docs/middleware/builtin/secure-headers)
- [SSG](/docs/helpers/ssg)
- [流式传输](/docs/helpers/streaming)
- [GraphQL 服务器](https://github.com/honojs/middleware/tree/main/packages/graphql-server)
- [Firebase 认证](https://github.com/honojs/middleware/tree/main/packages/firebase-auth)
- [Sentry](https://github.com/honojs/middleware/tree/main/packages/sentry)
- 其他!
例如,使用 Hono 添加 ETag 和请求日志记录只需几行代码:
```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)。
## 开发体验
Hono 提供愉快的"**开发体验**"。
得益于 `Context` 对象,可以轻松访问 Request/Response。
此外,Hono 是用 TypeScript 编写的。Hono 拥有"**类型**"。
例如,路径参数将是字面量类型。

此外,Validator 和 Hono Client `hc` 启用了 RPC 模式。在 RPC 模式下,
你可以使用你喜欢的验证器(如 Zod),并轻松地将服务器端 API 规范共享给客户端,构建类型安全的应用程序。
参见 [Hono 技术栈](/docs/concepts/stacks)。
# 最佳实践
Hono 非常灵活。你可以按照自己喜欢的方式编写应用。
然而,有一些最佳实践最好遵循。
## 尽可能不要创建“控制器”
如果可能,你不应该创建“类似 Ruby on Rails 的控制器”。
```ts
// 🙁
// 一个类似 RoR 的控制器
const booksList = (c: Context) => {
return c.json('list books')
}
app.get('/books', booksList)
```
问题与类型有关。例如,如果不编写复杂的泛型,无法在控制器中推断路径参数。
```ts
// 🙁
// 一个类似 RoR 的控制器
const bookPermalink = (c: Context) => {
const id = c.req.param('id') // 无法推断路径参数
return c.json(`get ${id}`)
}
```
因此,你不需要创建类似 RoR 的控制器,应该直接在路径定义后编写处理程序。
```ts
// 😃
app.get('/books/:id', (c) => {
const id = c.req.param('id') // 可以推断路径参数
return c.json(`get ${id}`)
})
```
## `hono/factory` 中的 `factory.createHandlers()`
如果你仍然想创建类似 RoR 的控制器,请使用 [`hono/factory`](/docs/helpers/factory) 中的 `factory.createHandlers()`。如果你使用这个,类型推断将正常工作。
```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)
```
## 构建大型应用
使用 `app.route()` 来构建大型应用,而无需创建“类似 Ruby on Rails 的控制器”。
如果你的应用有 `/authors` 和 `/books` 端点,并且你希望将文件从 `index.ts` 分离出来,创建 `authors.ts` 和 `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` 上。
```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 功能
上面的代码适用于正常用例。
但是,如果你想使用 `RPC` 功能,你可以通过如下链式调用获得正确的类型。
```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`,它将获得正确的类型。
```ts
import type { AppType } from './authors'
import { hc } from 'hono/client'
// 😃
const client = hc('http://localhost') // 类型正确
```
有关更详细的信息,请参阅 [RPC 页面](/docs/guides/rpc#using-rpc-with-larger-applications)。
# Create-hono
`create-hono` 支持的命令行选项 - 这是当你运行 `npm create hono@latest`、`npx create-hono@latest` 或 `pnpm create hono@latest` 时运行的项目初始化工具。
> [!NOTE]
> **为什么需要这个页面?** 安装/快速启动示例通常展示一个最小化的 `npm create hono@latest my-app` 命令。`create-hono` 支持几个有用的标志,你可以传递这些标志来自动化和自定义项目创建(选择模板、跳过提示、选择包管理器、使用本地缓存等)。
## 传递参数:
当你使用 `npm create`(或 `npx`)时,旨在传递给初始化脚本的参数必须放在 `--` **之后**。`--` 之后的任何内容都会转发给初始化程序。
::: code-group
```sh [npm]
# 将参数转发给 create-hono(npm 需要 `--`)
npm create hono@latest my-app -- --template cloudflare-workers
```
```sh [yarn]
# "--template cloudflare-workers" 选择 Cloudflare Workers 模板
yarn create hono my-app --template cloudflare-workers
```
```sh [pnpm]
# "--template cloudflare-workers" 选择 Cloudflare Workers 模板
pnpm create hono@latest my-app --template cloudflare-workers
```
```sh [bun]
# "--template cloudflare-workers" 选择 Cloudflare Workers 模板
bun create hono@latest my-app --template cloudflare-workers
```
```sh [deno]
# "--template cloudflare-workers" 选择 Cloudflare Workers 模板
deno init --npm hono@latest my-app --template cloudflare-workers
```
:::
## 常用参数
| 参数 | 描述 | 示例 |
| :---------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------ |
| `--template ` | 选择一个起始模板并跳过交互式模板提示。模板可能包含名称,如 `bun`、`cloudflare-workers`、`vercel` 等。 | `--template cloudflare-workers` |
| `--install` | 模板创建后自动安装依赖。 | `--install` |
| `--pm ` | 指定安装依赖时运行哪个包管理器。常用值:`npm`、`pnpm`、`yarn`。 | `--pm pnpm` |
| `--offline` | 使用本地缓存/模板而不是获取最新的远程模板。适用于离线环境或确定性本地运行。 | `--offline` |
> [!NOTE]
> 确切的模板集和可用选项由 `create-hono` 项目维护。此文档页面总结了最常用的标志 — 请参阅下方链接的仓库以获取完整、权威的参考。
## 示例流程
### 最小化,交互式
```bash
npm create hono@latest my-app
```
这将提示你选择模板和选项。
### 非交互式,选择模板和包管理器
```bash
npm create hono@latest my-app -- --template vercel --pm npm --install
```
这将使用 `vercel` 模板创建 `my-app`,使用 `npm` 安装依赖,并跳过交互式提示。
### 使用离线缓存(无网络)
```bash
pnpm create hono@latest my-app --template deno --offline
```
## 故障排除与提示
- 如果某个选项似乎未被识别,请确保在使用 `npm create` / `npx` 时使用 `--` 转发它。
- 要查看最新的模板和标志列表,请参阅 `create-hono` 仓库或在本地运行初始化程序并遵循其帮助输出。
## 链接与参考
- `create-hono` 仓库:[create-hono](https://github.com/honojs/create-hono)
# 示例
参见 [示例部分](/examples/).
# 常见问题
本指南收集了关于 Hono 的常见问题 (FAQ) 及其解决方法。
## Hono 有官方的 Renovate 配置吗?
Hono 团队目前不维护 [Renovate](https://github.com/renovatebot/renovate) 配置。
因此,请按如下方式使用第三方 renovate-config。
在你的 `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) 仓库以获取更多详情。
# 辅助工具
辅助工具可用于协助您开发应用程序。与中间件不同,它们不作为处理程序,而是提供有用的函数。
例如,以下是使用 [Cookie 辅助工具](/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')
//
})
```
## 可用的辅助工具
- [Accepts](/docs/helpers/accepts)
- [适配器](/docs/helpers/adapter)
- [Cookie](/docs/helpers/cookie)
- [css](/docs/helpers/css)
- [开发](/docs/helpers/dev)
- [工厂](/docs/helpers/factory)
- [html](/docs/helpers/html)
- [JWT](/docs/helpers/jwt)
- [SSG](/docs/helpers/ssg)
- [流式传输](/docs/helpers/streaming)
- [测试](/docs/helpers/testing)
- [WebSocket](/docs/helpers/websocket)
# 客户端组件
`hono/jsx` 不仅支持服务端,也支持客户端。这意味着可以创建在浏览器中运行的交互式 UI。我们称之为客户端组件或 `hono/jsx/dom`。
它速度快且体积非常小。`hono/jsx/dom` 中的计数器程序经过 Brotli 压缩后仅为 2.8KB,而 React 则为 47.8KB。
本节介绍客户端组件特有的功能。
## 计数器示例
这是一个简单计数器的示例,代码与 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()` 将 JSX 组件插入到指定的 HTML 元素中。
```tsx
render(, container)
```
你可以在此处查看完整的示例代码:[计数器示例](https://github.com/honojs/examples/tree/main/hono-vite-jsx)。
## 与 React 兼容的 Hooks
hono/jsx/dom 拥有与 React 兼容或部分兼容的 Hooks。你可以通过查看 [React 文档](https://react.dev/reference/react/hooks) 来了解这些 API。
- `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()` 系列包含原始的 hooks 和函数,用于轻松处理 [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API)。以下是如何使用它们的示例。
### 1. 最简单的示例
借助 `startViewTransition()`,你可以简便地编写使用 `document.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()` 结合使用
`viewTransition()` 函数允许你获取唯一的 `view-transition-name`。
你可以将其与 `keyframes()` 一起使用,`::view-transition-old()` 会被转换为 `::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`
如果你只想在动画期间更改样式。你可以使用 `useViewTransition()`。此 hook 返回 `[boolean, (callback: () => void) => void]`,它们分别是 `isUpdating` 标志和 `startViewTransition()` 函数。
使用此 hook 时,组件会在以下两个时间点进行求值。
- 在调用 `startViewTransition()` 的回调内部。
- 当 [`finish` promise 变为 fulfilled 状态](https://developer.mozilla.org/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` 运行时
有一个用于客户端组件的小型 JSX Runtime。使用这将比使用 `hono/jsx` 产生更小的打包结果。在 `tsconfig.json` 中指定 `hono/jsx/dom`。对于 Deno,请修改 deno.json。
```json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx/dom"
}
}
```
或者,你可以在 `vite.config.ts` 中的 esbuild transform 选项中指定 `hono/jsx/dom`。
```ts
import { defineConfig } from 'vite'
export default defineConfig({
esbuild: {
jsxImportSource: 'hono/jsx/dom',
},
})
```
# JSX
你可以使用 `hono/jsx` 通过 JSX 语法编写 HTML。
虽然 `hono/jsx` 可以在客户端工作,但你可能最常在服务器端渲染内容时使用它。以下是一些与 JSX 相关的事情,它们在服务器端和客户端是通用的。
## 设置
要使用 JSX,修改 `tsconfig.json`:
`tsconfig.json`:
```json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}
```
或者,使用 pragma 指令:
```ts
/** @jsx jsx */
/** @jsxImportSource hono/jsx */
```
对于 Deno,你必须修改 `deno.json` 而不是 `tsconfig.json`:
```json
{
"compilerOptions": {
"jsx": "precompile",
"jsxImportSource": "@hono/hono/jsx"
}
}
```
## 用法
:::info
如果你是直接从 [快速开始](/docs/#quick-start) 过来的,主文件的扩展名是 `.ts` - 你需要将其改为 `.tsx` - 否则你将根本无法运行应用程序。你还应该修改 `package.json`(如果你使用 Deno 则是 `deno.json`)以反映该更改(例如,在开发脚本中不是 `bun run --hot src/index.ts`,而应该是 `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
```
## 元数据提升
你可以直接在组件内部编写文档元数据标签,例如 ``、`` 和 ``。这些标签将自动提升到文档的 `` 部分。当 `` 元素渲染位置远离确定适当元数据的组件时,这尤其有用。
```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
```
:::info
当发生提升时,现有元素不会被移除。后面出现的元素会被添加到末尾。例如,如果你的 `` 中有 `Default` 并且组件渲染了 `Page Title`,两个标题都会出现在 head 中。
:::
## Fragment
使用 Fragment 将多个元素分组而不添加额外的节点:
```tsx
import { Fragment } from 'hono/jsx'
const List = () => (
first child
second child
third child
)
```
或者如果设置得当,你可以用 `<>>` 来编写。
```tsx
const List = () => (
<>
first child
second child
third child
>
)
```
## `PropsWithChildren`
你可以使用 `PropsWithChildren` 在函数组件中正确推断子元素。
```tsx
import { PropsWithChildren } from 'hono/jsx'
type Post = {
id: number
title: string
}
function Component({ title, children }: PropsWithChildren) {
return (
{title}
{children}
)
}
```
## 插入原始 HTML
要直接插入 HTML,使用 `dangerouslySetInnerHTML`:
```tsx
app.get('/foo', (c) => {
const inner = { __html: 'JSX · SSR' }
const Div =
})
```
## 记忆化
通过使用 `memo` 记忆计算后的字符串来优化你的组件:
```tsx
import { memo } from 'hono/jsx'
const Header = memo(() => )
const Footer = memo(() => )
const Layout = (
)
```
## Context
通过使用 `useContext`,你可以在组件树的任何层级全局共享数据,而无需通过 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(
)
})
```
## 异步组件
`hono/jsx` 支持异步组件,因此你可以在组件中使用 `async`/`await`。
如果你使用 `c.html()` 渲染它,它将自动等待。
```tsx
const AsyncComponent = async () => {
await new Promise((r) => setTimeout(r, 1000)) // 睡眠 1 秒
return Done!
}
app.get('/', (c) => {
return c.html(
)
})
```
## Suspense
类似 React 的 `Suspense` 功能可用。
如果你用 `Suspense` 包装异步组件,fallback 中的内容将首先渲染,一旦 Promise 解决,等待的内容将显示。
你可以配合 `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` 捕获子组件中的错误。
在下面的示例中,如果发生错误,它将显示 `fallback` 中指定的内容。
```tsx
function SyncComponent() {
throw new Error('Error')
return Hello
}
app.get('/sync', async (c) => {
return c.html(
Out of Service}>
)
})
```
`ErrorBoundary` 也可以与异步组件和 `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` 为 `Suspense` 和 `ErrorBoundary` 等流式组件提供配置。这对于为这些组件生成的脚本标签添加 nonce 值以用于内容安全策略 (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` 组件生成的任何 `
) : (
)}
Hello
)
})
```
为了正确构建脚本,你可以使用如下所示的示例配置文件 `vite.config.ts`。
```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',
}),
],
}
}
})
```
你可以运行以下命令来构建服务器和客户端脚本。
```sh
vite build --mode client && vite build
```
## Cloudflare Pages 中间件
Cloudflare Pages 使用其自己的 [中间件](https://developers.cloudflare.com/pages/functions/middleware/) 系统,这与 Hono 的中间件不同。你可以通过在名为 `_middleware.ts` 的文件中导出 `onRequest` 来启用它,如下所示:
```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 中间件。
```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)。
```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',
})
)
```
如果你想应用多个中间件,你可以这样写:
```ts
import { handleMiddleware } from 'hono/cloudflare-pages'
// ...
export const onRequest = [
handleMiddleware(middleware1),
handleMiddleware(middleware2),
handleMiddleware(middleware3),
]
```
### 访问 `EventContext`
你可以通过 `handleMiddleware` 中的 `c.env` 访问 [`EventContext`](https://developers.cloudflare.com/pages/functions/api-reference/#eventcontext) 对象。
```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` 访问数据值:
```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)
```
# Cloudflare Workers
[Cloudflare Workers](https://workers.cloudflare.com) 是 Cloudflare CDN 上的 JavaScript 边缘运行时。
您可以使用 [Wrangler](https://developers.cloudflare.com/workers/wrangler/) 在本地开发应用程序并通过几条命令发布它。
Wrangler 包含转译器,所以我们可以用 TypeScript 编写代码。
让我们用 Hono 制作您的第一个 Cloudflare Workers 应用程序。
## 1. 设置
Cloudflare Workers 的启动器可用。
使用 "create-hono" 命令开始您的项目。
为本示例选择 `cloudflare-workers` 模板。
::: 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` 并安装依赖。
::: 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. 你好世界
如下编辑 `src/index.ts`。
```ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Cloudflare Workers!'))
export default app
```
## 3. 运行
在本地运行开发服务器。然后,在您的网页浏览器中访问 `http://localhost:8787`。
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm dev
```
```sh [bun]
bun run dev
```
:::
### 更改端口号
如果您需要更改端口号,可以按照这里的说明更新 `wrangler.toml` / `wrangler.json` / `wrangler.jsonc` 文件:
[Wrangler 配置](https://developers.cloudflare.com/workers/wrangler/configuration/#local-development-settings)
或者,您可以按照这里的说明设置 CLI 选项:
[Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/commands/#dev)
## 4. 部署
如果您拥有 Cloudflare 账户,您可以部署到 Cloudflare。在 `package.json` 中,`$npm_execpath` 需要更改为您选择的包管理器。
::: code-group
```sh [npm]
npm run deploy
```
```sh [yarn]
yarn deploy
```
```sh [pnpm]
pnpm run deploy
```
```sh [bun]
bun run deploy
```
:::
就是这样!
## 将 Hono 与其他事件处理程序一起使用
您可以在 _Module Worker 模式_ 中将 Hono 与其他事件处理程序(例如 `scheduled`)集成。
为此,将 `app.fetch` 导出为模块的 `fetch` 处理程序,然后根据需要实现其他处理程序:
```ts
const app = new Hono()
export default {
fetch: app.fetch,
scheduled: async (batch, env) => {},
}
```
## 提供静态文件
如果您想提供静态文件,可以使用 Cloudflare Workers 的 [静态资产功能](https://developers.cloudflare.com/workers/static-assets/)。在 `wrangler.toml` 中指定文件目录:
```toml
assets = { directory = "public" }
```
然后创建 `public` 目录并将文件放在那里。例如,`./public/static/hello.txt` 将作为 `/static/hello.txt` 提供。
```
.
├── package.json
├── public
│ ├── favicon.ico
│ └── static
│ └── hello.txt
├── src
│ └── index.ts
└── wrangler.toml
```
## 类型
如果您想要拥有 workers 类型,必须安装 `@cloudflare/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
```
:::
## 测试
对于测试,我们推荐使用 `@cloudflare/vitest-pool-workers`。
参考 [示例](https://github.com/honojs/examples) 进行设置。
如果有以下应用程序。
```ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Please test me!'))
```
我们可以用这段代码测试它是否返回 "_200 OK_" 响应。
```ts
describe('Test the application', () => {
it('Should return 200 response', async () => {
const res = await app.request('http://localhost/')
expect(res.status).toBe(200)
})
})
```
## 绑定
在 Cloudflare Workers 中,我们可以绑定环境变量、KV 命名空间、R2 存储桶或 Durable Object。您可以在 `c.env` 中访问它们。如果您将绑定的 "_类型定义_" 作为泛型传递给 `Hono`,它将拥有类型。
```ts
type Bindings = {
MY_BUCKET: R2Bucket
USERNAME: string
PASSWORD: string
}
const app = new Hono<{ Bindings: Bindings }>()
// 访问环境变量
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!`)
})
```
## 在中间件中使用变量
这仅适用于 Module Worker 模式。
如果您想在中间件中使用变量或秘密变量,例如基本认证中间件中的 "username" 或 "password",您需要如下编写。
```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 认证或其他。
## 从 GitHub Actions 部署
在通过 CI 将代码部署到 Cloudflare 之前,您需要一个 Cloudflare 令牌。您可以从 [用户 API 令牌](https://dash.cloudflare.com/profile/api-tokens) 管理它。
如果是新创建的令牌,选择 **编辑 Cloudflare Workers** 模板。如果您已经有另一个令牌,请确保该令牌具有相应的权限。(注意:令牌权限在 Cloudflare Pages 和 Cloudflare Workers 之间不共享)。
然后转到您的 GitHub 仓库设置仪表板:`Settings->Secrets and variables->Actions->Repository secrets`,并添加一个名为 `CLOUDFLARE_API_TOKEN` 的新秘密。
然后在您的 Hono 项目根文件夹中创建 `.github/workflows/deploy.yml`,粘贴以下代码:
```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` 行后添加此代码。
```toml
main = "src/index.ts"
minify = true
```
一切都准备好了!现在推送代码并享受它。
## 本地开发时加载环境变量
要为本地开发配置环境变量,在项目根目录创建 `.dev.vars` 文件或 `.env` 文件。
这些文件应使用 [dotenv](https://hexdocs.pm/dotenvy/dotenv-file-format.html) 语法格式化。例如:
```
SECRET_KEY=value
API_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
```
> 关于此部分的更多信息,您可以在 Cloudflare 文档中找到:
> https://developers.cloudflare.com/workers/wrangler/configuration/#secrets
然后我们在代码中使用 `c.env.*` 来获取环境变量。
::: 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)
:::
```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 项目的配置中设置环境变量/秘密。
> 关于此部分的更多信息,您可以在 Cloudflare 文档中找到:
> https://developers.cloudflare.com/workers/configuration/environment-variables/#add-environment-variables-via-the-dashboard
# Deno
[Deno](https://deno.com/) 是一个基于 V8 构建的 JavaScript 运行时。它不是 Node.js。
Hono 也可以在 Deno 上运行。
你可以使用 Hono,用 TypeScript 编写代码,使用 `deno` 命令运行应用程序,并将其部署到 "Deno Deploy"。
## 1. 安装 Deno
首先,安装 `deno` 命令。
请参考 [官方文档](https://docs.deno.com/runtime/getting_started/installation/)。
## 2. 设置
Deno 有一个入门模板可用。
使用 [`deno init`](https://docs.deno.com/runtime/reference/cli/init/) 命令开始你的项目。
```sh
deno init --npm hono --template=deno my-app
```
进入 `my-app`。对于 Deno,你不需要显式安装 Hono。
```sh
cd my-app
```
## 3. 你好世界
编辑 `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. 运行
在本地运行开发服务器。然后,在 Web 浏览器中访问 `http://localhost:8000`。
```sh
deno task start
```
## 更改端口号
你可以通过更新 `main.ts` 中 `Deno.serve` 的参数来指定端口号:
```ts
Deno.serve(app.fetch) // [!code --]
Deno.serve({ port: 8787 }, app.fetch) // [!code ++]
```
## 提供静态文件
要提供静态文件,请使用从 `hono/deno` 导入的 `serveStatic`。
```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)
```
对于上述代码,配合以下目录结构可以正常工作。
```
./
├── favicon.ico
├── index.ts
└── static
├── demo
│ └── index.html
├── fallback.txt
├── hello.txt
└── images
└── dinotocat.png
```
### `rewriteRequestPath`
如果你想将 `http://localhost:8000/static/*` 映射到 `./statics`,可以使用 `rewriteRequestPath` 选项:
```ts
app.get(
'/static/*',
serveStatic({
root: './',
rewriteRequestPath: (path) =>
path.replace(/^\/static/, '/statics'),
})
)
```
### `mimes`
你可以使用 `mimes` 添加 MIME 类型:
```ts
app.get(
'/static/*',
serveStatic({
mimes: {
m3u8: 'application/vnd.apple.mpegurl',
ts: 'video/mp2t',
},
})
)
```
### `onFound`
你可以使用 `onFound` 指定当请求的文件被找到时的处理:
```ts
app.get(
'/static/*',
serveStatic({
// ...
onFound: (_path, c) => {
c.header('Cache-Control', `public, immutable, max-age=31536000`)
},
})
)
```
### `onNotFound`
你可以使用 `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。如果都不可用,则提供原始文件。
```ts
app.get(
'/static/*',
serveStatic({
precompressed: true,
})
)
```
## Deno Deploy
Deno Deploy 是一个用于在云端运行 JavaScript 和 TypeScript 应用程序的无服务器平台。
它提供了一个管理平面,支持通过 GitHub 部署等集成来部署和运行应用程序。
Hono 也可以在 Deno Deploy 上运行。请参考 [官方文档](https://docs.deno.com/deploy/manual/)。
## 测试
在 Deno 上测试应用程序很简单。
你可以使用 `Deno.test` 编写测试,并使用 [@std/assert](https://jsr.io/@std/assert) 中的 `assert` 或 `assertEquals`。
```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)
})
```
然后运行命令:
```sh
deno test hello.ts
```
## npm 和 JSR
Hono 在 [npm](https://www.npmjs.com/package/hono) 和 [JSR](https://jsr.io/@hono/hono)(JavaScript Registry)上均可用。你可以在 `deno.json` 中使用 `npm:hono` 或 `jsr:@hono/hono`:
```json
{
"imports": {
"hono": "jsr:@hono/hono" // [!code --]
"hono": "npm:hono" // [!code ++]
}
}
```
要使用中间件,你需要在导入中使用 [Deno 目录](https://docs.deno.com/runtime/fundamentals/configuration/#custom-path-mappings) 语法。
```json
{
"imports": {
"hono/": "npm:/hono/"
}
}
```
使用第三方中间件时,为了正确的 TypeScript 类型推断,你可能需要使用与中间件相同注册表的 Hono。例如,如果使用来自 npm 的中间件,你也应该使用来自 npm 的 Hono:
```json
{
"imports": {
"hono": "npm:hono",
"zod": "npm:zod",
"@hono/zod-validator": "npm:@hono/zod-validator"
}
}
```
我们在 [JSR](https://jsr.io/@hono) 上也提供了许多第三方中间件包。当使用 JSR 上的中间件时,请使用来自 JSR 的 Hono:
```json
{
"imports": {
"hono": "jsr:@hono/hono",
"zod": "npm:zod",
"@hono/zod-validator": "jsr:@hono/zod-validator"
}
}
```
# Fastly Compute
[Fastly Compute](https://www.fastly.com/products/edge-compute) 是一个先进的边缘计算系统,它可以在 Fastly 的全球边缘网络上以您喜欢的语言运行您的代码。Hono 也可以在 Fastly Compute 上运行。
您可以使用 [Fastly CLI](https://www.fastly.com/documentation/reference/tools/cli/) 在本地开发应用程序并通过几条命令发布它,该 CLI 作为模板的一部分会自动安装在本地。
## 1. 设置
Fastly Compute 的入门模板可用。
使用 "create-hono" 命令开始您的项目。
为本示例选择 `fastly` 模板。
::: 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` 并安装依赖项。
::: 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. 你好世界
编辑 `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` 会导致其路由器在应用程序初始化阶段构建其内部数据。
## 3. 运行
在本地运行开发服务器。然后,在您的 Web 浏览器中访问 `http://localhost:7676`。
::: code-group
```sh [npm]
npm run start
```
```sh [yarn]
yarn start
```
```sh [pnpm]
pnpm run start
```
```sh [bun]
bun run start
```
:::
## 4. 部署
要构建并将应用程序部署到您的 Fastly 账户,请输入以下命令。首次部署应用程序时,系统会提示您在账户中创建新服务。
如果您还没有账户,必须 [创建您的 Fastly 账户](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
```
:::
## 绑定
在 Fastly Compute 中,您可以绑定 Fastly 平台资源,例如 KV Stores、Config Stores、Secret Stores、Backends、Access Control Lists、Named Log Streams 和环境变量。您可以通过 `c.env` 访问它们,并将拥有它们各自的 SDK 类型。
要使用这些绑定,请从 `@fastly/hono-fastly-compute` 导入 `buildFire` 而不是 `fire`。定义您的 [绑定](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` 类型。
```ts
// src/index.ts
import { buildFire } from '@fastly/hono-fastly-compute'
const fire = buildFire({
siteData: 'KVStore:site-data', // 我有一个名为 "site-data" 的 KV Store
})
const app = new Hono<{ Bindings: typeof fire.Bindings }>()
app.put('/upload/:key', async (c, next) => {
// 例如,访问 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)
```
# Google Cloud Run
[Google Cloud Run](https://cloud.google.com/run) 是由 Google Cloud 构建的无服务器平台。您可以响应事件运行代码,Google 会自动为您管理底层计算资源。
Google Cloud Run 使用容器来运行您的服务。这意味着您可以通过提供 Dockerfile 来使用任何您喜欢的运行时(例如,Deno 或 Bun)。如果未提供 Dockerfile,Google Cloud Run 将使用默认的 Node.js 构建包。
本指南假设您已经拥有 Google Cloud 账户和结算账户。
## 1. 安装 CLI
在使用 Google Cloud Platform 时,使用 [gcloud CLI](https://cloud.google.com/sdk/docs/install) 是最简单的。
例如,在 MacOS 上使用 Homebrew:
```sh
brew install --cask gcloud-cli
```
使用 CLI 进行身份验证。
```sh
gcloud auth login
```
## 2. 项目设置
创建一个项目。在提示符处接受自动生成的项目 ID。
```sh
gcloud projects create --set-as-default --name="my app"
```
为您的项目 ID 和项目编号创建环境变量以便于重用。项目通过 `gcloud projects list` 命令成功返回可能需要 ~30 秒。
```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。
```sh
gcloud billing accounts list
```
将上一条命令中的结算账户添加到项目。
```sh
gcloud billing projects link $PROJECT_ID \
--billing-account=[billing_account_id]
```
启用所需的 API。
```sh
gcloud services enable run.googleapis.com \
cloudbuild.googleapis.com
```
更新服务账户权限以访问 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`。
```sh
npm create hono@latest my-app
```
进入 `my-app` 并安装依赖项。
```sh
cd my-app
npm i
```
将 `src/index.ts` 中的端口更新为 `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。
```sh
npm run dev
```
## 4. 部署
开始部署并按照交互式提示操作(例如,选择一个区域)。
```sh
gcloud run deploy my-app --source . --allow-unauthenticated
```
## 更改运行时
如果您想使用 Deno 或 Bun 运行时(或自定义的 Nodejs 容器)进行部署,请添加一个包含所需环境的 `Dockerfile`(以及可选的 `.dockerignore`)。
有关容器化的信息,请参阅:
- [Node.js](/docs/getting-started/nodejs#building-deployment)
- [Bun](https://bun.com/guides/ecosystem/docker)
- [Deno](https://docs.deno.com/examples/google_cloud_run_tutorial)
# Lambda@Edge
[Lambda@Edge](https://aws.amazon.com/lambda/edge/) 是 Amazon Web Services 的一个无服务器平台。它允许你在 Amazon CloudFront 的边缘位置运行 Lambda 函数,使你能够自定义 HTTP 请求/响应的行为。
Hono 支持在 Node.js 18+ 环境下使用 Lambda@Edge。
## 1. 设置
在 Lambda@Edge 上创建应用程序时,
[CDK](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-cdk.html)
对于设置 CloudFront、IAM 角色、API Gateway 等函数非常有用。
使用 `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`。
```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. 部署
编辑 `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`。
```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,
})
// 上传任意 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,
},
],
},
})
}
}
```
最后,运行以下命令进行部署:
```sh
cdk deploy
```
## 回调
如果你想添加基本认证并在验证后继续处理请求,可以使用 `c.env.callback()`
```ts
import { Hono } from 'hono'
import { basicAuth } from 'hono/basic-auth'
import type { Callback, CloudFrontRequest } from 'hono/lambda-edge'
import { handle } from 'hono/lambda-edge'
type Bindings = {
callback: Callback
request: CloudFrontRequest
}
const app = new Hono<{ Bindings: Bindings }>()
app.get(
'*',
basicAuth({
username: 'hono',
password: 'acoolproject',
})
)
app.get('/', async (c, next) => {
await next()
c.env.callback(null, c.env.request)
})
export const handler = handle(app)
```
# Netlify
Netlify 提供静态网站托管和无服务器后端服务。[边缘函数](https://docs.netlify.com/edge-functions/overview/) 使我们能够使网页动态化。
边缘函数支持使用 Deno 和 TypeScript 编写,并且通过 [Netlify CLI](https://docs.netlify.com/cli/get-started/) 使部署变得简单。使用 Hono,你可以为 Netlify 边缘函数创建应用程序。
## 1. 设置
Netlify 的入门模板可用。
使用 "create-hono" 命令启动你的项目。
为本示例选择 `netlify` 模板。
::: 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`。
## 2. Hello World
编辑 `netlify/edge-functions/index.ts`:
```ts
import { Hono } from 'jsr:@hono/hono'
import { handle } from 'jsr:@hono/hono/netlify'
const app = new Hono()
app.get('/', (c) => {
return c.text('Hello Hono!')
})
export default handle(app)
```
## 3. 运行
使用 Netlify CLI 运行开发服务器。然后,在你的 Web 浏览器中访问 `http://localhost:8888`。
```sh
netlify dev
```
## 4. 部署
你可以使用 `netlify deploy` 命令进行部署。
```sh
netlify deploy --prod
```
## `上下文`
你可以通过 `c.env` 访问 Netlify 的 `上下文`:
```ts
import { Hono } from 'jsr:@hono/hono'
import { handle } from 'jsr:@hono/hono/netlify'
// 导入类型定义
import type { Context } from 'https://edge.netlify.com/'
export type Env = {
Bindings: {
context: Context
}
}
const app = new Hono()
app.get('/country', (c) =>
c.json({
'You are in': c.env.context.geo.country?.name,
})
)
export default handle(app)
```
# Next.js
Next.js 是一个灵活的 React 框架,为您提供构建快速 Web 应用程序的基石。
在使用 Node.js 运行时时,您可以在 Next.js 上运行 Hono。\
在 Vercel 上,通过使用 Vercel Functions,可以轻松部署带有 Hono 的 Next.js 应用。
## 1. 设置
提供了一个 Next.js 入门模板。
使用 "create-hono" 命令启动您的项目。
本示例请选择 `nextjs` 模板。
::: 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` 目录并安装依赖。
::: 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. 你好世界
如果您使用 App Router,请编辑 `app/api/[[...route]]/route.ts`。更多选项请参考 [支持的 HTTP 方法](https://nextjs.org/docs/app/building-your-application/routing/route-handlers#supported-http-methods) 部分。
```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. 运行
在本地运行开发服务器。然后,在浏览器中访问 `http://localhost:3000`。
::: 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 创建全栈应用程序。
## 4. 部署
如果您拥有 Vercel 账户,可以通过关联 Git 仓库进行部署。
## Pages Router
如果您使用 Pages Router,首先需要安装 Node.js 适配器。
::: 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` 函数。
```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 助手。
```text
NODEJS_HELPERS=0
```
# Node.js
[Node.js](https://nodejs.org/) 是一个开源、跨平台的 JavaScript 运行时环境。
Hono 最初并非为 Node.js 设计,但通过 [Node.js Adapter](https://github.com/honojs/node-server),它也可以在 Node.js 上运行。
::: info
它适用于大于 18.x 的 Node.js 版本。具体所需的 Node.js 版本如下:
- 18.x => 18.14.1+
- 19.x => 19.7.0+
- 20.x => 20.0.0+
基本上,你可以简单地使用每个主要版本的最新版本。
:::
## 1. 设置
提供了适用于 Node.js 的启动器。
使用 "create-hono" 命令启动你的项目。
本示例请选择 `nodejs` 模板。
::: 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` 并安装依赖。
::: 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. 你好世界
编辑 `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)
```
如果你想优雅地关闭服务器,可以这样写:
```ts
const server = serve(app)
// 优雅关闭
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. 运行
在本地运行开发服务器。然后,在 Web 浏览器中访问 `http://localhost:3000`。
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm dev
```
:::
## 更改端口号
你可以使用 `port` 选项指定端口号。
```ts
serve({
fetch: app.fetch,
port: 8787,
})
```
## 访问原生 Node.js API
你可以通过 `c.env.incoming` 和 `c.env.outgoing` 访问 Node.js API。
```ts
import { Hono } from 'hono'
import { serve, type HttpBindings } from '@hono/node-server'
// 如果你使用 HTTP2,则使用 `Http2Bindings`
type Bindings = HttpBindings & {
/* ... */
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/', (c) => {
return c.json({
remoteAddress: c.env.incoming.socket.remoteAddress,
})
})
serve(app)
```
## 提供静态文件
你可以使用 `serveStatic` 从本地文件系统提供静态文件。例如,假设目录结构如下:
```sh
./
├── favicon.ico
├── index.ts
└── static
├── hello.txt
└── image.png
```
如果有请求访问路径 `/static/*` 并且你想返回 `./static` 下的文件,你可以这样写:
```ts
import { serveStatic } from '@hono/node-server/serve-static'
app.use('/static/*', serveStatic({ root: './' }))
```
::: warning
`root` 选项相对于当前工作目录(`process.cwd()`)解析路径。这意味着行为取决于**你从何处运行 Node.js 进程**,而不是源文件的位置。如果你从不同的目录启动服务器,文件解析可能会失败。
为了可靠的路径解析,始终指向与源文件相同的目录,请使用 `import.meta.url`:
```ts
import { fileURLToPath } from 'node:url'
import { serveStatic } from '@hono/node-server/serve-static'
app.use(
'/static/*',
serveStatic({ root: fileURLToPath(new URL('./', import.meta.url)) })
)
```
:::
使用 `path` 选项提供目录根目录下的 `favicon.ico`:
```ts
app.use('/favicon.ico', serveStatic({ path: './favicon.ico' }))
```
如果有请求访问路径 `/hello.txt` 或 `/image.png` 并且你想返回名为 `./static/hello.txt` 或 `./static/image.png` 的文件,你可以使用以下内容:
```ts
app.use('*', serveStatic({ root: './static' }))
```
### `rewriteRequestPath`
如果你想将 `http://localhost:3000/static/*` 映射到 `./statics`,你可以使用 `rewriteRequestPath` 选项:
```ts
app.get(
'/static/*',
serveStatic({
root: './',
rewriteRequestPath: (path) =>
path.replace(/^\/static/, '/statics'),
})
)
```
## http2
你可以在 [Node.js http2 Server](https://nodejs.org/api/http2.html) 上运行 hono。
### 未加密的 http2
```ts
import { createServer } from 'node:http2'
const server = serve({
fetch: app.fetch,
createServer,
})
```
### 加密的 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'),
},
})
```
## 构建与部署
::: 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)。
:::
### Dockerfile
这是一个 Node.js 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"]
```
# Service Worker
[Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) 是一个在浏览器后台运行的脚本,用于处理缓存和推送通知等任务。使用 Service Worker 适配器,你可以在浏览器内将用 Hono 制作的应用作为 [FetchEvent](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent) 处理器运行。
本页展示了使用 [Vite](https://vitejs.dev/) 创建项目的示例。
## 1. 设置
首先,创建并进入你的项目目录:
```sh
mkdir my-app
cd my-app
```
创建项目所需的文件。创建一个包含以下内容的 `package.json` 文件:
```json
{
"name": "my-app",
"private": true,
"scripts": {
"dev": "vite dev"
},
"type": "module"
}
```
同样,创建一个包含以下内容的 `tsconfig.json` 文件:
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM", "WebWorker"],
"moduleResolution": "bundler"
},
"include": ["./"],
"exclude": ["node_modules"]
}
```
接下来,安装必要的模块。
::: code-group
```sh [npm]
npm i hono
npm i -D vite
```
```sh [yarn]
yarn add hono
yarn add -D vite
```
```sh [pnpm]
pnpm add hono
pnpm add -D vite
```
```sh [bun]
bun add hono
bun add -D vite
```
:::
## 2. Hello World
编辑 `index.html`:
```html
Service Worker 的 Hello World
```
`main.ts` 是一个用于注册 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` 的访问。
```ts
// 为了支持类型
// 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()`
`fire()` 函数会自动为你调用 `addEventListener('fetch', handle(app))`,使代码更简洁。
```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. 运行
启动开发服务器。
::: 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 应用的响应。
# Supabase 边缘函数
[Supabase](https://supabase.com/) 是 Firebase 的开源替代品,提供了一套类似于 Firebase 功能的工具套件,包括数据库、身份验证、存储,以及现在的无服务器函数。
Supabase 边缘函数是服务器端 TypeScript 函数,它们在全球范围内分布,运行在离用户更近的地方以提高性能。这些函数使用 [Deno](https://deno.com/) 开发,带来了多种好处,包括提高安全性和现代化的 JavaScript/TypeScript 运行时。
以下是开始使用 Supabase 边缘函数的方法:
## 1. 设置
### 前提条件
开始之前,请确保已安装 Supabase CLI。如果尚未安装,请按照 [官方文档](https://supabase.com/docs/guides/cli/getting-started) 中的说明进行操作。
### 创建新项目
1. 打开终端或命令提示符。
2. 通过在本地机器上的目录中运行以下命令创建一个新的 Supabase 项目:
```bash
supabase init
```
此命令在当前目录中初始化一个新的 Supabase 项目。
### 添加边缘函数
3. 在 Supabase 项目中,创建一个名为 `hello-world` 的新边缘函数:
```bash
supabase functions new hello-world
```
此命令在项目中创建一个指定名称的新边缘函数。
## 2. 你好世界
通过修改文件 `supabase/functions/hello-world/index.ts` 来编辑 `hello-world` 函数:
```ts
import { Hono } from 'jsr:@hono/hono'
// 将此更改为你的函数名
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. 运行
要在本地运行函数,请使用以下命令:
1. 使用以下命令来服务函数:
```bash
supabase start # 启动 supabase 栈
supabase functions serve --no-verify-jwt # 启动 Functions 监视器
```
`--no-verify-jwt` 标志允许你在本地开发期间绕过 JWT 验证。
2. 使用 cURL 或 Postman 向 `http://127.0.0.1:54321/functions/v1/hello-world/hello` 发送 GET 请求:
```bash
curl --location 'http://127.0.0.1:54321/functions/v1/hello-world/hello'
```
此请求应返回文本 "Hello from hono-server!"。
## 4. 部署
你可以使用一条命令部署 Supabase 中的所有边缘函数:
```bash
supabase functions deploy
```
或者,你可以通过在部署命令中指定函数名称来部署单个边缘函数:
```bash
supabase functions deploy hello-world
```
有关更多部署方法,请访问 Supabase 文档关于 [部署到生产环境](https://supabase.com/docs/guides/functions/deploy) 的部分。
# Vercel
Vercel 是 AI 云,提供开发者工具和云基础设施,以构建、扩展和保护更快、更个性化的 Web。
Hono 可以零配置部署到 Vercel。
## 1. 设置
提供了一个 Vercel 的 starter 模板。
使用 "create-hono" 命令开始你的项目。
为本示例选择 `vercel` 模板。
::: 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` 并安装依赖。
::: 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) 全局安装它。
## 2. 你好世界
在项目的 `index.ts` 或 `src/index.ts` 中,将 Hono 应用作为默认导出。
```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` 模板开始项目,这已经为你设置好了。
## 3. 运行
要在本地运行开发服务器:
```sh
vercel dev
```
访问 `localhost:3000` 将返回文本响应。
## 4. 部署
使用 `vc deploy` 部署到 Vercel。
```sh
vercel deploy
```
## 进一步阅读
[在 Vercel 文档中了解更多关于 Hono 的信息](https://vercel.com/docs/frameworks/backend/hono)。
# WebAssembly (w/ WASI)
[WebAssembly][wasm-core] 是一个安全、沙箱化、可移植的运行时,可在网页浏览器内部和外部运行。
在实践中:
- 语言(如 JavaScript)_编译为_ WebAssembly(`.wasm` 文件)
- WebAssembly 运行时(如 [`wasmtime`][wasmtime] 或 [`jco`][jco])支持_运行_ WebAssembly 二进制文件
虽然核心 WebAssembly _无法_ 访问本地文件系统或套接字等内容,但 [WebAssembly 系统接口][wasi]
介入以支持在 WebAssembly 工作负载下定义平台。
这意味着_借助_ WASI,WebAssembly 可以操作文件、套接字以及更多内容。
::: info
想亲自看看 WASI 接口吗?查看 [`wasi:http`][wasi-http]
:::
JS 中对 WebAssembly w/ WASI 的支持由 [StarlingMonkey][sm] 驱动,得益于
StarlingMonkey 和 Hono 都对 Web 标准的关注,**Hono 可以*\*开箱即用\*地支持启用 WASI 的 WebAssembly 生态系统。**
[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. 设置
WebAssembly JS 生态系统提供了工具,使开始构建启用 WASI 的 WebAssembly 组件变得容易:
- [StarlingMonkey][sm] 是 [SpiderMonkey][spidermonkey] 的一个分支,可编译为 WebAssembly 并启用组件
- [`componentize-js`][componentize-js] 将 JavaScript ES 模块转换为 WebAssembly 组件
- [`jco`][jco] 是一个多用途工具,可构建组件、生成类型并在 Node.js 或浏览器等环境中运行组件
::: info
WebAssembly 拥有一个开放的生态系统且是开源的,核心项目主要由 [Bytecode Alliance][bca] 及其成员管理。
新功能、问题、拉取请求和其他类型的贡献总是受欢迎的。
:::
虽然 Hono 在 WebAssembly 上的 starter 尚不可用,但你可以像其他任何项目一样启动一个 WebAssembly Hono 项目:
::: 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
````
```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"`
:::
进入 `my-app` 文件夹后,安装依赖项并初始化 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`),请确保它具有以下配置:
- `compilerOptions.module` 设置为 `"nodenext"`
由于 `componentize-js`(以及重用它的 `jco`)仅支持单个 JS 文件,
因此需要进行捆绑,所以可以使用 [`rolldown`][rolldown] 来创建单个文件捆绑包。
可以使用如下所示的 Rolldown 配置(`rolldown.config.mjs`):
```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` 等)
:::
[jco]: https://github.com/bytecodealliance/jco
[componentize-js]: https://github.com/bytecodealliance/componentize-js
[rolldown]: https://rolldown.rs
[spidermonkey]: https://spidermonkey.dev/
## 2. 设置 WIT 接口和依赖
[WebAssembly 接口类型 (WIT)][wit] 是一种接口定义语言("IDL"),它管理 WebAssembly 组件使用的功能("imports")以及它提供的功能("exports")。
在标准化的 WIT 接口中,[`wasi:http`][wasi-http] 用于处理 HTTP 请求(无论是接收还是发送),由于我们打算制作一个 Web 服务器,我们的组件必须在其 [WIT 世界][wit-world] 中声明使用 `wasi:http/incoming-handler`:
首先,让我们在名为 `wit/component.wit` 的文件中设置组件的 WIT 世界:
```txt
package example:hono;
world component {
export wasi:http/incoming-handler@0.2.6;
}
```
简单来说,上面的 WIT 文件意味着我们的组件“提供”“接收”/“处理传入”HTTP 请求的功能。
`wasi:http/incoming-handler` 接口依赖于上游标准化的 WIT 接口(关于请求结构等的规范)。
为了拉取那些第三方(由 Bytecode Alliance 维护)的 WIT 接口,我们可以使用的一个工具是 [`wkg`][wkg]:
```sh
wkg wit fetch
```
`wkg` 运行完成后,你应该会发现你的 `wit` 文件夹中除了 `component.wit` 外,还填充了一个新的 `deps` 文件夹:
```
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 体验非常相似。
让我们在名为 `src/component.ts` 的文件中用一个基本的 Hono 应用程序作为 WebAssembly 组件来实现我们的 `component` 世界:
```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)
// 虽然我们在上面配置了 wasi HTTP 并调用了 `fire()`,
// 我们仍然需要实际导出 `wasi:http/incoming-handler` 接口对象,
// 因为 jco 和 componentize-js 将寻找匹配 WASI 接口的 ES 模块导出。
export { incomingHandler } from '@bytecodealliance/jco-std/wasi/0.2.6/http/adapters/hono/server'
```
## 4. 构建
由于我们使用的是 Rolldown(并且已配置为处理 TypeScript 编译),我们可以使用它来构建和捆绑:
::: 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 以及相关库。
对于需求更简单的组件,捆绑器不是必须的。
:::
要构建你的 WebAssembly 组件,请使用 `jco`(并间接使用 `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. 运行
要运行你的 Hono WebAssembly HTTP 服务器,你可以使用任何启用 WASI 的 WebAssembly 运行时:
- [`wasmtime`][wasmtime]
- `jco`(在 Node.js 中运行)
在本指南中,我们将使用 `jco serve`,因为它已经安装好了。
::: warning
`jco serve` 旨在用于开发,不建议用于生产环境。
:::
[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
```
:::
你应该看到如下输出:
```
$ npx jco serve dist/component.wasm
Server listening @ localhost:8000...
```
向 `localhost:8000/hello` 发送请求将产生你在 Hono 应用程序中指定的 JSON 输出。
你应该看到如下输出:
```json
{ "message": "Hello from WebAssembly!" }
```
::: info
`jco serve` 的工作原理是将 WebAssembly 组件转换为基本的 WebAssembly 核心模块,以便它可以在 Node.js 和浏览器等运行时中运行。
此过程通常通过 `jco transpile` 运行,这也是我们可以使用 JS 引擎(如 Node.js 和浏览器(可能使用 V8 或其他 Javascript 引擎))作为 WebAssembly 组件运行时的方式。
`jco transpile` 的工作原理不在本指南范围内,你可以在 [Jco book][jco-book] 中阅读更多相关内容
:::
## 更多信息
要了解有关 WASI、WebAssembly 组件等的更多信息,请参阅以下资源:
- [BytecodeAlliance Component Model book][cm-book]
- [`jco` 代码库][jco]
- [`jco` 示例组件][jco-example-components](特别是 [Hono 示例][jco-example-component-hono])
- [Jco book][jco-book]
- [`componentize-js` 代码库][componentize-js]
- [StarlingMonkey 代码库][sm]
要向 WebAssembly 社区咨询问题、评论、贡献或提交问题:
- [Bytecode Alliance Zulip](https://bytecodealliance.zulipchat.com)(考虑在 [#jco 频道](https://bytecodealliance.zulipchat.com/#narrow/channel/409526-jco) 中发帖)
- [Jco 仓库](https://github.com/bytecodealliance/jco)
- [componentize-js 仓库](https://github.com/bytecodealliance/componentize-js)
[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
# 上下文
`Context` 对象为每个请求实例化,并保留直到响应返回。你可以将值放入其中,设置要返回的标头和状态码,并访问 HonoRequest 和 Response 对象。
## req
`req` 是 HonoRequest 的实例。更多详情,请参阅 [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()`。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/posts', (c) => {
// 设置 HTTP 状态码
c.status(201)
return c.text('Your post is created!')
})
```
## header()
你可以为响应设置 HTTP 标头。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/', (c) => {
// 设置标头
c.header('X-Message', 'My custom message')
return c.text('Hello!')
})
```
## body()
返回 HTTP 响应。
::: info
**注意**:当返回文本或 HTML 时,建议使用 `c.text()` 或 `c.html()`。
:::
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/welcome', (c) => {
c.header('Content-Type', 'text/plain')
// 返回响应体
return c.body('Thank you for coming')
})
```
你也可以这样写。
```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` 对象相同。
```ts twoslash
new Response('Thank you for coming', {
status: 201,
headers: {
'X-Message': 'Hello!',
'Content-Type': 'text/plain',
},
})
```
## text()
将文本渲染为 `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`。
```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`。
```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) 自定义它。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/notfound', (c) => {
return c.notFound()
})
```
## redirect()
重定向,默认状态码为 `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] 对象。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
// Response 对象
app.use('/', async (c, next) => {
await next()
c.res.headers.append('X-Debug', 'Debug message')
})
```
[Response]: https://developer.mozilla.org/en-US/docs/Web/API/Response
## set() / get()
获取和设置任意键值对,生命周期为当前请求。这允许在中间件之间或从中间件到路由处理器传递特定值。
```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` 的构造函数以使其类型安全。
```ts twoslash
import { Hono } from 'hono'
// ---cut---
type Variables = {
message: string
}
const app = new Hono<{ Variables: Variables }>()
```
`c.set` / `c.get` 的值仅在同一请求内保留。它们不能在不同请求之间共享或持久化。
## var
你也可以使用 `c.var` 访问变量的值。
```ts twoslash
import type { Context } from 'hono'
declare const c: Context
// ---cut---
const result = c.var.client.oneMethod()
```
如果你想创建一个提供自定义方法的中间件,
请如下编写:
```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` 的构造函数以使其类型安全。
```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()` 设置布局。
```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()` 在此布局内创建响应。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/', (c) => {
return c.render('Hello!')
})
```
输出将为:
```html
Hello!
```
此外,此功能提供了自定义参数的灵活性。
为了确保类型安全,类型可以定义为:
```ts
declare module 'hono' {
interface ContextRenderer {
(
content: string | Promise,
head: { title: string }
): Response | Promise
}
}
```
以下是如何使用此功能的示例:
```ts
app.use('/pages/*', async (c, next) => {
c.setRenderer((content, head) => {
return c.html(
{head.title}
{content}
)
})
await next()
})
app.get('/pages/my-favorite', (c) => {
return c.render(Ramen and Sushi
, {
title: 'My favorite',
})
})
app.get('/pages/my-hobbies', (c) => {
return c.render(Watching baseball
, {
title: 'My hobbies',
})
})
```
## executionCtx
你可以访问 Cloudflare Workers 特定的 [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 对象
app.get('/foo', async (c) => {
c.executionCtx.waitUntil(c.env.KV.put(key, data))
// ...
})
```
`ExecutionContext` 还有一个 [`exports`](https://developers.cloudflare.com/workers/runtime-apis/context/#exports) 字段。要使用 Wrangler 生成的类型获得自动补全,你可以使用模块 augmentation:
```ts
import 'hono'
declare module 'hono' {
interface ExecutionContext {
readonly exports: Cloudflare.Exports
}
}
```
## event
你可以访问 Cloudflare Workers 特定的 `FetchEvent`。这在 "Service Worker" 语法中使用过。但现在不推荐使用。
```ts twoslash
import { Hono } from 'hono'
declare const key: string
declare const data: string
type KVNamespace = any
// ---cut---
// 用于类型推断的类型定义
type Bindings = {
MY_KV: KVNamespace
}
const app = new Hono<{ Bindings: Bindings }>()
// FetchEvent 对象(仅在使用 Service Worker 语法时设置)
app.get('/foo', async (c) => {
c.event.waitUntil(c.env.MY_KV.put(key, data))
// ...
})
```
## env
在 Cloudflare Workers 环境变量中,绑定到 worker 的 secrets、KV 命名空间、D1 数据库、R2 存储桶等被称为 bindings。
无论类型如何,bindings 始终可作为全局变量使用,并可通过上下文 `c.env.BINDING_KEY` 访问。
```ts twoslash
import { Hono } from 'hono'
type KVNamespace = any
// ---cut---
// 用于类型推断的类型定义
type Bindings = {
MY_KV: KVNamespace
}
const app = new Hono<{ Bindings: Bindings }>()
// Cloudflare Workers 的环境对象
app.get('/', async (c) => {
c.env.MY_KV.get('my-key')
// ...
})
```
## error
如果 Handler 抛出错误,错误对象将放置在 `c.error` 中。
你可以在中间件中访问它。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.use(async (c, next) => {
await next()
if (c.error) {
// 做一些事情...
}
})
```
## ContextVariableMap
例如,如果你希望在特定中间件使用时为变量添加类型定义,你可以扩展 `ContextVariableMap`。例如:
```ts
declare module 'hono' {
interface ContextVariableMap {
result: string
}
}
```
然后你可以在中间件中使用它:
```ts twoslash
import { createMiddleware } from 'hono/factory'
// ---cut---
const mw = createMiddleware(async (c, next) => {
c.set('result', 'some values') // result 是一个字符串
await next()
})
```
在处理器中,变量被推断为正确的类型:
```ts twoslash
import { Hono } from 'hono'
const app = new Hono<{ Variables: { result: string } }>()
// ---cut---
app.get('/', (c) => {
const val = c.get('result') // val 是一个字符串
// ...
return c.json({ result: val })
})
```
# HTTPException
当发生致命错误时,Hono(以及许多生态系统中间件)可能会抛出 `HTTPException`。这是一个自定义的 Hono `Error`,简化了 [返回错误响应](#handling-httpexceptions) 的过程。
## 抛出 HTTPExceptions
你可以通过指定状态码,以及消息或自定义响应来抛出你自己的 HTTPExceptions。
### 自定义消息
对于基本的 `text` 响应,只需设置错误 `message`。
```ts twoslash
import { HTTPException } from 'hono/http-exception'
throw new HTTPException(401, { message: 'Unauthorized' })
```
### 自定义响应
对于其他响应类型,或者要设置响应头,请使用 `res` 选项。_注意,传递给构造函数的状态码是用于创建响应的状态码。_
```ts twoslash
import { HTTPException } from 'hono/http-exception'
const errorResponse = new Response('Unauthorized', {
status: 401, // 这将被忽略
headers: {
Authenticate: 'error="invalid_token"',
},
})
throw new HTTPException(401, { res: errorResponse })
```
### 原因
在任何一种情况下,你都可以使用 [`cause`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) 选项向 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
你可以使用 [`app.onError`](/docs/api/hono#error-handling) 处理未捕获的 HTTPExceptions。它们包含一个 `getResponse` 方法,该方法返回一个新的 `Response`,该响应是根据错误 `status` 创建的,并使用错误 `message` 或抛出错误时设置的 [自定义响应](#custom-response)。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
import { HTTPException } from 'hono/http-exception'
// ...
app.onError((err, c) => {
if (err instanceof HTTPException) {
// 返回 HTTPException 生成的错误响应
return err.getResponse()
}
// 对于任何其他意外错误,记录日志并返回通用的 500 响应
console.error(err)
return c.text('Internal Server Error', 500)
})
```
::: warning
**`HTTPException.getResponse` 不知道 `Context`**。要包含已在 `Context` 中设置的头,你必须将它们应用到一个新的 `Response` 上。
:::
# 应用 - Hono
`Hono` 是主要对象。
它将首先被导入并一直使用到最后。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
//...
export default app // 用于 Cloudflare Workers 或 Bun
```
## 方法
`Hono` 的实例拥有以下方法。
- 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)。
## 未找到
`app.notFound` 允许你自定义未找到响应。
```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)。
:::
## 错误处理
`app.onError` 允许你处理未捕获的错误并返回自定义响应。
```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` 处理程序,则路由级处理程序优先。
:::
## fire()
::: warning
**`app.fire()` 已弃用**。请改用 `hono/service-worker` 中的 `fire()`。详见 [Service Worker 文档](/docs/getting-started/service-worker)。
:::
`app.fire()` 自动添加一个全局 `fetch` 事件监听器。
这对于遵循 [Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) 的环境很有用,例如 [非 ES 模块 Cloudflare Workers](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/)。
`app.fire()` 为你执行以下操作:
```ts
addEventListener('fetch', (event: FetchEventLike): void => {
event.respondWith(this.dispatch(...))
})
```
## fetch()
`app.fetch` 将成为你的应用程序入口点。
对于 Cloudflare Workers,你可以使用以下内容:
```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)
},
}
```
或者直接这样做:
```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` 是一个用于测试的有用方法。
你可以传递 URL 或路径名来发送 GET 请求。
`app` 将返回一个 `Response` 对象。
```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` 对象:
```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 应用中。
```ts
import { Router as IttyRouter } from 'itty-router'
import { Hono } from 'hono'
// 创建 itty-router 应用
const ittyRouter = IttyRouter()
// 处理 `GET /itty-router/hello`
ittyRouter.get('/hello', () => new Response('Hello from itty-router'))
// Hono 应用
const app = new Hono()
// 挂载!
app.mount('/itty-router', ittyRouter.handle)
```
## 严格模式
严格模式默认为 `true` 并区分以下路由。
- `/hello`
- `/hello/`
`app.get('/hello')` 不会匹配 `GET /hello/`。
通过将严格模式设置为 `false`,两条路径将被同等对待。
```ts twoslash
import { Hono } from 'hono'
// ---cut---
const app = new Hono({ strict: false })
```
## router 选项
`router` 选项指定使用哪个路由器。默认路由器是 `SmartRouter`。如果你想使用 `RegExpRouter`,将其传递给新的 `Hono` 实例:
```ts twoslash
import { Hono } from 'hono'
// ---cut---
import { RegExpRouter } from 'hono/router/reg-exp-router'
const app = new Hono({ router: new RegExpRouter() })
```
## 泛型
你可以传递泛型来指定 `c.set`/`c.get` 中使用的 Cloudflare Workers Bindings 和变量的类型。
```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 是 `string`
// ...
c.set('user', user) // user 应该是 `User`
await next()
})
```
# API
Hono 的 API 很简单。
仅由基于 Web 标准扩展的对象组成。
因此,你可以很快上手。
在本节中,我们将介绍 Hono 的 API,如下所示。
- Hono 对象
- 关于路由
- 上下文对象
- 关于中间件
# 预设
Hono 拥有多个路由器,每个路由器都为特定目的而设计。
您可以在 Hono 的构造函数中指定想要使用的路由器。
**预设** 是为常见用例提供的,因此您不必每次都指定路由器。
从所有预设中导入的 `Hono` 类是相同的,唯一区别在于路由器。
因此,您可以互换使用它们。
## `hono`
用法:
```ts twoslash
import { Hono } from 'hono'
```
路由器:
```ts
this.router = new SmartRouter({
routers: [new RegExpRouter(), new TrieRouter()],
})
```
## `hono/quick`
用法:
```ts twoslash
import { Hono } from 'hono/quick'
```
路由器:
```ts
this.router = new SmartRouter({
routers: [new LinearRouter(), new TrieRouter()],
})
```
## `hono/tiny`
用法:
```ts twoslash
import { Hono } from 'hono/tiny'
```
路由器:
```ts
this.router = new PatternRouter()
```
## 我应该使用哪个预设?
| 预设 | 适合的平台 |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `hono` | 这是大多数用例的强烈推荐选项。虽然注册阶段可能比 `hono/quick` 慢,但一旦启动,它表现出高性能。它非常适合使用 **Deno**、**Bun** 或 **Node.js** 构建的长生命周期服务器。它也适用于 **Fastly Compute**,因为在该平台上路由注册发生在应用构建阶段。对于利用 v8 isolates 的环境,如 **Cloudflare Workers**、**Deno Deploy**,此预设也适用。因为隔离在启动后会持续一定时间。 |
| `hono/quick` | 此预设专为每个请求都初始化应用程序的环境而设计。 |
| `hono/tiny` | 这是最小的路由器包,适用于资源有限的环境。 |
# HonoRequest
`HonoRequest` 是一个对象,可以从 `c.req` 获取,它包装了一个 [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) 对象。
## param()
获取路径参数的值。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
// 捕获的参数
app.get('/entry/:id', async (c) => {
const id = c.req.param('id')
// ^?
// ...
})
// 一次性获取所有参数
app.get('/entry/:id/comment/:commentId', async (c) => {
const { id, commentId } = c.req.param()
// ^?
})
```
## query()
获取查询字符串参数。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
// 查询参数
app.get('/search', async (c) => {
const query = c.req.query('q')
// ^?
})
// 一次性获取所有参数
app.get('/search', async (c) => {
const { q, limit, offset } = c.req.query()
// ^?
})
```
## queries()
获取多个查询字符串参数值,例如 `/search?tags=A&tags=B`
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/search', async (c) => {
// tags 将是 string[]
const tags = c.req.queries('tags')
// ^?
// ...
})
```
## header()
获取请求头值。
```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()` 时,返回记录中的所有键都是 **小写** 的。
如果你想获取大写名称的请求头的值,
请使用 `c.req.header("X-Foo")`。
```ts
// ❌ 不起作用
const headerRecord = c.req.header()
const foo = headerRecord['X-Foo']
// ✅ 起作用
const foo = c.req.header('X-Foo')
```
:::
## parseBody()
解析类型为 `multipart/form-data` 或 `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()` 支持以下行为。
**单个文件**
```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)`。
如果上传了多个文件,将使用最后一个。
### 多个文件
```ts twoslash
import { Context } from 'hono'
declare const c: Context
// ---cut---
const body = await c.req.parseBody()
body['foo[]']
```
`body['foo[]']` 始终是 `(string | File)[]`。
`[]` 后缀是必需的。
### 多个文件或同名字段
如果你有一个允许多个 `` 的输入字段,或者多个具有相同名称的复选框 ``。
```ts twoslash
import { Context } from 'hono'
declare const c: Context
// ---cut---
const body = await c.req.parseBody({ all: true })
body['foo']
```
`all` 选项默认是禁用的。
- 如果 `body['foo']` 是多个文件,它将被解析为 `(string | File)[]`。
- 如果 `body['foo']` 是单个文件,它将被解析为 `(string | File)`。
### 点表示法
如果你将 `dot` 选项设置为 `true`,返回值将基于点表示法进行结构化。
想象接收以下数据:
```ts twoslash
const data = new FormData()
data.append('obj.key1', 'value1')
data.append('obj.key2', 'value2')
```
你可以通过将 `dot` 选项设置为 `true` 来获取结构化值:
```ts twoslash
import { Context } from 'hono'
declare const c: Context
// ---cut---
const body = await c.req.parseBody({ dot: true })
// body 是 `{ obj: { key1: 'value1', key2: 'value2' } }`
```
## json()
解析类型为 `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` 的请求体
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.text()
// ...
})
```
## arrayBuffer()
将请求体解析为 `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`。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.blob()
// ...
})
```
## formData()
将请求体解析为 `FormData`。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.formData()
// ...
})
```
## valid()
获取验证后的数据。
```
app.post('/posts', async (c) => {
const { title, body } = c.req.valid('form')
// ...
})
```
可用的目标如下。
- `form`
- `json`
- `query`
- `header`
- `cookie`
- `param`
参见 [验证部分](/docs/guides/validation) 了解使用示例。
## routePath
::: warning
**已在 v4.8.0 中弃用**:此属性已弃用。请改用 [路由助手](/docs/helpers/route) 中的 `routePath()`。
:::
你可以像在这样在处理程序中检索注册的路径:
```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`:
```json
{ "path": "/posts/:id" }
```
## matchedRoutes
::: warning
**已在 v4.8.0 中弃用**:此属性已弃用。请改用 [路由助手](/docs/helpers/route) 中的 `matchedRoutes()`。
:::
它返回处理程序中匹配的路由,这对于调试很有用。
```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
请求路径名。
```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 字符串。
```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
请求的方法名。
```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://developer.mozilla.org/en-US/docs/Web/API/Request) 对象。
```ts
// 对于 Cloudflare Workers
app.post('/', async (c) => {
const metadata = c.req.raw.cf?.hostMetadata?
// ...
})
```
## cloneRawRequest()
从 HonoRequest 克隆原始 Request 对象。即使在请求体已被验证器或 HonoRequest 方法消耗后也能工作。
```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) => {
// 验证后克隆
const clonedReq = await cloneRawRequest(c.req)
// 不会抛出错误
await clonedReq.json()
// ...
}
)
```
# 路由
Hono 的路由灵活且直观。
让我们来看看。
## 基础
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
// HTTP 方法
app.get('/', (c) => c.text('GET /'))
app.post('/', (c) => c.text('POST /'))
app.put('/', (c) => c.text('PUT /'))
app.delete('/', (c) => c.text('DELETE /'))
// 通配符
app.get('/wild/*/card', (c) => {
return c.text('GET /wild/*/card')
})
// 任意 HTTP 方法
app.all('/hello', (c) => c.text('Any Method /hello'))
// 自定义 HTTP 方法
app.on('PURGE', '/cache', (c) => c.text('PURGE Method /cache'))
// 多个方法
app.on(['PUT', 'DELETE'], '/post', (c) =>
c.text('PUT or DELETE /post')
)
// 多个路径
app.on('GET', ['/hello', '/ja/hello', '/en/hello'], (c) =>
c.text('Hello')
)
```
## 路径参数
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/user/:name', async (c) => {
const name = c.req.param('name')
// ^?
// ...
})
```
或者一次性获取所有参数:
```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()
// ^?
// ...
})
```
## 可选参数
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
// 将匹配 `/api/animal` 和 `/api/animal/:type`
app.get('/api/animal/:type?', (c) => c.text('Animal!'))
```
## 正则表达式
```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()
// ^?
// ...
})
```
## 包含斜杠
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/posts/:filename{.+\\.png}', async (c) => {
//...
})
```
## 链式路由
```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')
})
```
## 分组
你可以使用 Hono 实例对路由进行分组,并使用 route 方法将它们添加到主应用中。
```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)
```
## 不改变基础路径的分组
你也可以在保持基础路径的同时对多个实例进行分组。
```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) // 处理 /book
app.route('/', user) // 处理 /user
```
## 基础路径
你可以指定基础路径。
```ts twoslash
import { Hono } from 'hono'
// ---cut---
const api = new Hono().basePath('/api')
api.get('/book', (c) => c.text('List Books')) // GET /api/book
```
## 带主机名的路由
如果包含主机名,它也能正常工作。
```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` Header 值的路由
如果你在 Hono 构造函数中设置了 `getPath()` 函数,Hono 可以处理 `host` header 值。
```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'))
// 以下请求将匹配该路由:
// new Request('http://www1.example.com/hello', {
// headers: { host: 'www1.example.com' },
// })
```
通过应用这一点,例如,你可以根据 `User-Agent` header 更改路由。
## 路由优先级
处理程序或中间件将按照注册顺序执行。
```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`
```
当处理程序执行时,过程将停止。
```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 不会被分发
```
如果你有想要执行的中间件,请将代码写在处理程序上方。
```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_"(回退)处理程序,请将代码写在其他处理程序下方。
```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`
```
## 分组顺序
请注意,路由分组的错误很难被发现。
`route()` 函数从第二个参数(例如 `three` 或 `two`)获取存储的路由,并将其添加到自身(`two` 或 `app`)的路由中。
```ts
three.get('/hi', (c) => c.text('hi'))
two.route('/three', three)
app.route('/two', two)
export default app
```
它将返回 200 响应。
```
GET /two/three/hi ---> `hi`
```
然而,如果顺序错误,它将返回 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` 没有路由
two.route('/three', three)
export default app
```
```
GET /two/three/hi ---> 404 Not Found
```