This is the tiny developer documentation for Hono.
# Start of Hono documentation
# Hono
Hono - _**在日语中意为火焰🔥**_ - 是一个小型、简单且超快的 Web 框架,基于 Web Standards 构建。
它可以在任何 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 Standards。
- **多运行时** 🌍 - 可在 Cloudflare Workers、Fastly Compute、Deno、Bun、AWS Lambda 或 Node.js 上运行。相同的代码在所有平台上都能运行。
- **功能齐全** 🔋 - Hono 内置了中间件、自定义中间件、第三方中间件和辅助函数。功能齐全。
- **愉悦的开发体验** 😃 - 超干净的 API。一流的 TypeScript 支持。现在,我们有了"类型"。
## 使用场景
Hono 是一个简单的 Web 应用程序框架,类似于 Express,不包含前端。
但它可以运行在 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) | 本地 AI 服务器 | 带记忆的无服务器 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 世界中最快的路由器。它使用调度前创建的单个大型 Regex 匹配路由。使用 **SmartRouter**,它支持所有路由模式。
**LinearRouter** 注册路由非常快,因此适合每次初始化应用程序的环境。**PatternRouter** 简单地添加和匹配模式,使其变小。
查看 [有关路由的更多信息](/docs/concepts/routers)。
## Web Standards
由于使用 **Web Standards**,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 Standards 的更多信息](/docs/concepts/web-standard)。
## 中间件和辅助函数
**Hono 有许多中间件和辅助函数**。这使得"少写代码,多做事情"成为现实。
开箱即用,Hono 提供以下中间件和辅助函数:
- [基本认证](/docs/middleware/builtin/basic-auth)
- [Bearer 认证](/docs/middleware/builtin/bearer-auth)
- [请求体限制](/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)
- [Pretty JSON](/docs/middleware/builtin/pretty-json)
- [安全头](/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` 对象,可以轻松访问请求/响应。
此外,Hono 使用 TypeScript 编写。Hono 拥有"**类型**"。
例如,路径参数将是字面量类型。

此外,Validator 和 Hono Client `hc` 启用 RPC 模式。在 RPC 模式中,
你可以使用喜欢的验证器(如 Zod),轻松与客户端共享服务器端 API 规范,并构建类型安全的应用程序。
查看 [Hono 技术栈](/docs/concepts/stacks)。
# Best Practices
Hono 非常灵活。你可以随心所欲地编写应用程序。然而,有一些最佳实践最好遵循。
## Don't make "Controllers" when possible
在可能的情况下,你不应该创建"Ruby on Rails-like Controllers"。
```ts
// 🙁
// 一个 RoR-like Controller
const booksList = (c: Context) => {
return c.json('list books')
}
app.get('/books', booksList)
```
问题与类型有关。例如,路径参数无法在 Controller 中推断,除非编写复杂的泛型。
```ts
// 🙁
// 一个 RoR-like Controller
const bookPermalink = (c: Context) => {
const id = c.req.param('id') // 无法推断路径参数
return c.json(`get ${id}`)
}
```
因此,你不需要创建 RoR-like controllers,应该在路径定义后直接编写 handlers。
```ts
// 😃
app.get('/books/:id', (c) => {
const id = c.req.param('id') // 可以推断路径参数
return c.json(`get ${id}`)
})
```
## `factory.createHandlers()` in `hono/factory`
如果你仍然想创建 RoR-like Controller,使用 `hono/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)
```
## Building a larger application
使用 `app.route()` 来构建更大的应用程序,而无需创建"Ruby on Rails-like Controllers"。
如果你的应用程序有 `/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
```
### If you want to use RPC features
上面的代码在普通用例中效果很好。但是,如果你想使用 `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 page](/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` 支持一些有用的标志,你可以传递这些标志来自动化和自定义项目创建(选择 templates、跳过提示、选择包管理器、使用本地缓存等)。
## Passing arguments:
当你使用 `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 template
yarn create hono my-app --template cloudflare-workers
```
```sh [pnpm]
# "--template cloudflare-workers" 选择 Cloudflare Workers template
pnpm create hono@latest my-app --template cloudflare-workers
```
```sh [bun]
# "--template cloudflare-workers" 选择 Cloudflare Workers template
bun create hono@latest my-app --template cloudflare-workers
```
```sh [deno]
# "--template cloudflare-workers" 选择 Cloudflare Workers template
deno init --npm hono@latest my-app --template cloudflare-workers
```
:::
## Commonly used arguments
| Argument | Description | Example |
| :---------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------ |
| `--template ` | 选择 starter template 并跳过交互式 template 提示。Templates 可能包括名称如 `bun`、`cloudflare-workers`、`vercel` 等。 | `--template cloudflare-workers` |
| `--install` | 在创建 template 后自动安装依赖项。 | `--install` |
| `--pm ` | 指定安装依赖项时运行哪个包管理器。常用值:`npm`、`pnpm`、`yarn`。 | `--pm pnpm` |
| `--offline` | 使用本地缓存/templates 而不是获取最新的远程 templates。适用于离线环境或确定性本地运行。 | `--offline` |
> [!NOTE]
> 确切的 templates 集和可用选项由 `create-hono` 项目维护。此文档页面总结了最常用的标志 - 查看下面链接的仓库获取完整、权威的参考。
## Example flows
### Minimal, interactive
```bash
npm create hono@latest my-app
```
这会提示你选择 template 和选项。
### Non-interactive, pick template and package manager
```bash
npm create hono@latest my-app -- --template vercel --pm npm --install
```
这使用 `vercel` template 创建 `my-app`,使用 `npm` 安装依赖项,并跳过交互式提示。
### Use offline cache (no network)
```bash
pnpm create hono@latest my-app --template deno --offline
```
## Troubleshooting & tips
- 如果某个选项似乎未被识别,请确保在使用 `npm create` / `npx` 时使用 `--` 转发它。
- 要查看最新的 templates 和标志列表,请咨询 `create-hono` 仓库或在本地运行初始化脚本并遵循其帮助输出。
## Links & references
- `create-hono` 仓库:[create-hono](https://github.com/honojs/create-hono)
# Examples
查看 [Examples section](/examples/)。
# Frequently Asked Questions
本指南是关于 Hono 的常见问题解答(FAQ)以及如何解决它们。
## Is there an official Renovate config for Hono?
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) 仓库了解更多详情。
# Helpers
Helpers 可用于协助开发你的应用程序。与中间件不同,它们不作为 handlers,而是提供有用的函数。
例如,以下是使用 [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
- [Accepts](/docs/helpers/accepts)
- [Adapter](/docs/helpers/adapter)
- [Cookie](/docs/helpers/cookie)
- [css](/docs/helpers/css)
- [Dev](/docs/helpers/dev)
- [Factory](/docs/helpers/factory)
- [html](/docs/helpers/html)
- [JWT](/docs/helpers/jwt)
- [SSG](/docs/helpers/ssg)
- [Streaming](/docs/helpers/streaming)
- [Testing](/docs/helpers/testing)
- [WebSocket](/docs/helpers/websocket)
# Client Components
`hono/jsx` 不仅支持服务器端,也支持客户端。这意味着可以创建在浏览器中运行的交互式 UI。我们称之为 Client Components 或 `hono/jsx/dom`。
它非常快且非常小。`hono/jsx/dom` 中的计数器程序使用 Brotli 压缩后仅为 2.8KB,而 React 为 47.8KB。
本节介绍 Client Components 特定的功能。
## Counter example
这是一个简单计数器的示例,与 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 组件。
```tsx
render(, container)
```
你可以在此处查看完整示例代码:[Counter example](https://github.com/honojs/examples/tree/main/hono-vite-jsx)。
## Hooks compatible with React
hono/jsx/dom 具有与 React 兼容或部分兼容的 Hooks。你可以通过查看 [React documentation](https://react.dev/reference/react/hooks) 了解这些 APIs。
- `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()` family
`startViewTransition()` 系列包含原始的 hooks 和函数,用于轻松处理 [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API)。以下是如何使用它们的示例。
### 1. An easiest example
你可以使用 `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. Using `viewTransition()` with `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. Using `useViewTransition`
如果你想在动画期间仅更改样式。你可以使用 `useViewTransition()`。这个 hook 返回 `[boolean, (callback: () => void) => void]`,它们是 `isUpdating` 标志和 `startViewTransition()` 函数。
当使用这个 hook 时,Component 会在以下两次进行评估。
- 在调用 `startViewTransition()` 的回调内。
- 当 [the `finish` promise becomes 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 ? (

) : (
)}
>
)
}
```
## The `hono/jsx/dom` runtime
有一个用于 Client Components 的小型 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 转换选项中指定 `hono/jsx/dom`。
```ts
import { defineConfig } from 'vite'
export default defineConfig({
esbuild: {
jsxImportSource: 'hono/jsx/dom',
},
})
```
# JSX
你可以使用 `hono/jsx` 用 JSX 语法编写 HTML。
虽然 `hono/jsx` 在客户端上工作,但你最常在服务器端渲染内容时使用它。以下是与 JSX 相关的一些对服务器和客户端都通用的事项。
## Settings
要使用 JSX,修改 `tsconfig.json`:
`tsconfig.json`:
```json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}
```
或者,使用 pragma directives:
```ts
/** @jsx jsx */
/** @jsxImportSource hono/jsx */
```
对于 Deno,你必须修改 `deno.json` 而不是 `tsconfig.json`:
```json
{
"compilerOptions": {
"jsx": "precompile",
"jsxImportSource": "@hono/hono/jsx"
}
}
```
## Usage
:::info
如果你直接从 [Quick Start](/docs/#quick-start) 来,主文件的扩展名为 `.ts` - 你需要将其更改为 `.tsx` - 否则你将根本无法运行应用程序。你还应该修改 `package.json`(如果你使用 Deno 则为 `deno.json`)以反映该更改(例如,在 dev 脚本中不使用 `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
```
## Metadata hoisting
你可以直接在组件内编写文档元数据标签,如 ``、`` 和 ``。这些标签将自动提升到文档的 `` 部分。当 `` 元素远离确定适当元数据的组件渲染时,这特别有用。
```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}
)
}
```
## Inserting Raw HTML
要直接插入 HTML,使用 `dangerouslySetInnerHTML`:
```tsx
app.get('/foo', (c) => {
const inner = { __html: 'JSX · SSR' }
const Div =
})
```
## Memoization
使用 `memo` 通过记忆计算的字符串来优化你的组件:
```tsx
import { memo } from 'hono/jsx'
const Header = memo(() => )
const Footer = memo(() => )
const Layout = (
)
```
## Context
通过使用 `useContext`,你可以在 Component 树的任何级别全局共享数据,而无需通过 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 Component,因此你可以在组件中使用 `async`/`await`。如果你使用 `c.html()` 渲染它,它将自动等待。
```tsx
const AsyncComponent = async () => {
await new Promise((r) => setTimeout(r, 1000)) // sleep 1s
return Done!
}
app.get('/', (c) => {
return c.html(
)
})
```
## Suspense
可以使用 React-like 的 `Suspense` 功能。如果你用 `Suspense` 包装 async component,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` 也可以与 async components 和 `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 值以用于 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` 组件生成的任何 `
) : (
)}
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 Middleware
Cloudflare Pages 使用自己的 [middleware](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 的内置和第三方中间件。例如,要添加 Basic Authentication,你可以使用 [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',
})
)
```
如果你想应用多个中间件,可以这样写:
```ts
import { handleMiddleware } from 'hono/cloudflare-pages'
// ...
export const onRequest = [
handleMiddleware(middleware1),
handleMiddleware(middleware2),
handleMiddleware(middleware3),
]
```
### Accessing `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 edge runtime。
你可以使用 [Wrangler](https://developers.cloudflare.com/workers/wrangler/) 在本地开发应用程序并通过几个命令发布它。Wrangler 包含转译器,所以我们可以用 TypeScript 编写代码。
让我们用 Hono 创建你的第一个 Cloudflare Workers 应用程序。
## 1. Setup
Cloudflare Workers 有一个 starter。使用 "create-hono" 命令开始你的项目。为此示例选择 `cloudflare-workers` template。
::: 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. Hello World
编辑 `src/index.ts` 如下。
```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`。
::: 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 Configuration](https://developers.cloudflare.com/workers/wrangler/configuration/#local-development-settings)
或者,你可以按照此处的说明设置 CLI 选项:[Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/commands/#dev)
## 4. Deploy
如果你有 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
```
:::
就是这样!
## Using Hono with other event handlers
你可以在 _Module Worker mode_ 中将 Hono 与其他事件处理器(如 `scheduled`)集成。
为此,将 `app.fetch` 导出为模块的 `fetch` 处理器,然后根据需求实现其他处理器:
```ts
const app = new Hono()
export default {
fetch: app.fetch,
scheduled: async (batch, env) => {},
}
```
## Serve static files
如果你想提供静态文件,可以使用 Cloudflare Workers 的 [Static Assets feature](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
```
## Types
如果你想要 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
```
:::
## Testing
对于测试,我们推荐使用 `@cloudflare/vitest-pool-workers`。参考 [examples](https://github.com/honojs/examples) 进行设置。
如果有下面的应用程序。
```ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Please test me!'))
```
我们可以测试它是否返回 "_200 OK_" Response。
```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 namespace、R2 bucket 或 Durable Object。你可以在 `c.env` 中访问它们。如果你将 bindings 的"_类型定义_"作为泛型传递给 `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!`)
})
```
## Using Variables in Middleware
这仅适用于 Module Worker mode。如果你想在中间件中使用 Variables 或 Secret Variables,例如 Basic Authentication Middleware 中的 "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 Authentication Middleware、JWT Authentication 等。
## Deploy from GitHub Actions
在通过 CI 部署代码到 Cloudflare 之前,你需要一个 Cloudflare token。你可以从 [User API Tokens](https://dash.cloudflare.com/profile/api-tokens) 管理它。
如果是新创建的 token,选择 **Edit Cloudflare Workers** template。如果你已经有另一个 token,请确保 token 具有相应的权限。(注意:token 权限在 Cloudflare Pages 和 Cloudflare Workers 之间不共享)。
然后进入你的 GitHub 仓库设置 dashboard:`Settings->Secrets and variables->Actions->Repository secrets`,并添加一个名为 `CLOUDFLARE_API_TOKEN` 的新 secret。
然后在你的 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
```
一切都准备好了!现在推送代码并享受它。
## Load env when local development
要配置本地开发的环境变量,在项目根目录创建 `.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`。详细信息请查看 [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 项目的配置中设置环境变量/secrets。
> 有关此部分的更多信息,你可以在 Cloudflare 文档中找到:https://developers.cloudflare.com/workers/configuration/environment-variables/#add-environment-variables-via-the-dashboard
# Deno
[Deno](https://deno.com/) 是一个基于 V8 的 JavaScript runtime。它不是 Node.js。Hono 也可以在 Deno 上运行。
你可以使用 Hono,用 TypeScript 编写代码,使用 `deno` 命令运行应用程序,并部署到 "Deno Deploy"。
## 1. Install Deno
首先,安装 `deno` 命令。请参考 [官方文档](https://docs.deno.com/runtime/getting_started/installation/)。
## 2. Setup
Deno 有一个 starter。使用 [`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. Hello World
编辑 `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`。
```sh
deno task start
```
## Change port number
你可以通过更新 `main.ts` 中 `Deno.serve` 的参数来指定端口号:
```ts
Deno.serve(app.fetch) // [!code --]
Deno.serve({ port: 8787 }, app.fetch) // [!code ++]
```
## Serve static files
要提供静态文件,使用从 `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` header 提供它们。它优先考虑 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/)。
## Testing
在 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 and 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 directory](https://docs.deno.com/runtime/fundamentals/configuration/#custom-path-mappings) 语法。
```json
{
"imports": {
"hono/": "npm:/hono/"
}
}
```
当使用第三方中间件时,你可能需要使用与中间件相同 registry 的 Hono 以获得正确的 TypeScript 类型推断。例如,如果使用 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 作为 template 的一部分自动本地安装。
## 1. Setup
Fastly Compute 有一个 starter。使用 "create-hono" 命令开始你的项目。为此示例选择 `fastly` template。
::: 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. Hello World
编辑 `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` 会导致其 router 在应用程序初始化阶段构建其内部数据。
## 3. Run
在本地运行开发服务器。然后在 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. Deploy
要构建并部署你的应用程序到你的 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
```
:::
## Bindings
在 Fastly Compute 中,你可以绑定 Fastly 平台资源,如 KV Stores、Config Stores、Secret Stores、Backends、Access Control Lists、Named Log Streams 和 Environment Variables。你可以通过 `c.env` 访问它们,并将拥有它们各自的 SDK 类型。
要使用这些 bindings,从 `@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`。然后使用 `fire.Bindings` 定义你的 `Env` 类型来构建 `Hono`。
```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 来使用任何你喜欢的 runtime(例如,Deno 或 Bun)。如果未提供 Dockerfile,Google Cloud Run 将使用默认的 Node.js buildpack。
本指南假设你已经拥有 Google Cloud 账户和计费账户。
## 1. Install the 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. Project setup
创建项目。在提示符处接受自动生成的项目 ID。
```sh
gcloud projects create --set-as-default --name="my app"
```
为你的项目 ID 和项目号创建环境变量以便于重复使用。可能需要约 30 秒后项目才能通过 `gcloud projects list` 命令成功返回。
```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]
```
启用所需的 APIs。
```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. Deploy
开始部署并按照交互式提示操作(例如,选择一个 region)。
```sh
gcloud run deploy my-app --source . --allow-unauthenticated
```
## Changing runtimes
如果你想使用 Deno 或 Bun runtimes(或定制的 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. Setup
在 Lambda@Edge 上创建应用程序时,使用 [CDK](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-cdk.html) 来设置 CloudFront、IAM Role、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. Deploy
编辑 `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,
})
// 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,
},
],
},
})
}
}
```
最后,运行命令部署:
```sh
cdk deploy
```
## Callback
如果你想添加 Basic Auth 并在验证后继续处理请求,可以使用 `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 提供静态站点托管和无服务器后端服务。[Edge Functions](https://docs.netlify.com/edge-functions/overview/) 使我们能够使网页动态化。
Edge Functions 支持用 Deno 和 TypeScript 编写,通过 [Netlify CLI](https://docs.netlify.com/cli/get-started/) 轻松完成部署。使用 Hono,你可以为 Netlify Edge Functions 创建应用程序。
## 1. Setup
Netlify 有一个 starter。使用 "create-hono" 命令开始你的项目。为此示例选择 `netlify` template。
::: 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. Run
使用 Netlify CLI 运行开发服务器。然后在 Web 浏览器中访问 `http://localhost:8888`。
```sh
netlify dev
```
## 4. Deploy
你可以使用 `netlify deploy` 命令部署。
```sh
netlify deploy --prod
```
## `Context`
你可以通过 `c.env` 访问 Netlify 的 `Context`:
```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 runtime 时,你可以在 Next.js 上运行 Hono。在 Vercel 上,使用 Vercel Functions 可以轻松部署带有 Hono 的 Next.js。
## 1. Setup
Next.js 有一个 starter。使用 "create-hono" 命令开始你的项目。为此示例选择 `nextjs` template。
::: 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. Hello World
如果你使用 App Router,编辑 `app/api/[[...route]]/route.ts`。更多信息参考 [Supported HTTP Methods](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. Run
在本地运行开发服务器。然后在 Web 浏览器中访问 `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 UIs,你可以用 Hono 创建全栈应用程序。
## 4. Deploy
如果你有 Vercel 账户,你可以通过链接 Git 仓库进行部署。
## Pages Router
如果你使用 Pages Router,你需要首先安装 Node.js adapter。
::: 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 一起工作,重要的是通过在你的项目 dashboard 或 `.env` 文件中设置环境变量来禁用 Vercel Node.js helpers。
```text
NODEJS_HELPERS=0
```
# Node.js
[Node.js](https://nodejs.org/) 是一个开源、跨平台的 JavaScript runtime 环境。
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. Setup
Node.js 有一个 starter。使用 "create-hono" 命令开始你的项目。为此示例选择 `nodejs` template。
::: 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. Hello World
编辑 `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. Run
在本地运行开发服务器。然后在 Web 浏览器中访问 `http://localhost:3000`。
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm dev
```
:::
## Change port number
你可以使用 `port` 选项指定端口号。
```ts
serve({
fetch: app.fetch,
port: 8787,
})
```
## Access the raw Node.js APIs
你可以从 `c.env.incoming` 和 `c.env.outgoing` 访问 Node.js APIs。
```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)
```
## Serve static files
你可以使用 `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。
### unencrypted http2
```ts
import { createServer } from 'node:http2'
const server = serve({
fetch: app.fetch,
createServer,
})
```
### 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's Vite plugins](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 adapter,你可以在浏览器内作为 [FetchEvent](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent) 处理器运行用 Hono 制作的应用程序。
本页面展示使用 [Vite](https://vitejs.dev/) 创建项目的示例。
## 1. Setup
首先,创建并进入你的项目目录:
```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
Hello World by Service Worker
```
`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 adapter 的 `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))
```
### Using `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. Run
启动开发服务器。
::: 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 Edge Functions
[Supabase](https://supabase.com/) 是 Firebase 的开源替代方案,提供类似 Firebase 功能的一系列工具,包括数据库、身份验证、存储,现在还有无服务器函数。
Supabase Edge Functions 是服务器端 TypeScript 函数,全球分布,在离用户更近的地方运行以提高性能。这些函数使用 [Deno](https://deno.com/) 开发,带来了多种好处,包括改进的安全性和现代 JavaScript/TypeScript runtime。
以下是如何开始使用 Supabase Edge Functions:
## 1. Setup
### Prerequisites
在开始之前,请确保已安装 Supabase CLI。如果尚未安装,请按照 [官方文档](https://supabase.com/docs/guides/cli/getting-started) 中的说明操作。
### Creating a New Project
1. 打开终端或命令提示符。
2. 通过运行以下命令在本地机器上的目录中创建新的 Supabase 项目:
```bash
supabase init
```
此命令在当前目录中初始化新的 Supabase 项目。
### Adding an Edge Function
3. 在 Supabase 项目内,创建名为 `hello-world` 的新 Edge Function:
```bash
supabase functions new hello-world
```
此命令在项目中创建指定名称的新 Edge Function。
## 2. Hello World
通过修改文件 `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. Run
要本地运行函数,使用以下命令:
1. 使用以下命令提供函数:
```bash
supabase start # 启动 supabase stack
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. Deploy
你可以使用单个命令部署 Supabase 中的所有 Edge Functions:
```bash
supabase functions deploy
```
或者,你可以通过在 deploy 命令中指定函数名称来部署单个 Edge Functions:
```bash
supabase functions deploy hello-world
```
有关更多部署方法,请访问 Supabase 文档的 [Deploying to Production](https://supabase.com/docs/guides/functions/deploy)。
# Vercel
Vercel 是 AI 云,提供开发者工具和云基础设施来构建、扩展和保护更快、更个性化的 web。
Hono 可以零配置部署到 Vercel。
## 1. Setup
Vercel 有一个 starter。使用 "create-hono" 命令开始你的项目。为此示例选择 `vercel` template。
::: 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. Hello World
在你的项目的 `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` template 开始,这已经为你设置好了。
## 3. Run
要在本地运行开发服务器:
```sh
vercel dev
```
访问 `localhost:3000` 将返回文本响应。
## 4. Deploy
使用 `vc deploy` 部署到 Vercel。
```sh
vercel deploy
```
## Further reading
[在 Vercel 文档中了解更多关于 Hono 的信息](https://vercel.com/docs/frameworks/backend/hono)。
# WebAssembly (w/ WASI)
[WebAssembly][wasm-core] 是一个安全、沙盒化、可移植的 runtime,在 web 浏览器内外运行。
实际上:
- 语言(如 JavaScript)_编译为_ WebAssembly(`.wasm` 文件)
- WebAssembly runtimes(如 [`wasmtime`][wasmtime] 或 [`jco`][jco])启用_运行_ WebAssembly 二进制文件
虽然核心 WebAssembly _无法_ 访问本地文件系统或套接字等,但 [WebAssembly System Interface][wasi] 介入以在 WebAssembly 工作负载下定义平台。
这意味着使用 WASI,WebAssembly 可以操作文件、套接字等更多内容。
::: info
想自己查看 WASI 接口?查看 [`wasi:http`][wasi-http]
:::
JS 中对 WebAssembly w/ WASI 的支持由 [StarlingMonkey][sm] 提供支持,得益于 StarlingMonkey 和 Hono 中对 Web standards 的关注,**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. Setup
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] 及其成员管理。
新功能、问题、pull requests 和其他类型的贡献总是受欢迎的。
:::
虽然 Hono on 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
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"`
:::
进入 `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. Set up WIT interface & dependencies
[WebAssembly Interface Types (WIT)][wit] 是一个接口定义语言("IDL"),规定了 WebAssembly 组件使用的功能("imports")和提供的功能("exports")。
在标准化的 WIT 接口中,[`wasi:http`][wasi-http] 用于处理 HTTP 请求(无论是接收还是发送),由于我们打算制作一个 web 服务器,我们的组件必须在其 [WIT world][wit-world] 中声明使用 `wasi:http/incoming-handler`:
首先,让我们在名为 `wit/component.wit` 的文件中设置组件的 WIT world:
```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] 项目,它包含的 helpers 使体验与标准 Hono 体验非常相似。
让我们在名为 `src/component.ts` 的文件中用基本的 Hono 应用程序作为 WebAssembly 组件来实现我们的 `component` world:
```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. Build
由于我们使用 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
```
:::
## 5. Run
要运行你的 Hono WebAssembly HTTP 服务器,你可以使用任何启用 WASI 的 WebAssembly runtime:
- [`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 coremodule 来工作,以便它可以在 Node.js 和浏览器等 runtimes 中运行。
这个过程通常通过 `jco transpile` 运行,这是我们可以在 JS 引擎(如 Node.js 和浏览器,可能使用 V8 或其他 Javascript 引擎)作为 WebAssembly Component runtimes 的方式。
`jco transpile` 如何工作超出了本指南的范围,你可以在 [the Jco book][jco-book] 中阅读更多相关信息
:::
## More information
要了解更多关于 WASI、WebAssembly 组件等更多信息,请查看以下资源:
- [BytecodeAlliance Component Model book][cm-book]
- [`jco` codebase][jco]
- [`jco` example components][jco-example-components](特别是 [Hono example][jco-example-component-hono])
- [Jco book][jco-book]
- [`componentize-js` codebase][componentize-js]
- [StarlingMonkey codebase][sm]
要与 WebAssembly 社区联系,提出问题、评论、贡献或提交问题:
- [Bytecode Alliance Zulip](https://bytecodealliance.zulipchat.com)(考虑在 [#jco channel](https://bytecodealliance.zulipchat.com/#narrow/channel/409526-jco) 中发帖)
- [Jco repository](https://github.com/bytecodealliance/jco)
- [componentize-js repository](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
`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 Headers。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/', (c) => {
// 设置 headers
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 namespaces、D1 database、R2 bucket 等被称为 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) {
// do something...
}
})
```
## 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 是 string 类型
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 是 string 类型
// ...
return c.json({ result: val })
})
```
# HTTPException
当发生致命错误时,Hono(以及许多生态系统中间件)可能会抛出 `HTTPException`。这是一个自定义的 Hono `Error`,简化了[返回错误响应](#handling-httpexceptions) 的过程。
## Throwing HTTPExceptions
你可以通过指定状态码以及消息或自定义响应来抛出你自己的 HTTPExceptions。
### Custom Message
对于基本的 `text` 响应,只需设置错误 `message`。
```ts twoslash
import { HTTPException } from 'hono/http-exception'
throw new HTTPException(401, { message: 'Unauthorized' })
```
### Custom Response
对于其他响应类型,或要设置响应 headers,使用 `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
在任何一种情况下,你都可以使用 [`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('/')
})
```
## Handling HTTPExceptions
你可以使用 [`app.onError`](/docs/api/hono#error-handling) 处理未捕获的 HTTPExceptions。它们包含一个 `getResponse` 方法,返回一个根据错误 `status` 创建的新 `Response`,以及错误 `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` 中设置的 headers,你必须将它们应用到新的 `Response`。
:::
# App - Hono
`Hono` 是主要对象。它会被首先导入并一直使用到最后。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
//...
export default app // 用于 Cloudflare Workers 或 Bun
```
## Methods
`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)
其中第一部分用于路由,请参阅 [routing section](/docs/api/routing)。
## Not Found
`app.notFound` 允许你自定义 Not Found 响应。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.notFound((c) => {
return c.text('Custom 404 Message', 404)
})
```
:::warning
`notFound` 方法仅从顶级 app 调用。更多信息见这个 [issue](https://github.com/honojs/hono/issues/3465#issuecomment-2381210165)。
:::
## Error Handling
`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
如果父 app 和其 routes 都有 `onError` 处理器,route 级别的处理器优先级更高。
:::
## fire()
::: warning
**`app.fire()` 已弃用**。改用 `hono/service-worker` 中的 `fire()`。详情见 [Service Worker documentation](/docs/getting-started/service-worker)。
:::
`app.fire()` 自动添加全局 `fetch` 事件监听器。
这对于遵循 [Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) 的环境很有用,例如 [非 ES module 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)
```
## strict mode
strict mode 默认为 `true`,区分以下 routes:
- `/hello`
- `/hello/`
`app.get('/hello')` 不会匹配 `GET /hello/`。
通过将 strict mode 设置为 `false`,两条路径将被同等对待。
```ts twoslash
import { Hono } from 'hono'
// ---cut---
const app = new Hono({ strict: false })
```
## router option
`router` 选项指定使用哪个 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() })
```
## Generics
你可以传递泛型来指定 Cloudflare Workers Bindings 和 `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 是 `string`
// ...
c.set('user', user) // user 应该是 `User`
await next()
})
```
# API
Hono 的 API 很简单。仅由 Web Standards 的扩展对象组成。因此,你可以很快地理解它。
在本节中,我们介绍 Hono 的 API,如下所示:
- Hono 对象
- 关于路由
- Context 对象
- 关于中间件
# Presets
Hono 有多个 routers,每个都针对特定用途设计。你可以在 Hono 的构造函数中指定要使用的 router。
**Presets** 为常见用例提供,因此你不必每次都指定 router。从所有 presets 导入的 `Hono` 类是相同的,唯一区别在于 router。因此,你可以互换使用它们。
## `hono`
用法:
```ts twoslash
import { Hono } from 'hono'
```
Routers:
```ts
this.router = new SmartRouter({
routers: [new RegExpRouter(), new TrieRouter()],
})
```
## `hono/quick`
用法:
```ts twoslash
import { Hono } from 'hono/quick'
```
Router:
```ts
this.router = new SmartRouter({
routers: [new LinearRouter(), new TrieRouter()],
})
```
## `hono/tiny`
用法:
```ts twoslash
import { Hono } from 'hono/tiny'
```
Router:
```ts
this.router = new PatternRouter()
```
## Which preset should I use?
| Preset | 适用平台 |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `hono` | 这在大多数用例中强烈推荐。虽然注册阶段可能比 `hono/quick` 慢,但一旦启动就会表现出高性能。它非常适合用 **Deno**、**Bun** 或 **Node.js** 构建的长生命周期服务器。它也适合 **Fastly Compute**,因为在该平台上路由注册发生在应用程序构建阶段。对于 **Cloudflare Workers**、**Deno Deploy** 等使用 v8 isolates 的环境,这个 preset 也合适。因为 isolates 在启动后会持续一段时间。 |
| `hono/quick` | 这个 preset 专为每次请求都初始化应用程序的环境设计。 |
| `hono/tiny` | 这是最小的 router 包,适合资源有限的环境。 |
# 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---
// 捕获的 params
app.get('/entry/:id', async (c) => {
const id = c.req.param('id')
// ^?
// ...
})
// 一次性获取所有 params
app.get('/entry/:id/comment/:commentId', async (c) => {
const { id, commentId } = c.req.param()
// ^?
})
```
## query()
获取 querystring 参数。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
// Query params
app.get('/search', async (c) => {
const query = c.req.query('q')
// ^?
})
// 一次性获取所有 params
app.get('/search', async (c) => {
const { q, limit, offset } = c.req.query()
// ^?
})
```
## queries()
获取多个 querystring 参数值,例如 `/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()
获取请求 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()` 不带参数调用时,返回的记录中的所有键都是**小写的**。
如果你想获取大写名称的 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` 类型的 Request body
```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` 类型的请求 body
```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` 类型的请求 body
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.text()
// ...
})
```
## arrayBuffer()
将请求 body 解析为 `ArrayBuffer`
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.arrayBuffer()
// ...
})
```
## blob()
将请求 body 解析为 `Blob`。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.blob()
// ...
})
```
## formData()
将请求 body 解析为 `FormData`。
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.formData()
// ...
})
```
## valid()
获取验证后的数据。
```ts
app.post('/posts', async (c) => {
const { title, body } = c.req.valid('form')
// ...
})
```
可用目标如下:
- `form`
- `json`
- `query`
- `header`
- `cookie`
- `param`
使用示例见 [Validation section](/docs/guides/validation)。
## routePath
::: warning
**在 v4.8.0 中已弃用**:此属性已弃用。改用 [Route Helper](/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 中已弃用**:此属性已弃用。改用 [Route Helper](/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
请求 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 字符串。
```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 对象。即使在请求 body 已被验证器或 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()
// ...
}
)
```
# Routing
Hono 的路由灵活且直观。让我们来看看。
## 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')
// ^?
// ...
})
```
或一次性获取所有参数:
```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---
// 将匹配 `/api/animal` 和 `/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 实例对路由进行分组,并使用 route 方法将它们添加到主 app。
```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
你也可以在保持 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) // 处理 /book
app.route('/', user) // 处理 /user
```
## Base path
你可以指定 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
如果包含 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'))
```
## Routing with `host` Header value
如果你在 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 改变路由。
## Routing priority
Handlers 或 middleware 将按注册顺序执行。
```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`
```
当 handler 执行时,进程将停止。
```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 将不会 dispatch
```
如果你有想要执行的 middleware,请在 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_"handler,请在其他 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`)路由中。
```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` 没有 routes
two.route('/three', three)
export default app
```
```
GET /two/three/hi ---> 404 Not Found
```