This is the full developer documentation for Hono. # Start of Hono documentation # Hono Hono - _**\[炎\] 🔥**_ - は小さく、シンプルで爆速なエッジ向け Web フレームワークです。 あらゆる JavaScript ランタイムで動作します: Cloudflare Workers 、 Fastly Compute 、 Deno 、 Bun 、 Vercel 、 Netlify 、 AWS Lambda 、 Lambda@Edge そして Node.js。 Hono は速いけど、速いだけではありません。 ```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 にはビルドインミドルウェア、カスタムミドルウェア、サードパーティーミドルウェア及びヘルパーが含まれています。 バッテリー同梱! - **楽しい DX** 😃 - 非常にクリーンな API 。 最上級の TypeScript サポート。 Now, we've got "Types". ## 使用例 Hono は Express に似たフロントエンドを持たない Web アプリケーションフレームワークです。 しかし CDN エッジでミドルウェアを組み合わせることでより大規模なアプリケーションを構築できます。 以下にいくつかの使用例を紹介します。 - Web API の構築 - バックエンドサーバーのプロキシ - CDN のフロント - エッジアプリケーション - ライブラリのベースサーバー - フルスタックアプリケーション ## Hono を使用している主なプロジェクト | プロジェクト | プラットフォーム | 内容 | | ---------------------------------------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | | [cdnjs](https://cdnjs.com) | Cloudflare Workers | 無料かつオープンソースの CDN サービス。 _API サーバーに Hono を使用_。 | | [Cloudflare D1](https://www.cloudflare.com/developer-platform/d1/) | Cloudflare Workers | サーバーレス SQL データベース。 _内部 API サーバーに Hono を使用_。 | | [Cloudflare Workers KV](https://www.cloudflare.com/developer-platform/workers-kv/) | Cloudflare Workers | サーバーレス KVS (キーバリューストア)。 _内部 API サーバーに Hono を使用_。 | | [BaseAI](https://baseai.dev) | Local AI Server | メモリ機能を持つサーバーレス AI エージェントパイプ。 Web 向けオープンソースエージェント AI フレームワーク。 _API サーバーに Hono を使用_。 | | [Unkey](https://unkey.dev) | Cloudflare Workers | オープンソースの API 認証・認可プラットフォーム。 _API サーバーに Hono を使用_。 | | [OpenStatus](https://openstatus.dev) | Bun | オープンソースの Web サイト・ API 監視プラットフォーム。 _API サーバーに Hono を使用_。 | | [Deno Benchmarks](https://deno.com/benchmarks) | Deno | V8 上で動作する安全な TypeScript ランタイム。 _ベンチマーク用途に Hono を使用_。 | | [Clerk](https://clerk.com) | Cloudflare Workers | オープンソースのユーザー管理プラットフォーム。 _API サーバーに Hono を使用_。 | | [PostHog](https://posthog.com) | Node.js | セルフドライビングプロダクトを開発するためのオープンソースプラットフォーム。 _MCP サーバーに Hono を使用_。 | そして、 - [Drivly](https://driv.ly/) - Cloudflare Workers - [repeat.dev](https://repeat.dev/) - Cloudflare Workers 他のプロジェクトを見たいですか? [Who is using Hono in production?](https://github.com/orgs/honojs/discussions/1510) をご覧ください。 ## Hono 1分クッキング Hono を使用して Cloudflare Workers 向けのアプリケーションを作成するデモ。 ![高速なイテレーションで Hono アプリが素早く作成される様子を示す GIF](/images/sc.gif) ## 爆速 **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` プリセットを使用した時、 Minify すれば **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 - 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 には沢山のミドルウェアやヘルパーがあります**。 それらは "Write Less, do more" を実現します。 Hono は以下のすぐに使えるミドルウェアとヘルパーを提供します: - [Basic 認証](/docs/middleware/builtin/basic-auth) - [Bearer 認証](/docs/middleware/builtin/bearer-auth) - [Body Limit](/docs/middleware/builtin/body-limit) - [キャッシュ](/docs/middleware/builtin/cache) - [圧縮](/docs/middleware/builtin/compress) - [Context Storage](/docs/middleware/builtin/context-storage) - [Cookie](/docs/helpers/cookie) - [CORS](/docs/middleware/builtin/cors) - [ETag](/docs/middleware/builtin/etag) - [html](/docs/helpers/html) - [JSX](/docs/guides/jsx) - [JWT 認証](/docs/middleware/builtin/jwt) - [Logger](/docs/middleware/builtin/logger) - [Language](/docs/middleware/builtin/language) - [Pretty JSON](/docs/middleware/builtin/pretty-json) - [Secure 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) - etc...! 例えば、 ETag と リクエストロギングを追加するためには Hono を使用して以下のコードを書くだけです: ```ts import { Hono } from 'hono' import { etag } from 'hono/etag' import { logger } from 'hono/logger' const app = new Hono() app.use(etag(), logger()) ``` [ミドルウェアの詳細](/docs/concepts/middleware)もご確認ください。 ## 開発体験 Hono は楽しい "**開発体験**" を提供します。 `Context` オブジェクトによって Request/Response へ簡単にアクセスできます。 更に、 Hono は TypeScript で書かれており、 "**型**" を持っています。 例えば、パスパラメータはリテラル型になります。 ![URL パラメータに対して Hono が適切なリテラル型を持つことを示すスクリーンショット。 URL "/entry/:date/:id" により、リクエストパラメータは "date" または "id" になる](/images/ss.png) そして、バリデーターと Hono Client `hc` は RPC モードを有効にします。 RPC モードでは、 Zod などのお気に入りのバリデーターを使用して、サーバーサイド API 仕様をクライアントと簡単に共有してタイプセーフなアプリケーションを構築できます。 [Hono Stacks](/docs/concepts/stacks)もご確認ください。 # サードパーティー製のミドルウェア サードパーティーミドルウェアとは Hono のパッケージにバンドルされていないミドルウェアのことです。 ここにある殆どのミドルウェアは外部ライブラリを使用します。 ### 認証 - [Auth.js(Next Auth)](https://github.com/honojs/middleware/tree/main/packages/auth-js) - [Casbin](https://github.com/honojs/middleware/tree/main/packages/casbin) - [Clerk Auth](https://github.com/honojs/middleware/tree/main/packages/clerk-auth) - [Cloudflare Access](https://github.com/honojs/middleware/tree/main/packages/cloudflare-access) - [OAuth Providers](https://github.com/honojs/middleware/tree/main/packages/oauth-providers) - [OIDC Auth](https://github.com/honojs/middleware/tree/main/packages/oidc-auth) - [Firebase Auth](https://github.com/honojs/middleware/tree/main/packages/firebase-auth) - [Verify RSA JWT (JWKS)](https://github.com/wataruoguchi/verify-rsa-jwt-cloudflare-worker) - [SSOJet Auth](https://github.com/ssojet/ssojet-hono) - [Stytch Auth](https://github.com/honojs/middleware/tree/main/packages/stytch-auth) - [Shopify Auth](https://github.com/besart-k/hono-shopify-auth) ### バリデーター - [Ajv Validator](https://github.com/honojs/middleware/tree/main/packages/ajv-validator) - [ArkType Validator](https://github.com/honojs/middleware/tree/main/packages/arktype-validator) - [Class Validator](https://github.com/honojs/middleware/tree/main/packages/class-validator) - [Conform Validator](https://github.com/honojs/middleware/tree/main/packages/conform-validator) - [Effect Schema Validator](https://github.com/honojs/middleware/tree/main/packages/effect-validator) - [Standard Schema Validator](https://github.com/honojs/middleware/tree/main/packages/standard-validator) - [TypeBox Validator](https://github.com/honojs/middleware/tree/main/packages/typebox-validator) - [Typia Validator](https://github.com/honojs/middleware/tree/main/packages/typia-validator) - [unknownutil Validator](https://github.com/ryoppippi/hono-unknownutil-validator) - [Valibot Validator](https://github.com/honojs/middleware/tree/main/packages/valibot-validator) - [Zod Validator](https://github.com/honojs/middleware/tree/main/packages/zod-validator) ### OpenAPI - [Zod OpenAPI](https://github.com/honojs/middleware/tree/main/packages/zod-openapi) - [Scalar](https://github.com/scalar/scalar/tree/main/integrations/hono) - [Swagger UI](https://github.com/honojs/middleware/tree/main/packages/swagger-ui) - [Swagger Editor](https://github.com/honojs/middleware/tree/main/packages/swagger-editor) - [Hono OpenAPI](https://github.com/rhinobase/hono-openapi) - [hono-zod-openapi](https://github.com/paolostyle/hono-zod-openapi) ### Development - [ESLint Config](https://github.com/honojs/middleware/tree/main/packages/eslint-config) - [hono-testkit](https://github.com/md-sagban/hono-testkit) - [SSG Plugin Essential](https://github.com/honojs/middleware/tree/main/packages/ssg-plugins-essential) ### Monitoring / Tracing - [Apitally (API monitoring & analytics)](https://docs.apitally.io/frameworks/hono) - [Highlight.io](https://www.highlight.io/docs/getting-started/backend-sdk/js/hono) - [LogTape (Logging)](https://logtape.org/manual/integrations#hono) - [OpenTelemetry](https://github.com/honojs/middleware/tree/main/packages/otel) - [Prometheus Metrics](https://github.com/honojs/middleware/tree/main/packages/prometheus) - [Sentry](https://github.com/honojs/middleware/tree/main/packages/sentry) - [Pino logger](https://github.com/maou-shonen/hono-pino) ### Server / Adapter - [GraphQL Server](https://github.com/honojs/middleware/tree/main/packages/graphql-server) - [oRPC](https://orpc.dev/docs/adapters/hono) - [tRPC Server](https://github.com/honojs/middleware/tree/main/packages/trpc-server) - [mcp-use (MCP Server)](https://github.com/mcp-use/mcp-use) - [hono-mcp-bridge (Expose routes as MCP tools)](https://github.com/serikshaikamalov/hono-mcp-bridge) - [chanx-js (Typed WebSockets with AsyncAPI docs)](https://js.chanx.dev/server/integrations/hono) ### Transpiler - [Bun Transpiler](https://github.com/honojs/middleware/tree/main/packages/bun-transpiler) - [esbuild Transpiler](https://github.com/honojs/middleware/tree/main/packages/esbuild-transpiler) ### UI / Renderer - [Qwik City](https://github.com/honojs/middleware/tree/main/packages/qwik-city) - [React Compatibility](https://github.com/honojs/middleware/tree/main/packages/react-compat) - [React Renderer](https://github.com/honojs/middleware/tree/main/packages/react-renderer) ### Queue / Job Processing - [GlideMQ (Message Queue REST API + SSE)](https://github.com/avifenesh/glidemq-hono) ### Internationalization - [Intlayer i18n](https://intlayer.org/doc/environment/hono) ### Utilities - [Bun Compress](https://github.com/honojs/middleware/tree/main/packages/bun-compress) - [Cap Checkpoint](https://capjs.js.org/guide/middleware/hono.html) - [Event Emitter](https://github.com/honojs/middleware/tree/main/packages/event-emitter) - [Geo](https://github.com/ktkongtong/hono-geo-middleware/tree/main/packages/middleware) - [Guard Core (IP filtering, rate limiting, attack detection)](https://github.com/Guard-Core/guard-core-ts/tree/master/packages/hono) - [Hono Rate Limiter](https://github.com/rhinobase/hono-rate-limiter) - [Hono Problem Details (RFC 9457)](https://github.com/paveg/hono-problem-details) - [Hono Simple DI](https://github.com/maou-shonen/hono-simple-DI) - [InferDI](https://github.com/inferdi/inferdi/tree/main/packages/hono) - [Idempotency (Stripe-style idempotency keys)](https://github.com/paveg/hono-idempotency) - [idempot-js](https://js.idempot.dev) - spec-compliant middleware, supporting multiple storage backends (redis, postgres, mysql, sqlite) - [jsonv-ts (Validator, OpenAPI, MCP)](https://github.com/dswbx/jsonv-ts) - [MCP](https://github.com/honojs/middleware/tree/main/packages/mcp) - [RONIN (Database)](https://github.com/ronin-co/hono-client) - [Session](https://github.com/honojs/middleware/tree/main/packages/session) - [StitchAPI (Typed, resilient API calls + SSE)](https://github.com/rejifald/StitchAPI/tree/main/packages/hono) - [tsyringe](https://github.com/honojs/middleware/tree/main/packages/tsyringe) - [User Agent based Blocker](https://github.com/honojs/middleware/tree/main/packages/ua-blocker) # Basic 認証ミドルウェア このミドルウェアは、特定のパスに Basic 認証を設定することができます。 Cloudflare Workers や他のプラットフォームで Basic 認証を実装することは思ったよりも複雑ですが、このミドルウェアを使えば簡単にできます。 Basic 認証方式が内部でどのように動くか知りたい場合は、 [MDN のドキュメント](https://developer.mozilla.org/ja/docs/Web/HTTP/Guides/Authentication#basic_%E8%AA%8D%E8%A8%BC%E6%96%B9%E5%BC%8F) をご覧ください。 ## Import ```ts import { Hono } from 'hono' import { basicAuth } from 'hono/basic-auth' ``` ## 使い方 ```ts const app = new Hono() app.use( '/auth/*', basicAuth({ username: 'hono', password: 'acoolproject', }) ) app.get('/auth/page', (c) => { return c.text('You are authorized') }) ``` 特定のルート + メソッドを制限します: ```ts const app = new Hono() app.get('/auth/page', (c) => { return c.text('Viewing page') }) app.delete( '/auth/page', basicAuth({ username: 'hono', password: 'acoolproject' }), (c) => { return c.text('Page deleted') } ) ``` ユーザーを自分で検証したい場合は、 `verifyUser` オプションを指定してください。 `true` を返すことで、資格情報が受け入れられたことを示します。 ```ts const app = new Hono() app.use( basicAuth({ verifyUser: (username, password, c) => { return ( username === 'dynamic-user' && password === 'hono-password' ) }, }) ) ``` ## オプション ### username: `string` 認証されている人のユーザー名。 ### password: `string` そのユーザーのパスワード。 ### realm: `string` 返される WWW-Authenticate チャレンジヘッダーの一部としてのレルムのドメイン名です。 デフォルトは `"Secure Area"` です。 詳しくは: https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Headers/WWW-Authenticate#digest ### hashFunction: `Function` パスワードを安全に比較するためのハッシュ関数。 ### verifyUser: `(username: string, password: string, c: Context) => boolean | Promise` ユーザーを検証する関数。 ### invalidUserMessage: `string | object | MessageFunction` `MessageFunction` は `(c: Context) => string | object | Promise` 。 ユーザーが無効だったときのメッセージ。 ### onAuthSuccess: `(c: Context, username: string) => void | Promise` 認証が成功した後に呼び出されるコールバック関数です。 これにより、 Authorization ヘッダーを再パースすることなく、コンテキスト変数を設定したり副作用を実行したりできます。 ```ts app.use( '/auth/*', basicAuth({ username: 'hono', password: 'acoolproject', onAuthSuccess: (c, username) => { c.set('username', username) }, }) ) app.get('/auth/page', (c) => { const username = c.get('username') return c.text(`Hello, ${username}!`) }) ``` ## More Options ### ...users: `{ username: string, password: string }[]` ## レシピ ### 複数ユーザーの設定 このミドルウェアでは、 `username` と `password` のペアを含む任意の数のオブジェクトをパラメータとして渡すこともできます。 ```ts app.use( '/auth/*', basicAuth( { username: 'hono', password: 'acoolproject', // Define other params in the first object realm: 'www.example.com', }, { username: 'hono-admin', password: 'super-secure', // Cannot redefine other params here }, { username: 'hono-user-1', password: 'a-secret', // Or here } ) ) ``` Or less hardcoded: ```ts import { users } from '../config/users' app.use( '/auth/*', basicAuth( { realm: 'www.example.com', ...users[0], }, ...users.slice(1) ) ) ``` # Bearer Auth ミドルウェア Bearer Auth Middleware は、リクエストヘッダー内の API トークンを検証することで認証を提供します。 エンドポイントにアクセスする HTTP クライアントは、ヘッダー値として `Bearer {token}` を付けた `Authorization` ヘッダーを追加します。 ターミナルから `curl` を使用すると、次のようになります: ```sh curl -H 'Authorization: Bearer honoiscool' http://localhost:8787/auth/page ``` ## Import ```ts import { Hono } from 'hono' import { bearerAuth } from 'hono/bearer-auth' ``` ## 使い方 > [!NOTE] > `token` は正規表現 `/[A-Za-z0-9._~+/-]+=*/` にマッチする必要があります。 そうでない場合は 400 エラーが返されます。 特に、この正規表現は URL セーフ Base64 と標準 Base64 でエンコードされた JWT の両方に対応しています。 このミドルウェアは、ベアラートークンが JWT であることを要求せず、上記の正規表現にマッチすることのみを要求します。 ```ts const app = new Hono() const token = 'honoiscool' app.use('/api/*', bearerAuth({ token })) app.get('/api/page', (c) => { return c.json({ message: 'You are authorized' }) }) ``` 特定のルート + メソッドに制限する場合: ```ts const app = new Hono() const token = 'honoiscool' app.get('/api/page', (c) => { return c.json({ message: 'Read posts' }) }) app.post('/api/page', bearerAuth({ token }), (c) => { return c.json({ message: 'Created post!' }, 201) }) ``` 複数のトークンを実装する場合 (例えば、任意の有効なトークンは読み取り可能だが、作成/更新/削除は特権トークンに制限される): ```ts const app = new Hono() const readToken = 'read' const privilegedToken = 'read+write' const privilegedMethods = ['POST', 'PUT', 'PATCH', 'DELETE'] app.on('GET', '/api/page/*', async (c, next) => { // List of valid tokens const bearer = bearerAuth({ token: [readToken, privilegedToken] }) return bearer(c, next) }) app.on(privilegedMethods, '/api/page/*', async (c, next) => { // Single valid privileged token const bearer = bearerAuth({ token: privilegedToken }) return bearer(c, next) }) // Define handlers for GET, POST, etc. ``` トークンの値を自分で検証したい場合は、 `verifyToken` オプションを指定してください。 `true` を返すと受け入れられたことを意味します。 ```ts const app = new Hono() app.use( '/auth-verify-token/*', bearerAuth({ verifyToken: async (token, c) => { return token === 'dynamic-token' }, }) ) ``` ## オプション ### token: `string` | `string[]` 受信したベアラートークンを検証するための文字列です。 ### realm: `string` 返される WWW-Authenticate チャレンジヘッダーの一部としてのレルムのドメイン名です。 デフォルトは `""` です。 詳しくは次を参照してください: https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/WWW-Authenticate#directives ### prefix: `string` Authorization ヘッダー値のプレフィックス (`schema` とも呼ばれます) です。 デフォルトは `"Bearer"` です。 ### headerName: `string` ヘッダー名です。 デフォルト値は `Authorization` です。 ### hashFunction: `Function` 認証トークンを安全に比較するためのハッシュ処理を行う関数です。 ### verifyToken: `(token: string, c: Context) => boolean | Promise` トークンを検証する関数です。 ### noAuthenticationHeader: `object` リクエストに認証ヘッダーがない場合のエラーレスポンスをカスタマイズします。 - `wwwAuthenticateHeader`: `string | object | MessageFunction` - WWW-Authenticate ヘッダー値をカスタマイズします。 - `message`: `string | object | MessageFunction` - レスポンスボディのカスタムメッセージです。 `MessageFunction` は `(c: Context) => string | object | Promise` です。 ### invalidAuthenticationHeader: `object` 認証ヘッダーの形式が無効な場合のエラーレスポンスをカスタマイズします。 - `wwwAuthenticateHeader`: `string | object | MessageFunction` - WWW-Authenticate ヘッダー値をカスタマイズします。 - `message`: `string | object | MessageFunction` - レスポンスボディのカスタムメッセージです。 ### invalidToken: `object` トークンが無効な場合のエラーレスポンスをカスタマイズします。 - `wwwAuthenticateHeader`: `string | object | MessageFunction` - WWW-Authenticate ヘッダー値をカスタマイズします。 - `message`: `string | object | MessageFunction` - レスポンスボディのカスタムメッセージです。 # Body Limit ミドルウェア Body Limit ミドルウェアは、リクエストボディのファイルサイズを制限できます。 このミドルウェアは、まずリクエスト内の `Content-Length` ヘッダーの値を使用します (存在する場合)。 設定されていない場合は、ボディをストリームとして読み込み、指定されたファイルサイズより大きい場合にエラーハンドラーを実行します。 ## Import ```ts import { Hono } from 'hono' import { bodyLimit } from 'hono/body-limit' ``` ## 使い方 ```ts const app = new Hono() app.post( '/upload', bodyLimit({ maxSize: 50 * 1024, // 50kb onError: (c) => { return c.text('overflow :(', 413) }, }), async (c) => { const body = await c.req.parseBody() if (body['file'] instanceof File) { console.log(`Got file sized: ${body['file'].size}`) } return c.text('pass :)') } ) ``` ## オプション ### maxSize: `number` 制限したいファイルの最大ファイルサイズです。 デフォルトは `100 * 1024` - `100kb` です。 ### onError: `OnError` 指定されたファイルサイズを超えた場合に呼び出されるエラーハンドラーです。 ## Bun での大きなリクエストの扱い Body Limit ミドルウェアを、デフォルトよりも大きいリクエストボディを許可するために明示的に使用する場合、それに応じて `Bun.serve` の設定を変更する必要があるかもしれません。 [執筆時点](https://github.com/oven-sh/bun/blob/f2cfa15e4ef9d730fc6842ad8b79fb7ab4c71cb9/packages/bun-types/bun.d.ts#L2191) では、 `Bun.serve` のデフォルトのリクエストボディ上限は 128MiB です。 Hono の Body Limit ミドルウェアにそれより大きい値を設定しても、リクエストは失敗し続け、さらにミドルウェアで指定された `onError` ハンドラーも呼び出されません。 これは、 `Bun.serve()` がステータスコードを `413` に設定し、リクエストを Hono に渡す前に接続を切断するためです。 Hono と Bun で 128MiB より大きいリクエストを受け付けたい場合は、 Bun 側にも上限を設定する必要があります: ```ts export default { port: process.env['PORT'] || 3000, fetch: app.fetch, maxRequestBodySize: 1024 * 1024 * 200, // your value here } ``` または、セットアップによっては次のようになります: ```ts Bun.serve({ fetch(req, server) { return app.fetch(req, { ip: server.requestIP(req) }) }, maxRequestBodySize: 1024 * 1024 * 200, // your value here }) ``` # Cache ミドルウェア Cache ミドルウェアは、 Web 標準の [Cache API](https://developer.mozilla.org/ja/docs/Web/API/Cache) を使用します。 Cache ミドルウェアは現在、カスタムドメインを使用する Cloudflare Workers プロジェクトと、 [Deno 1.26+](https://github.com/denoland/deno/releases/tag/v1.26.0) を使用する Deno プロジェクトをサポートしています。 Deno Deploy でも利用できます。 Cloudflare Workers は `Cache-Control` ヘッダーを尊重し、キャッシュされたレスポンスを返します。 詳細については、 [Cloudflare Docs の Cache](https://developers.cloudflare.com/workers/runtime-apis/cache/) を参照してください。 Deno はヘッダーを尊重しないため、キャッシュを更新する必要がある場合は、独自のメカニズムを実装する必要があります。 各プラットフォームでの手順については、後述の [使い方](#usage) を参照してください。 ## Import ```ts import { Hono } from 'hono' import { cache } from 'hono/cache' ``` ## 使い方 ::: code-group ```ts [Cloudflare Workers] app.get( '*', cache({ cacheName: 'my-app', cacheControl: 'max-age=3600', }) ) ``` ```ts [Deno] // Must use `wait: true` for the Deno runtime app.get( '*', cache({ cacheName: 'my-app', cacheControl: 'max-age=3600', wait: true, }) ) ``` ::: ## 認証済みレスポンスのキャッシュ キャッシュはすべてのクライアントで共有され、サインインしたユーザー向けのレスポンスを含む、キャッシュ可能なすべてのレスポンスを保存します。 ミドルウェアはリクエストの `Cookie` ヘッダーを参照しません。 ユーザーごとのレスポンスを共有キャッシュの外に保つには: - 認証ミドルウェアの後に `cache()` を登録し、未認証のリクエストがキャッシュに到達しないようにします。 - ユーザーごとのレスポンスに `Cache-Control: private` (または `no-store`) を設定します。 ミドルウェアはそれらを保存しません。 - または `vary` オプションを使用します。 例えば `vary: 'Cookie'` とすると、Cookie の値ごとに個別のレスポンスをキャッシュします。 ```ts app.use('/api/*', auth) app.get( '/api/*', cache({ cacheName: 'my-app', cacheControl: 'max-age=60', vary: 'Cookie', }) ) ``` ## QUERY リクエストのキャッシュ Cache ミドルウェアは、 [QUERY](https://www.rfc-editor.org/rfc/rfc10008.html) リクエストへのレスポンスもキャッシュします。 RFC 10008 の要件に従い、 QUERY リクエストのキャッシュキーにはリクエストコンテンツとその表現メタデータのダイジェストが組み込まれるため、ボディが異なるリクエストは個別にキャッシュされます。 `maxQueryBodySize` (デフォルトは 64 KiB) より大きいボディを持つ QUERY リクエストは、キャッシュをバイパスします。 ::: info これをサポートするために、キャッシュされたエントリは、リクエスト URL 自体の代わりに `/.hono/cache?__hono_cache_key=...` という形式の内部キーで保存されます。 Cache API を通じて直接キャッシュエントリをパージする場合 (例えば、元のリクエスト URL で `caches.delete()` を呼び出す場合) 、そのロジックを更新する必要があります。 これは GET を含むすべてのメソッドに適用されます。 ::: ## オプション ### cacheName: `string` | `(c: Context) => string` | `Promise` キャッシュの名前です。 異なる識別子を持つ複数のキャッシュを保存するために使用できます。 ### wait: `boolean` Hono がリクエストの処理を続行する前に、 `cache.put` 関数の Promise の解決を待つべきかどうかを示すブール値です。 _Deno 環境では true である必要があります_ 。 デフォルトは `false` です。 ### cacheControl: `string` `Cache-Control` ヘッダーのためのディレクティブ文字列です。 詳しくは [MDN ドキュメント](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Cache-Control) を参照してください。 このオプションが指定されていない場合、リクエストに `Cache-Control` ヘッダーは追加されません。 ### vary: `string` | `string[]` レスポンスに `Vary` ヘッダーを設定します。 元のレスポンスヘッダーに既に `Vary` ヘッダーが含まれている場合、重複を削除した上で値がマージされます。 これを `*` に設定するとエラーになります。 Vary ヘッダーとキャッシング戦略への影響について詳しくは、 [MDN ドキュメント](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Vary) を参照してください。 ### keyGenerator: `(c: Context) => string | Promise` `cacheName` ストア内のすべてのリクエストに対してキーを生成します。 これを使用して、リクエストパラメータやコンテキストパラメータに基づいてデータをキャッシュできます。 デフォルトは `c.req.url` です。 QUERY リクエストの場合、キーにはさらにリクエストコンテンツとその表現メタデータのダイジェストが含まれます。 ### maxQueryBodySize: `number` キャッシュ可能な QUERY リクエストのボディサイズの最大値 (バイト) です。 それより大きいボディを持つ QUERY リクエストはキャッシュをバイパスします。 デフォルトは `65536` (64 KiB) です。 ### cacheableStatusCodes: `number[]` キャッシュすべきステータスコードの配列です。 デフォルトは `[200]` です。 このオプションを使用して、特定のステータスコードを持つレスポンスをキャッシュします。 ```ts app.get( '*', cache({ cacheName: 'my-app', cacheControl: 'max-age=3600', cacheableStatusCodes: [200, 404, 412], }) ) ``` ### onCacheNotAvailable: `((reason: string) => void | Promise)` | `false` Cache API がグローバルスコープで利用できない場合や、 QUERY キャッシングが Web Crypto を使用できない場合の挙動を制御するコールバック関数または `false` です。 コールバックは理由とともに呼び出されます。 デフォルトでは、理由は `console.log` でログに出力されます。 カスタム関数を提供して挙動をカスタマイズしたり、 `false` を設定してログを完全に抑制したりできます。 ```ts // Custom logging app.use( cache({ cacheName: 'my-app-v1', onCacheNotAvailable: () => { console.log('Custom log: Cache API is not available.') }, }) ) ``` ```ts // Suppress logging app.use( cache({ cacheName: 'my-app-v1', onCacheNotAvailable: false, }) ) ``` # Combine ミドルウェア Combine Middleware は、複数のミドルウェア関数を1つのミドルウェアに結合します。 次の3つの関数を提供します: - `some` - 与えられたミドルウェアのうち1つだけを実行します。 - `every` - 与えられたすべてのミドルウェアを実行します。 - `except` - 条件が満たされない場合にのみ、与えられたすべてのミドルウェアを実行します。 ## Import ```ts import { Hono } from 'hono' import { some, every, except } from 'hono/combine' ``` ## 使い方 Combine Middleware を使った複雑なアクセス制御ルールの例です。 ```ts import { Hono } from 'hono' import { bearerAuth } from 'hono/bearer-auth' import { getConnInfo } from '@hono/cloudflare-workers' import { every, some } from 'hono/combine' import { ipRestriction } from 'hono/ip-restriction' import { rateLimit } from '@/my-rate-limit' const app = new Hono() app.use( '*', some( every( ipRestriction(getConnInfo, { allowList: ['192.168.0.2'] }), bearerAuth({ token }) ), // If both conditions are met, rateLimit will not execute. rateLimit() ) ) app.get('/', (c) => c.text('Hello Hono!')) ``` ### some true を返した最初のミドルウェアを実行します。 ミドルウェアは順番に適用され、いずれかのミドルウェアが正常に終了すると、それ以降のミドルウェアは実行されません。 ```ts import { some } from 'hono/combine' import { bearerAuth } from 'hono/bearer-auth' import { myRateLimit } from '@/rate-limit' // If client has a valid token, skip rate limiting. // Otherwise, apply rate limiting. app.use( '/api/*', some(bearerAuth({ token }), myRateLimit({ limit: 100 })) ) ``` ### every すべてのミドルウェアを実行し、いずれかが失敗した場合は停止します。 ミドルウェアは順番に適用され、いずれかのミドルウェアがエラーをスローすると、それ以降のミドルウェアは実行されません。 ```ts import { some, every } from 'hono/combine' import { bearerAuth } from 'hono/bearer-auth' import { myCheckLocalNetwork } from '@/check-local-network' import { myRateLimit } from '@/rate-limit' // If client is in local network, skip authentication and rate limiting. // Otherwise, apply authentication and rate limiting. app.use( '/api/*', some( myCheckLocalNetwork(), every(bearerAuth({ token }), myRateLimit({ limit: 100 })) ) ) ``` ### except 条件が満たされていない場合にすべてのミドルウェアを実行します。 条件として文字列または関数を渡せます。 複数のターゲットにマッチさせる必要がある場合は、配列として渡します。 ```ts import { except } from 'hono/combine' import { bearerAuth } from 'hono/bearer-auth' // If client is accessing public API, skip authentication. // Otherwise, require a valid token. app.use('/api/*', except('/api/public/*', bearerAuth({ token }))) ``` # Compress ミドルウェア このミドルウェアは、 `Accept-Encoding` リクエストヘッダーに応じてレスポンスボディを圧縮します。 ::: info **Note**: Cloudflare Workers と Deno Deploy では、レスポンスボディは自動的に圧縮されるため、このミドルウェアを使用する必要はありません。 ::: ## Import ```ts import { Hono } from 'hono' import { compress } from 'hono/compress' ``` ## 使い方 ```ts const app = new Hono() app.use(compress()) ``` ## オプション ### encoding: `'gzip'` | `'deflate'` レスポンスの圧縮に許可する圧縮方式です。 `gzip` または `deflate` のいずれかです。 定義しない場合は両方が許可され、 `Accept-Encoding` ヘッダーに基づいて使用されます。 このオプションが指定されておらず、クライアントが `Accept-Encoding` ヘッダーで両方を指定している場合は、 `gzip` が優先されます。 ### threshold: `number` 圧縮する最小サイズ (バイト) です。 デフォルトは 1024 バイトです。 ### contentTypeFilter: `RegExp` | `(contentType: string) => boolean` `Content-Type` に基づいてレスポンスを圧縮するかどうかを判定するための `RegExp` または関数です。 デフォルトでは、組み込みの圧縮可能な Content-Type のリストが使用されます。 マッチする Content-Types のみを圧縮したい場合は、 `RegExp` を渡せます: ```ts // Compress only JSON responses app.use(compress({ contentTypeFilter: /^application\/json/ })) ``` または、カスタムロジックのために関数を渡せます。 組み込みの `COMPRESSIBLE_CONTENT_TYPE_REGEX` もエクスポートされているので、デフォルトの挙動を拡張できます: ```ts import { compress, COMPRESSIBLE_CONTENT_TYPE_REGEX, } from 'hono/compress' // Compress the default Content-Types plus a custom one app.use( compress({ contentTypeFilter: (type) => COMPRESSIBLE_CONTENT_TYPE_REGEX.test(type) || type === 'application/x-myformat', }) ) ``` # Context Storage ミドルウェア Context Storage Middleware は、 Hono の `Context` を `AsyncLocalStorage` に保存し、グローバルにアクセスできるようにします。 ::: info **Note** このミドルウェアは `AsyncLocalStorage` を使用します。 ランタイムがこれをサポートしている必要があります。 **Cloudflare Workers**: `AsyncLocalStorage` を有効にするには、 `wrangler.toml` ファイルに [`nodejs_compat` または `nodejs_als` フラグ](https://developers.cloudflare.com/workers/configuration/compatibility-dates/#nodejs-compatibility-flag) を追加してください。 ::: ## Import ```ts import { Hono } from 'hono' import { contextStorage, getContext, tryGetContext, } from 'hono/context-storage' ``` ## 使い方 `contextStorage()` がミドルウェアとして適用されている場合、 `getContext()` は現在の Context オブジェクトを返します。 ```ts type Env = { Variables: { message: string } } const app = new Hono() app.use(contextStorage()) app.use(async (c, next) => { c.set('message', 'Hello!') await next() }) // You can access the variable outside the handler. const getMessage = () => { return getContext().var.message } app.get('/', (c) => { return c.text(getMessage()) }) ``` Cloudflare Workers では、ハンドラーの外部から Bindings にアクセスできます。 ```ts type Env = { Bindings: { KV: KVNamespace } } const app = new Hono() app.use(contextStorage()) const setKV = (value: string) => { return getContext().env.KV.put('key', value) } ``` ## tryGetContext `tryGetContext()` は `getContext()` と同様に動作しますが、コンテキストが利用できない場合にエラーをスローする代わりに `undefined` を返します: ```ts const context = tryGetContext() if (context) { // Context is available console.log(context.var.message) } ``` # CORS ミドルウェア Web API としての Cloudflare Workers のユースケースは多く、外部のフロントエンドアプリケーションから呼び出されます。 そのためには CORS を実装する必要があります。 これもミドルウェアで実装しましょう。 ## Import ```ts import { Hono } from 'hono' import { cors } from 'hono/cors' ``` ## 使い方 ```ts const app = new Hono() // CORS should be called before the route app.use('/api/*', cors()) app.use( '/api2/*', cors({ origin: 'http://example.com', allowHeaders: ['X-Custom-Header', 'Upgrade-Insecure-Requests'], allowMethods: ['POST', 'GET', 'OPTIONS'], exposeHeaders: ['Content-Length', 'X-Kuma-Revision'], maxAge: 600, credentials: true, }) ) app.all('/api/abc', (c) => { return c.json({ success: true }) }) app.all('/api2/abc', (c) => { return c.json({ success: true }) }) ``` 複数のオリジン: ```ts app.use( '/api3/*', cors({ origin: ['https://example.com', 'https://example.org'], }) ) // Or you can use "function" app.use( '/api4/*', cors({ // `c` is a `Context` object origin: (origin, c) => { return origin.endsWith('.example.com') ? origin : 'http://example.com' }, }) ) ``` オリジンに基づく動的な許可メソッド: ```ts app.use( '/api5/*', cors({ origin: (origin) => origin === 'https://example.com' ? origin : '*', // `c` is a `Context` object allowMethods: (origin, c) => origin === 'https://example.com' ? ['GET', 'HEAD', 'POST', 'PATCH', 'DELETE'] : ['GET', 'HEAD'], }) ) ``` ## オプション ### origin: `string` | `string[]` | `(origin:string, c:Context) => string` "_Access-Control-Allow-Origin_" CORS ヘッダーの値です。 `origin: (origin) => (origin.endsWith('.example.com') ? origin : 'http://example.com')` のようなコールバック関数を渡すこともできます。 デフォルトは `*` です。 ### allowMethods: `string[]` | `(origin:string, c:Context) => string[]` "_Access-Control-Allow-Methods_" CORS ヘッダーの値です。 オリジンに基づいて許可メソッドを動的に決定するコールバック関数を渡すこともできます。 デフォルトは `['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH', 'QUERY']` です。 ### allowHeaders: `string[]` "_Access-Control-Allow-Headers_" CORS ヘッダーの値です。 デフォルトは `[]` です。 ### maxAge: `number` "_Access-Control-Max-Age_" CORS ヘッダーの値です。 ### credentials: `boolean` "_Access-Control-Allow-Credentials_" CORS ヘッダーの値です。 ### exposeHeaders: `string[]` "_Access-Control-Expose-Headers_" CORS ヘッダーの値です。 デフォルトは `[]` です。 ## 環境に依存する CORS の設定 開発環境や本番環境など、実行環境に応じて CORS の設定を調整したい場合は、環境変数から値を注入すると便利です。 これにより、アプリケーションが自身の実行環境を意識する必要がなくなります。 以下の例を参考にしてください。 ```ts app.use('*', async (c, next) => { const corsMiddlewareHandler = cors({ origin: c.env.CORS_ORIGIN, }) return corsMiddlewareHandler(c, next) }) ``` ## Vite との併用 Hono を Vite と併用する場合は、 `vite.config.ts` で `server.cors` を `false` に設定して、 Vite の組み込み CORS 機能を無効にする必要があります。 これは、 Hono の CORS ミドルウェアとの競合を防ぎます。 ```ts // vite.config.ts import { cloudflare } from '@cloudflare/vite-plugin' import { defineConfig } from 'vite' export default defineConfig({ server: { cors: false, // disable Vite's built-in CORS setting }, plugins: [cloudflare()], }) ``` # CSRF 保護 このミドルウェアは、 `Origin` ヘッダーと `Sec-Fetch-Site` ヘッダーの両方をチェックすることで、 CSRF 攻撃から保護します。 いずれかの検証に合格すれば、リクエストは許可されます。 ミドルウェアが検証するのは、次のようなリクエストのみです: - 安全でない HTTP メソッドを使用している (GET 、 HEAD 、 OPTIONS 以外) - HTML フォームで送信可能なコンテンツタイプを持っている (`application/x-www-form-urlencoded` 、 `multipart/form-data` 、 `text/plain`) `Origin` ヘッダーを送信しない古いブラウザや、リバースプロキシでこれらのヘッダーを削除する環境では、うまく動作しない場合があります。 そのような環境では、他の CSRF トークン方式を使用してください。 ## Import ```ts import { Hono } from 'hono' import { csrf } from 'hono/csrf' ``` ## 使い方 ```ts const app = new Hono() // Default: both origin and sec-fetch-site validation app.use(csrf()) // Allow specific origins app.use(csrf({ origin: 'https://myapp.example.com' })) // Allow multiple origins app.use( csrf({ origin: [ 'https://myapp.example.com', 'https://development.myapp.example.com', ], }) ) // Allow specific sec-fetch-site values app.use(csrf({ secFetchSite: 'same-origin' })) app.use(csrf({ secFetchSite: ['same-origin', 'none'] })) // Dynamic origin validation // It is strongly recommended that the protocol be verified to ensure a match to `$`. // You should *never* do a forward match. app.use( '*', csrf({ origin: (origin) => /https:\/\/(\w+\.)?myapp\.example\.com$/.test(origin), }) ) // Dynamic sec-fetch-site validation app.use( csrf({ secFetchSite: (secFetchSite, c) => { // Always allow same-origin if (secFetchSite === 'same-origin') return true // Allow cross-site for webhook endpoints if ( secFetchSite === 'cross-site' && c.req.path.startsWith('/webhook/') ) { return true } return false }, }) ) ``` ## オプション ### origin: `string` | `string[]` | `Function` CSRF 保護で許可するオリジンを指定します。 - **`string`**: 単一の許可オリジン (例: `'https://example.com'`) - **`string[]`**: 許可オリジンの配列 - **`Function`**: 柔軟なオリジン検証とバイパスロジックのためのカスタムハンドラ `(origin: string, context: Context) => boolean` **Default**: リクエスト URL と同じオリジンのみ 関数ハンドラは、リクエストの `Origin` ヘッダーの値とリクエストコンテキストを受け取ります。 これにより、パス、ヘッダー、その他のコンテキストデータなどのリクエストプロパティに基づいた動的な検証が可能になります。 ### secFetchSite: `string` | `string[]` | `Function` [Fetch Metadata](https://web.dev/articles/fetch-metadata) を使用した CSRF 保護で許可する Sec-Fetch-Site ヘッダーの値を指定します。 - **`string`**: 単一の許可値 (例: `'same-origin'`) - **`string[]`**: 許可値の配列 (例: `['same-origin', 'none']`) - **`Function`**: 柔軟な検証のためのカスタムハンドラ `(secFetchSite: string, context: Context) => boolean` **Default**: `'same-origin'` のみ許可 標準の Sec-Fetch-Site の値: - `same-origin`: 同一オリジンからのリクエスト - `same-site`: 同一サイト (異なるサブドメイン) からのリクエスト - `cross-site`: 異なるサイトからのリクエスト - `none`: Web ページ以外からのリクエスト (例: ブラウザのアドレスバー、ブックマーク) 関数ハンドラは、リクエストの `Sec-Fetch-Site` ヘッダーの値とリクエストコンテキストを受け取り、リクエストのプロパティに基づいた動的な検証を可能にします。 # ETag ミドルウェア このミドルウェアを使用すると、 ETag ヘッダーを簡単に追加できます。 ## Import ```ts import { Hono } from 'hono' import { etag } from 'hono/etag' ``` ## 使い方 ```ts const app = new Hono() app.use('/etag/*', etag()) app.get('/etag/abc', (c) => { return c.text('Hono is cool') }) ``` ## 保持されるヘッダー 304 レスポンスには、同等の 200 OK レスポンスで送信されるはずだったヘッダーを含める必要があります。 デフォルトのヘッダーは、 Cache-Control 、 Content-Location 、 Date 、 ETag 、 Expires 、 Vary です。 送信されるヘッダーを追加したい場合は、 `retainedHeaders` オプションと、デフォルトのヘッダーを含む `RETAINED_304_HEADERS` 文字列配列変数を使用できます: ```ts import { etag, RETAINED_304_HEADERS } from 'hono/etag' // ... app.use( '/etag/*', etag({ retainedHeaders: ['x-message', ...RETAINED_304_HEADERS], }) ) ``` ## オプション ### weak: `boolean` [弱い検証](https://developer.mozilla.org/ja/docs/Web/HTTP/Conditional_requests#weak_validation) を使用するかどうかを定義します。 `true` が設定されると、値のプレフィックスに `w/` が追加されます。 デフォルトは `false` です。 ### retainedHeaders: `string[]` 304 レスポンスに保持したいヘッダーです。 ### generateDigest: `(body: Uint8Array) => ArrayBuffer | Promise` カスタムダイジェスト生成関数です。 デフォルトでは `SHA-1` を使用します。 この関数はレスポンスボディを `Uint8Array` として受け取り、ハッシュを `ArrayBuffer` またはその Promise として返す必要があります。 # IP Restriction ミドルウェア IP Restriction Middleware は、ユーザーの IP アドレスに基づいてリソースへのアクセスを制限するミドルウェアです。 ## Import ```ts import { Hono } from 'hono' import { ipRestriction } from 'hono/ip-restriction' ``` ## 使い方 Bun 上で動作するアプリケーションで、ローカルからのアクセスのみを許可したい場合は、次のように書けます。 拒否したいルールを `denyList` に、許可したいルールを `allowList` に指定します。 ```ts import { Hono } from 'hono' import { getConnInfo } from '@hono/bun' import { ipRestriction } from 'hono/ip-restriction' const app = new Hono() app.use( '*', ipRestriction(getConnInfo, { denyList: [], allowList: ['127.0.0.1', '::1'], }) ) app.get('/', (c) => c.text('Hello Hono!')) ``` 環境に適した [ConnInfo Helper](/docs/helpers/conninfo) の `getConninfo` を `ipRestriction` の第1引数として渡します。 例えば、 Deno の場合は次のようになります: ```ts import { getConnInfo } from '@hono/deno' import { ipRestriction } from 'hono/ip-restriction' //... app.use( '*', ipRestriction(getConnInfo, { // ... }) ) ``` ## ルール ルールの書き方は以下の手順に従ってください。 ### IPv4 - `192.168.2.0` - Static IP Address - `192.168.2.0/24` - CIDR Notation - `*` - ALL Addresses ### IPv6 - `::1` - Static IP Address - `::1/10` - CIDR Notation - `*` - ALL Addresses ## エラー処理 エラーをカスタマイズするには、第3引数で `Response` を返します。 ```ts app.use( '*', ipRestriction( getConnInfo, { denyList: ['192.168.2.0/24'], }, async (remote, c) => { return c.text(`Blocking access from ${remote.addr}`, 403) } ) ) ``` # JSX Renderer ミドルウェア JSX Renderer Middleware は、 `c.setRenderer()` を使うことなく、 `c.render()` 関数で JSX をレンダリングする際のレイアウトを設定できるようにします。 さらに、 `useRequestContext()` を使用して、コンポーネント内の Context のインスタンスにアクセスできるようにします。 ## Import ```ts import { Hono } from 'hono' import { jsxRenderer, useRequestContext } from 'hono/jsx-renderer' ``` ## 使い方 ```jsx const app = new Hono() app.get( '/page/*', jsxRenderer(({ children }) => { return (
Menu
{children}
) }) ) app.get('/page/about', (c) => { return c.render(

About me!

) }) ``` ## オプション ### docType: `boolean` | `string` HTML の先頭に DOCTYPE を追加したくない場合は、 `docType` オプションを `false` に設定してください。 ```tsx app.use( '*', jsxRenderer( ({ children }) => { return ( {children} ) }, { docType: false } ) ) ``` また、 DOCTYPE を指定することもできます。 ```tsx app.use( '*', jsxRenderer( ({ children }) => { return ( {children} ) }, { docType: '', } ) ) ``` ### stream: `boolean` | `Record` `true` を設定するか Record 値を指定すると、ストリーミングレスポンスとしてレンダリングされます。 ```tsx const AsyncComponent = async () => { await new Promise((r) => setTimeout(r, 1000)) // sleep 1s return
Hi!
} app.get( '*', jsxRenderer( ({ children }) => { return (

SSR Streaming

{children} ) }, { stream: true } ) ) app.get('/', (c) => { return c.render( loading...}> ) }) ``` `true` を設定すると、次のヘッダーが追加されます: ```ts { 'Transfer-Encoding': 'chunked', 'Content-Type': 'text/html; charset=UTF-8', 'Content-Encoding': 'Identity' } ``` Record 値を指定することで、ヘッダー値をカスタマイズできます。 ### Function-based Options 静的なオプションオブジェクトの代わりに、 `Context` オブジェクトを受け取る関数を渡せます。 これにより、環境変数やリクエストパラメータなど、リクエストコンテキストに基づいてオプションを動的に設定できます。 ```tsx app.use( '*', jsxRenderer( ({ children }) => { return ( {children} ) }, (c) => ({ stream: c.req.header('X-Enable-Streaming') === 'true', }) ) ) ``` 具体的な例として、 [`isSSGContext`](/docs/helpers/ssg#isssgcontext) ヘルパーを使用して、 `` で静的サイト (SSG) を生成する際にストリーミングを無効化できます: ```tsx app.use( '*', jsxRenderer( ({ children }) => { return (
) }, (c) => ({ stream: !isSSGContext(c), }) ) ) ``` ## ネストされたレイアウト `Layout` コンポーネントにより、レイアウトをネストできます。 ```tsx app.use( jsxRenderer(({ children }) => { return ( {children} ) }) ) const blog = new Hono() blog.use( jsxRenderer(({ children, Layout }) => { return (
{children}
) }) ) app.route('/blog', blog) ``` ## `useRequestContext()` `useRequestContext()` は Context のインスタンスを返します。 ```tsx import { useRequestContext, jsxRenderer } from 'hono/jsx-renderer' const app = new Hono() app.use(jsxRenderer()) const RequestUrlBadge: FC = () => { const c = useRequestContext() return {c.req.url} } app.get('/page/info', (c) => { return c.render(
You are accessing:
) }) ``` ::: warning Deno の `precompile` JSX オプションでは `useRequestContext()` を使用できません。 `react-jsx` を使用してください: ```json "compilerOptions": { "jsx": "precompile", // [!code --] "jsx": "react-jsx", // [!code ++] "jsxImportSource": "hono/jsx" } } ``` ::: ## `ContextRenderer` の拡張 以下のように `ContextRenderer` を定義すると、レンダラーに追加のコンテンツを渡せます。 これは例えば、ページごとに head タグの中身を変更したい場合に便利です。 ```tsx declare module 'hono' { interface ContextRenderer { ( content: string | Promise, props: { title: string } ): Response } } const app = new Hono() app.get( '/page/*', jsxRenderer(({ children, title }) => { return ( {title}
Menu
{children}
) }) ) app.get('/page/favorites', (c) => { return c.render(
  • Eating sushi
  • Watching baseball games
, { title: 'My favorites', } ) }) ``` # JWK Auth ミドルウェア JWK Auth Middleware は、 JWK (JSON Web Key) を使ってトークンを検証することでリクエストを認証します。 `Authorization` ヘッダーと、指定された場合はクッキーなどの他の設定されたソースをチェックします。 提供された `keys` を使用してトークンを検証し、指定された場合は `jwks_uri` からキーを取得し、 `cookie` オプションが設定されている場合はクッキーからのトークン抽出をサポートします。 ## このミドルウェアが検証すること 各トークンについて、 `jwk()` は次を行います: - JWT ヘッダーの形式をパースして検証します。 - `kid` ヘッダーを要求し、 `kid` によってマッチするキーを探します。 - 対称アルゴリズム (`HS256` 、 `HS384` 、 `HS512`) を拒否します。 - ヘッダーの `alg` が設定された `alg` 許可リストに含まれていることを要求します。 - マッチした JWK に `alg` フィールドがある場合、それが JWT ヘッダーの `alg` と一致することを要求します。 - マッチしたキーでトークンの署名を検証します。 - デフォルトでは、時刻ベースのクレームである `nbf` 、 `exp` 、 `iat` を検証します。 オプションのクレーム検証は、 `verification` オプションで設定できます: - `iss`: 指定された場合に issuer を検証します。 - `aud`: 指定された場合にオーディエンスを検証します。 上記以外の追加のトークンチェックが必要な場合 (例えば、カスタムのアプリケーションレベルの認可ルールなど) は、 `jwk()` の後に自分のミドルウェアで追加してください。 :::info クライアントから送信される Authorization ヘッダーには、指定されたスキームが必要です。 例: `Bearer my.token.value` または `Basic my.token.value` ::: ## Import ```ts import { Hono } from 'hono' import { jwk } from 'hono/jwk' import { verifyWithJwks } from 'hono/jwt' ``` ## 使い方 ```ts const app = new Hono() app.use( '/auth/*', jwk({ jwks_uri: `https://${backendServer}/.well-known/jwks.json`, alg: ['RS256'], }) ) app.get('/auth/page', (c) => { return c.text('You are authorized') }) ``` ペイロードの取得: ```ts const app = new Hono() app.use( '/auth/*', jwk({ jwks_uri: `https://${backendServer}/.well-known/jwks.json`, alg: ['RS256'], }) ) app.get('/auth/page', (c) => { const payload = c.get('jwtPayload') return c.json(payload) // eg: { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 } }) ``` 匿名アクセス: ```ts const app = new Hono() app.use( '/auth/*', jwk({ jwks_uri: (c) => `https://${c.env.authServer}/.well-known/jwks.json`, alg: ['RS256'], allow_anon: true, }) ) app.get('/auth/page', (c) => { const payload = c.get('jwtPayload') return c.json(payload ?? { message: 'hello anon' }) }) ``` ## ミドルウェア外での `verifyWithJwks` の使用 `verifyWithJwks` ユーティリティ関数は、 SvelteKit の SSR ページなどの Hono のミドルウェアコンテキスト外のサーバーサイド環境で JWT トークンを検証するために使用できます: ```ts const id_payload = await verifyWithJwks( id_token, { jwks_uri: 'https://your-auth-server/.well-known/jwks.json', allowedAlgorithms: ['RS256'], }, { cf: { cacheEverything: true, cacheTtl: 3600 }, } ) ``` ## JWKS 取得リクエストオプションの設定 `jwks_uri` から JWKS を取得する方法を設定するには、 `jwk()` の第2引数として fetch リクエストオプションを渡します。 この引数は `RequestInit` であり、 JWKS の fetch リクエストにのみ使用されます。 ```ts const app = new Hono() app.use( '/auth/*', jwk( { jwks_uri: `https://${backendServer}/.well-known/jwks.json`, alg: ['RS256'], }, { headers: { Authorization: 'Bearer TOKEN', }, } ) ) ``` ## オプション ### alg: `AsymmetricAlgorithm[]` トークン検証に使用される許可された非対称アルゴリズムの配列です。 利用可能な型は `RS256` | `RS384` | `RS512` | `PS256` | `PS384` | `PS512` | `ES256` | `ES384` | `ES512` | `EdDSA` です。 ### keys: `HonoJsonWebKey[] | (c: Context) => Promise` 公開鍵の値、またはそれらを返す関数です。 関数は Context オブジェクトを受け取ります。 ### jwks_uri: `string` | `(c: Context) => Promise` この値が設定されている場合、この URI から JWK の取得を試みます。 `keys` を含む JSON レスポンスを期待し、取得したキーは提供された `keys` オプションに追加されます。 Context を使用して JWKS URI を動的に決定するコールバック関数を渡すこともできます。 ### allow_anon: `boolean` この値が `true` に設定されている場合、有効なトークンを持たないリクエストもミドルウェアを通過できるようになります。 リクエストが認証されているかどうかを確認するには `c.get('jwtPayload')` を使用してください。 デフォルトは `false` です。 ### cookie: `string` この値が設定されている場合、その値をキーとしてクッキーヘッダーから値が取得され、トークンとして検証されます。 ### headerName: `string` JWT トークンを探すヘッダーの名前です。 デフォルトは `Authorization` です。 ### realm: `string` `401` レスポンスで返される `WWW-Authenticate` チャレンジヘッダーの `realm` パラメータによって記述される保護空間です。 デフォルトはリクエスト URL です。 ### verification: `VerifyOptions` 署名検証に加えて、クレーム検証の挙動を設定します: [Keep in sync with jwt.md]: # #### VerifyOptions.iss: `string | RegExp` トークン検証に使用される期待される issuer です。 これが設定されていない場合、 `iss` クレームはチェック**されません**。 #### VerifyOptions.aud: `string | string[] | RegExp` トークン検証に使用される期待されるオーディエンスです。 これが設定されている場合、トークンは `aud` クレームを含んでいなければならず、少なくとも1つのオーディエンス値が一致する必要があります。 #### VerifyOptions.nbf: `boolean` `nbf` (not before) クレームは、存在し、かつこれが `true` に設定されている場合に検証されます。 デフォルトは `true` です。 #### VerifyOptions.iat: `boolean` `iat` (issued at) クレームは、存在し、かつこれが `true` に設定されている場合に検証されます。 デフォルトは `true` です。 #### VerifyOptions.exp: `boolean` `exp` (expiration time) クレームは、存在し、かつこれが `true` に設定されている場合に検証されます。 デフォルトは `true` です。 # JWT 認証ミドルウェア JWT 認証ミドルウェアは、 JWT を使ってトークンを検証して認証することができます。 ミドルウェアは、 `cookie` オプションがセットされていない場合に `Authorization` ヘッダを確認します。 ヘッダの名前は `headerName` オプションでカスタマイズすることができます。 :::info クライアントから送信された Authotization ヘッダには、指定されたスキーマが必要です。 例: `Bearer my.token.value` や `Basic my.token.value` など ::: ## Import ```ts import { Hono } from 'hono' import { jwt } from 'hono/jwt' import type { JwtVariables } from 'hono/jwt' ``` ## 使い方 ```ts // Specify the variable types to infer the `c.get('jwtPayload')`: type Variables = JwtVariables const app = new Hono<{ Variables: Variables }>() app.use( '/auth/*', jwt({ secret: 'it-is-very-secret', alg: 'HS256', }) ) app.get('/auth/page', (c) => { return c.text('You are authorized') }) ``` ペイロードを取得する: ```ts const app = new Hono() app.use( '/auth/*', jwt({ secret: 'it-is-very-secret', alg: 'HS256', verification: { iss: 'my-trusted-issuer', aud: 'my-api', }, }) ) app.get('/auth/page', (c) => { const payload = c.get('jwtPayload') return c.json(payload) // eg: { "sub": "1234567890", "name": "John Doe", "iat": 1516239022, "iss": "my-trusted-issuer" } }) ``` ::: tip `jwt()` は単なるミドルウェア関数です。 環境変数を使いたい場合は、 (例: `c.env.JWT_SECRET`) 、 以下のように使用できます: ```js app.use('/auth/*', (c, next) => { const jwtMiddleware = jwt({ secret: c.env.JWT_SECRET, alg: 'HS256', }) return jwtMiddleware(c, next) }) ``` ::: ## オプション ### secret: `string` 秘密鍵の値。 ### alg: `string` 認証に使われるアルゴリズム。 利用可能な型は `HS256` | `HS384` | `HS512` | `RS256` | `RS384` | `RS512` | `PS256` | `PS384` | `PS512` | `ES256` | `ES384` | `ES512` | `EdDSA` です。 ### cookie: `string` この値が設定されている場合は、 Cookie ヘッダから値が取得され、検証されます。 ### headerName: `string` JWT トークンを探すヘッダの名前。 デフォルトは `Authorization` です。 ```ts app.use( '/auth/*', jwt({ secret: 'it-is-very-secret', alg: 'HS256', headerName: 'x-custom-auth-header', }) ) ``` ### realm: `string` `401` レスポンスで返される `WWW-Authenticate` チャレンジヘッダーの `realm` パラメータによって記述される保護空間です。 デフォルトはリクエスト URL です。 ```ts app.use( '/auth/*', jwt({ secret: 'it-is-very-secret', realm: 'my-protected-api', }) ) ``` ### verification: `VerifyOptions` トークンの検証を制御するオプション。 [Keep in sync with jwk.md]: # #### VerifyOptions.iss: `string | RegExp` 期待される検証トークンの発行者。 この値が設定されていない場合、 `iss` クレームはチェック**されません**。 #### VerifyOptions.aud: `string | string[] | RegExp` トークン検証に使用される期待されるオーディエンスです。 これが設定されている場合、トークンは `aud` クレームを含んでいなければならず、少なくとも1つのオーディエンス値が一致する必要があります。 #### VerifyOptions.nbf: `boolean` `nbf` (not before) クレームは、存在し、かつこれが `true` に設定されている場合に検証されます。 デフォルトは `true` です。 #### VerifyOptions.iat: `boolean` `iat` (issued at) クレームは、存在し、かつこれが `true` に設定されている場合に検証されます。 デフォルトは `true` です。 #### VerifyOptions.exp: `boolean` `exp` (expiration time) クレームは、存在し、かつこれが `true` に設定されている場合に検証されます。 デフォルトは `true` です。 # Language ミドルウェア Language Detector ミドルウェアは、様々なソースからユーザーの優先言語 (ロケール) を自動的に判定し、 `c.get('language')` 経由で利用できるようにします。 検出戦略には、クエリパラメータ、クッキー、ヘッダー、 URL パスセグメントが含まれます。 国際化 (i18n) やロケール固有のコンテンツに最適です。 ## Import ```ts import { Hono } from 'hono' import { languageDetector } from 'hono/language' ``` ## 基本的な使い方 クエリ文字列、クッキー、ヘッダー (デフォルトの順序) から言語を検出し、フォールバックとして英語を使用します: ```ts const app = new Hono() app.use( languageDetector({ supportedLanguages: ['en', 'ar', 'ja'], // Must include fallback fallbackLanguage: 'en', // Required }) ) app.get('/', (c) => { const lang = c.get('language') return c.text(`Hello! Your language is ${lang}`) }) ``` ### クライアントの例 ```sh # Via path curl http://localhost:8787/ar/home # Via query parameter curl http://localhost:8787/?lang=ar # Via cookie curl -H 'Cookie: language=ja' http://localhost:8787/ # Via header curl -H 'Accept-Language: ar,en;q=0.9' http://localhost:8787/ ``` ## デフォルト設定 ```ts export const DEFAULT_OPTIONS: DetectorOptions = { order: ['querystring', 'cookie', 'header'], lookupQueryString: 'lang', lookupCookie: 'language', lookupFromHeaderKey: 'accept-language', lookupFromPathIndex: 0, caches: ['cookie'], ignoreCase: true, fallbackLanguage: 'en', supportedLanguages: ['en'], cookieOptions: { sameSite: 'Strict', secure: true, maxAge: 365 * 24 * 60 * 60, httpOnly: true, }, debug: false, } ``` ## 主な挙動 ### 検出のワークフロー 1. **Order**: デフォルトではこの順序でソースをチェックします: - クエリパラメータ (?lang=ar) - クッキー (language=ar) - Accept-Language ヘッダー 2. **Caching**: 検出された言語をクッキーに保存します (デフォルトで1年) 3. **Fallback**: 有効な検出がなかった場合に `fallbackLanguage` を使用します (`supportedLanguages` に含まれている必要があります) ## 高度な設定 ### カスタム検出順序 URL パスからの検出を優先します (例: /en/about): ```ts app.use( languageDetector({ order: ['path', 'cookie', 'querystring', 'header'], lookupFromPathIndex: 0, // /en/profile → index 0 = 'en' supportedLanguages: ['en', 'ar'], fallbackLanguage: 'en', }) ) ``` ### 段階的なロケールマッチング `ja-JP` のような検出されたロケールコードが `supportedLanguages` に存在しない場合、ミドルウェアはサブタグを段階的に切り詰めてマッチを探します。 例えば、 `zh-Hant-CN` は `zh-Hant` 、次に `zh` を試します。 完全一致が常に優先されます。 ```ts app.use( languageDetector({ supportedLanguages: ['en', 'ja', 'zh-Hant'], fallbackLanguage: 'en', }) ) // Accept-Language: ja-JP → matches 'ja' // Accept-Language: zh-Hant-CN → matches 'zh-Hant' ``` ### 言語コードの変換 複雑なコードを正規化します (例: en-US → en): ```ts app.use( languageDetector({ convertDetectedLanguage: (lang) => lang.split('-')[0], supportedLanguages: ['en', 'ja'], fallbackLanguage: 'en', }) ) ``` ### クッキーの設定 ```ts app.use( languageDetector({ lookupCookie: 'app_lang', caches: ['cookie'], cookieOptions: { path: '/', // Cookie path sameSite: 'Lax', // Cookie same-site policy secure: true, // Only send over HTTPS maxAge: 86400 * 365, // 1 year expiration httpOnly: true, // Not accessible via JavaScript domain: '.example.com', // Optional: specific domain }, }) ) ``` クッキーキャッシュを無効にする場合: ```ts languageDetector({ caches: false, }) ``` ### デバッグ 検出の過程をログに出力します: ```ts languageDetector({ debug: true, // Shows: "Detected from querystring: ar" }) ``` ## オプションリファレンス ### 基本オプション | Option | Type | Default | Required | Description | | :------------------- | :--------------- | :------------------------------------ | :------- | :--------------------- | | `supportedLanguages` | `string[]` | `['en']` | Yes | Allowed language codes | | `fallbackLanguage` | `string` | `'en'` | Yes | Default language | | `order` | `DetectorType[]` | `['querystring', 'cookie', 'header']` | No | Detection sequence | | `debug` | `boolean` | `false` | No | Enable logging | ### 検出オプション | Option | Type | Default | Description | | :-------------------- | :------- | :------------------ | :------------------- | | `lookupQueryString` | `string` | `'lang'` | Query parameter name | | `lookupCookie` | `string` | `'language'` | Cookie name | | `lookupFromHeaderKey` | `string` | `'accept-language'` | Header name | | `lookupFromPathIndex` | `number` | `0` | Path segment index | ### クッキーオプション | Option | Type | Default | Description | | :----------------------- | :---------------------------- | :----------- | :------------------- | | `caches` | `CacheType[] \| false` | `['cookie']` | Cache settings | | `cookieOptions.path` | `string` | `'/'` | Cookie path | | `cookieOptions.sameSite` | `'Strict' \| 'Lax' \| 'None'` | `'Strict'` | SameSite policy | | `cookieOptions.secure` | `boolean` | `true` | HTTPS only | | `cookieOptions.maxAge` | `number` | `31536000` | Expiration (seconds) | | `cookieOptions.httpOnly` | `boolean` | `true` | JS accessibility | | `cookieOptions.domain` | `string` | `undefined` | Cookie domain | ### 高度なオプション | Option | Type | Default | Description | | :------------------------ | :------------------------- | :---------- | :------------------------ | | `ignoreCase` | `boolean` | `true` | Case-insensitive matching | | `convertDetectedLanguage` | `(lang: string) => string` | `undefined` | Language code transformer | ## バリデーションとエラー処理 - `fallbackLanguage` は `supportedLanguages` に含まれている必要があります (セットアップ時にエラーをスローします) - `lookupFromPathIndex` は 0 以上である必要があります - 無効な設定はミドルウェアの初期化時にエラーをスローします - 検出に失敗した場合は、黙って `fallbackLanguage` が使用されます ## よくあるレシピ ### パスベースのルーティング ```ts app.get('/:lang/home', (c) => { const lang = c.get('language') // 'en', 'ar', etc. return c.json({ message: getLocalizedContent(lang) }) }) ``` ### 複数のサポート言語 ```ts languageDetector({ supportedLanguages: ['en', 'en-GB', 'ar', 'ar-EG'], convertDetectedLanguage: (lang) => lang.replace('_', '-'), // Normalize }) ``` # Logger ミドルウェア シンプルなロガーです。 ## Import ```ts import { Hono } from 'hono' import { logger } from 'hono/logger' ``` ## 使い方 ```ts const app = new Hono() app.use(logger()) app.get('/', (c) => c.text('Hello Hono!')) ``` ## ログの詳細 Logger Middleware は、各リクエストについて以下の詳細をログに出力します: - **Incoming Request**: HTTP メソッド、リクエストパス、受信したリクエストをログに出力します。 - **Outgoing Response**: HTTP メソッド、リクエストパス、レスポンスのステータスコード、リクエスト/レスポンスの時間をログに出力します。 - **Status Code Coloring**: レスポンスのステータスコードは、視認性を高め、ステータスカテゴリを素早く識別できるように色分けされます。 異なるステータスコードのカテゴリは、異なる色で表現されます。 - **Elapsed Time**: リクエスト/レスポンスのサイクルにかかった時間は、人間が読める形式で、ミリ秒 (ms) または秒 (s) のいずれかでログに出力されます。 Logger Middleware を使用することで、 Hono アプリケーションにおけるリクエストとレスポンスの流れを簡単に監視し、問題やパフォーマンスのボトルネックを素早く特定できます。 独自の `PrintFunc` 関数を提供することで、ログの挙動をカスタマイズし、ミドルウェアをさらに拡張することもできます。 ::: tip _status code coloring_ を無効にするには、 `NO_COLOR` 環境変数を設定します。 これは、ロギングライブラリで ANSI カラーエスケープコードを無効にする一般的な方法で、 で説明されています。 なお、 Cloudflare Workers には `process.env` オブジェクトがないため、デフォルトではプレーンテキストのログ出力になります。 ::: ## PrintFunc Logger Middleware は、オプションの `PrintFunc` 関数をパラメータとして受け取ります。 この関数により、ロガーをカスタマイズしたり、追加のログを出力したりできます。 ## オプション ### fn: `PrintFunc(str: string, ...rest: string[])` - `str`: ロガーから渡されます。 - `...rest`: コンソールに出力される追加の文字列 props です。 ### 例 Logger Middleware にカスタム `PrintFunc` 関数を設定します: ```ts export const customLogger = (message: string, ...rest: string[]) => { console.log(message, ...rest) } app.use(logger(customLogger)) ``` ルート内でカスタムロガーを使用します: ```ts app.post('/blog', (c) => { // Routing logic customLogger('Blog saved:', `Path: ${blog.url},`, `ID: ${blog.id}`) // Output // <-- POST /blog // Blog saved: Path: /blog/example, ID: 1 // --> POST /blog 201 93ms // Return Context }) ``` # Method Not Allowed ミドルウェア Method Not Allowed ミドルウェアは、リクエストパスが登録済みのルートにマッチするが、リクエストメソッドがサポートされていない場合に、 `Allow` ヘッダー付きの `405 Method Not Allowed` レスポンスを返します。 このミドルウェアがない場合、 Hono はそのケースで `404 Not Found` を返します。 ## Import ```ts import { Hono } from 'hono' import { methodNotAllowed } from 'hono/method-not-allowed' ``` ## 使い方 ```ts const app = new Hono() app.use(methodNotAllowed({ app })) app.get('/hello', (c) => c.text('Hello!')) app.post('/hello', (c) => c.text('Posted!')) // PUT /hello -> 405 Method Not Allowed // Allow: GET, HEAD, POST ``` `onMethodNotAllowed` オプションでレスポンスをカスタマイズできます: ```ts app.use( methodNotAllowed({ app, onMethodNotAllowed: (c, methods) => c.json({ error: 'Method Not Allowed' }, 405, { Allow: methods.join(', '), }), }) ) ``` ## オプション ### app: `Hono` アプリケーションで使用される Hono のインスタンスです。 ミドルウェアは、登録されたルートから各パスに対して許可されたメソッドを収集します。 ### onMethodNotAllowed: `(c: Context, allowedMethods: string[]) => Response | Promise` `Allow` ヘッダーを含むレスポンスを生成します。 デフォルトでは、ミドルウェアは許可されたメソッドが設定された `Allow` ヘッダー付きの `405 Method Not Allowed` レスポンスを返します。 # Method Override ミドルウェア このミドルウェアは、フォーム、ヘッダー、クエリの値に応じて、リクエストの実際のメソッドとは異なる指定されたメソッドのハンドラーを実行し、そのレスポンスを返します。 ## Import ```ts import { Hono } from 'hono' import { methodOverride } from 'hono/method-override' ``` ## 使い方 ```ts const app = new Hono() // If no options are specified, the value of `_method` in the form, // e.g. DELETE, is used as the method. app.use('/posts', methodOverride({ app })) app.delete('/posts', (c) => { // .... }) ``` ## 例 HTML フォームは DELETE メソッドを送信できないため、 `_method` という名前のプロパティに `DELETE` の値を入れて送信できます。 すると、 `app.delete()` のハンドラーが実行されます。 HTML フォーム: ```html
``` アプリケーション: ```ts import { methodOverride } from 'hono/method-override' const app = new Hono() app.use('/posts', methodOverride({ app })) app.delete('/posts', () => { // ... }) ``` デフォルト値を変更したり、ヘッダー値やクエリ値を使用できます: ```ts app.use('/posts', methodOverride({ app, form: '_custom_name' })) app.use( '/posts', methodOverride({ app, header: 'X-METHOD-OVERRIDE' }) ) app.use('/posts', methodOverride({ app, query: '_method' })) ``` ## オプション ### app: `Hono` アプリケーションで使用される `Hono` のインスタンスです。 ### form: `string` メソッド名を含む値を持つフォームのキーです。 デフォルトは `_method` です。 ### header: `boolean` メソッド名を含む値を持つヘッダー名です。 ### query: `boolean` メソッド名を含む値を持つクエリパラメータのキーです。 # Mount ミドルウェア Mount ミドルウェアは、 別のフレームワークでビルドされたアプリケーションを自身の Hono アプリケーションへマウントさせます。 ## Import ```ts twoslash import { Hono } from 'hono' import { mount } from 'hono/mount' ``` ## 使い方 `app.all()` を使用して `/*` で終わるパスに対してミドルウェアを登録します: ```ts import { Router as IttyRouter } from 'itty-router' // itty-router アプリケーションを生成 const ittyRouter = IttyRouter() // `GET /itty-router/hello` を処理 ittyRouter.get('/hello', () => new Response('Hello from itty-router')) const app = new Hono() app.all('/itty-router/*', mount(ittyRouter.handle)) ``` デフォルトでは、 マウントされたアプリケーションは、 URL からプレフィクスが削除されたパスを持った新しい `Request` オブジェクトを受け取ります。 `/itty-router/hello` へのリクエストは、 `/hello` として itty-router へ渡されます。 マウントされたアプリケーションは、 `handler(request, c.env, c.executionCtx)` のようにコールされます。 Cloudflare Workers `fetch` ハンドラと同じ引数です。 別の引数を渡すには `optionHandler` を使用します。 ## オプション 第 2 引数として関数やオブジェクトを渡すことができます。 ### optionHandler: `(c: Context) => unknown` `Request` の後にマウントされたアプリケーションに渡した引数を返します。 配列は複数の引数に展開され、 第 2 引数として直接関数を渡すことができます: ```ts twoslash import { Hono } from 'hono' import { mount } from 'hono/mount' const app = new Hono() const anotherApp = (request: Request, ...args: unknown[]) => new Response(request.url) // ---cut--- // `anotherApp(request, c.env)` をコール app.all( '/app/*', mount(anotherApp, (c) => c.env) ) // `anotherApp(request, c.env, c.executionCtx)` をコール app.all( '/app/*', mount(anotherApp, { optionHandler: (c) => [c.env, c.executionCtx], }) ) ``` ### replaceRequest: `((originalRequest: Request) => Request) | false` どの `Request` がマウントされたアプリケーションに渡されたのかを制御します: ```ts twoslash import { Hono } from 'hono' import { mount } from 'hono/mount' const app = new Hono() const anotherApp = (request: Request, ...args: unknown[]) => new Response(request.url) // ---cut--- // `/legacy/users` は `/api/users` としてマウントされたアプリケーションに届きます app.all( '/legacy/*', mount(anotherApp, { replaceRequest: (req) => { const url = new URL(req.url) url.pathname = url.pathname.replace(/^\/legacy/, '/api') return new Request(url, req) }, }) ) ``` 変更されていない元の `Request` を渡す際に、 `(req) => req` を簡略化するために `false` をセットします: ```ts twoslash import { Hono } from 'hono' import { mount } from 'hono/mount' const app = new Hono() const anotherApp = (request: Request, ...args: unknown[]) => new Response(request.url) // ---cut--- app.all( '/app/*', mount(anotherApp, { replaceRequest: false, }) ) ``` # Pretty JSON ミドルウェア Pretty JSON ミドルウェアは、 JSON レスポンスボディに対して "_JSON pretty print_" を有効にします。 URL のクエリパラメータに `?pretty` を追加すると、 JSON 文字列が整形されます。 ```js // GET / {"project":{"name":"Hono","repository":"https://github.com/honojs/hono"}} ``` これは次のようになります: ```js // GET /?pretty { "project": { "name": "Hono", "repository": "https://github.com/honojs/hono" } } ``` ## Import ```ts import { Hono } from 'hono' import { prettyJSON } from 'hono/pretty-json' ``` ## 使い方 ```ts const app = new Hono() app.use(prettyJSON()) // With options: prettyJSON({ space: 4 }) app.get('/', (c) => { return c.json({ message: 'Hono!' }) }) ``` ## オプション ### space: `number` インデントに使用するスペースの数です。 デフォルトは `2` です。 ### query: `string` 適用するためのクエリ文字列の名前です。 デフォルトは `pretty` です。 ### force: `boolean` `true` に設定すると、クエリパラメータに関係なく JSON レスポンスが常に整形されます。 デフォルトは `false` です。 # Request ID ミドルウェア Request ID Middleware は、各リクエストに対して一意の ID を生成し、ハンドラー内で使用できます。 ::: info **Node.js**: このミドルウェアは `crypto.randomUUID()` を使って ID を生成します。 グローバルな `crypto` は Node.js のバージョン 20 以降で導入されました。 そのため、それより前のバージョンではエラーが発生する可能性があります。 その場合は `generator` を指定してください。 ただし、 [Node.js アダプタ](https://github.com/honojs/node-server) を使用している場合は、 `crypto` が自動的にグローバルに設定されるため、この必要はありません。 ::: ## Import ```ts import { Hono } from 'hono' import { requestId } from 'hono/request-id' ``` ## 使い方 Request ID Middleware が適用されたハンドラーやミドルウェアでは、 `requestId` 変数を通して Request ID にアクセスできます。 ```ts const app = new Hono() app.use('*', requestId()) app.get('/', (c) => { return c.text(`Your request id is ${c.get('requestId')}`) }) ``` 型を明示的に指定したい場合は、 `RequestIdVariables` をインポートして、 `new Hono()` のジェネリクスに渡します。 ```ts import type { RequestIdVariables } from 'hono/request-id' const app = new Hono<{ Variables: RequestIdVariables }>() ``` ### Request ID の設定 ヘッダー (デフォルト: `X-Request-Id`) にカスタム Request ID を設定すると、ミドルウェアは新しい ID を生成する代わりにその値を使用します: ```ts const app = new Hono() app.use('*', requestId()) app.get('/', (c) => { return c.text(`${c.get('requestId')}`) }) const res = await app.request('/', { headers: { 'X-Request-Id': 'your-custom-id', }, }) console.log(await res.text()) // your-custom-id ``` この機能を無効にしたい場合は、 [`headerName` オプション](#headername-string) に空文字列を設定してください。 ## オプション ### limitLength: `number` Request ID の最大長です。 デフォルトは `255` です。 ### headerName: `string` Request ID に使用されるヘッダー名です。 デフォルトは `X-Request-Id` です。 ### generator: `(c: Context) => string` Request ID の生成関数です。 デフォルトでは `crypto.randomUUID()` を使用します。 ## プラットフォーム固有の Request ID 一部のプラットフォーム (AWS Lambda など) は、リクエストごとに独自の Request ID を既に生成しています。 追加の設定を行わない場合、このミドルウェアはこれらのプラットフォーム固有の Request ID を認識せず、 新しい Request ID を生成します。 これにより、アプリケーションのログを見るときに混乱が生じる可能性があります。 これらの ID を統一するには、 `generator` 関数を使ってプラットフォーム固有の Request ID を取得し、このミドルウェアで使用します。 ### プラットフォーム固有のリンク - AWS Lambda - [AWS documentation: Context object](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-context.html) - [Hono: Access AWS Lambda Object](/docs/getting-started/aws-lambda#access-aws-lambda-object) - Cloudflare - [Cloudflare Ray ID ](https://developers.cloudflare.com/fundamentals/reference/cloudflare-ray-id/) - Deno - [Request ID on the Deno Blog](https://deno.com/blog/zero-config-debugging-deno-opentelemetry#:~:text=s%20automatically%20have-,unique%20request%20IDs,-associated%20with%20them) - Fastly - [Fastly documentation: req.xid](https://www.fastly.com/documentation/reference/vcl/variables/client-request/req-xid/) # Secure Headers ミドルウェア Secure Headers Middleware は、セキュリティヘッダーの設定を簡単にします。 Helmet の機能に一部インスピレーションを受けており、特定のセキュリティヘッダーの有効化と無効化を制御できます。 ## Import ```ts import { Hono } from 'hono' import { secureHeaders } from 'hono/secure-headers' ``` ## 使い方 デフォルトで、最適な設定を使用できます。 ```ts const app = new Hono() app.use(secureHeaders()) ``` 不要なヘッダーは、 `false` を設定することで抑制できます。 ```ts const app = new Hono() app.use( '*', secureHeaders({ xFrameOptions: false, xXssProtection: false, }) ) ``` デフォルトのヘッダー値は、文字列で上書きできます。 ```ts const app = new Hono() app.use( '*', secureHeaders({ strictTransportSecurity: 'max-age=63072000; includeSubDomains; preload', xFrameOptions: 'DENY', xXssProtection: '1', }) ) ``` ## サポートされているオプション 各オプションは、次のヘッダーのキーと値のペアに対応しています。 | Option | Header | Value | Default | | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ---------- | | - | X-Powered-By | (Delete Header) | True | | contentSecurityPolicy | [Content-Security-Policy](https://developer.mozilla.org/ja/docs/Web/HTTP/CSP) | Usage: [Setting Content-Security-Policy](#setting-content-security-policy) | No Setting | | contentSecurityPolicyReportOnly | [Content-Security-Policy-Report-Only](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only) | Usage: [Setting Content-Security-Policy](#setting-content-security-policy) | No Setting | | trustedTypes | [Trusted Types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/trusted-types) | Usage: [Setting Content-Security-Policy](#setting-content-security-policy) | No Setting | | requireTrustedTypesFor | [Require Trusted Types For](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/require-trusted-types-for) | Usage: [Setting Content-Security-Policy](#setting-content-security-policy) | No Setting | | crossOriginEmbedderPolicy | [Cross-Origin-Embedder-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy) | require-corp | **False** | | crossOriginResourcePolicy | [Cross-Origin-Resource-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy) | same-origin | True | | crossOriginOpenerPolicy | [Cross-Origin-Opener-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy) | same-origin | True | | originAgentCluster | [Origin-Agent-Cluster](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin-Agent-Cluster) | ?1 | True | | referrerPolicy | [Referrer-Policy](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Referrer-Policy) | no-referrer | True | | reportingEndpoints | [Reporting-Endpoints](https://www.w3.org/TR/reporting-1/#header) | Usage: [Setting Content-Security-Policy](#setting-content-security-policy) | No Setting | | reportTo | [Report-To](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to) | Usage: [Setting Content-Security-Policy](#setting-content-security-policy) | No Setting | | strictTransportSecurity | [Strict-Transport-Security](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Strict-Transport-Security) | max-age=15552000; includeSubDomains | True | | xContentTypeOptions | [X-Content-Type-Options](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-Content-Type-Options) | nosniff | True | | xDnsPrefetchControl | [X-DNS-Prefetch-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control) | off | True | | xDownloadOptions | [X-Download-Options](https://learn.microsoft.com/en-us/archive/blogs/ie/ie8-security-part-v-comprehensive-protection#mime-handling-force-save) | noopen | True | | xFrameOptions | [X-Frame-Options](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-Frame-Options) | SAMEORIGIN | True | | xPermittedCrossDomainPolicies | [X-Permitted-Cross-Domain-Policies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Permitted-Cross-Domain-Policies) | none | True | | xXssProtection | [X-XSS-Protection](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-XSS-Protection) | 0 | True | | permissionPolicy | [Permissions-Policy](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Permissions-Policy) | Usage: [Setting Permission-Policy](#setting-permission-policy) | No Setting | ## Middleware Conflict 同じヘッダーを操作するミドルウェアを扱う場合は、指定の順序に注意してください。 この場合、 Secure-headers が動作し、 `x-powered-by` は削除されます: ```ts const app = new Hono() app.use(secureHeaders()) app.use(poweredBy()) ``` この場合、 Powered-By が動作し、 `x-powered-by` は追加されます: ```ts const app = new Hono() app.use(poweredBy()) app.use(secureHeaders()) ``` ## Content-Security-Policy の設定 ```ts const app = new Hono() app.use( '/test', secureHeaders({ reportingEndpoints: [ { name: 'endpoint-1', url: 'https://example.com/reports', }, ], // -- or alternatively // reportTo: [ // { // group: 'endpoint-1', // max_age: 10886400, // endpoints: [{ url: 'https://example.com/reports' }], // }, // ], contentSecurityPolicy: { defaultSrc: ["'self'"], baseUri: ["'self'"], childSrc: ["'self'"], connectSrc: ["'self'"], fontSrc: ["'self'", 'https:', 'data:'], formAction: ["'self'"], frameAncestors: ["'self'"], frameSrc: ["'self'"], imgSrc: ["'self'", 'data:'], manifestSrc: ["'self'"], mediaSrc: ["'self'"], objectSrc: ["'none'"], reportTo: 'endpoint-1', reportUri: '/csp-report', sandbox: ['allow-same-origin', 'allow-scripts'], scriptSrc: ["'self'"], scriptSrcAttr: ["'none'"], scriptSrcElem: ["'self'"], styleSrc: ["'self'", 'https:', "'unsafe-inline'"], styleSrcAttr: ['none'], styleSrcElem: ["'self'", 'https:', "'unsafe-inline'"], upgradeInsecureRequests: [], workerSrc: ["'self'"], }, }) ) ``` ### `nonce` 属性 `hono/secure-headers` からインポートした `NONCE` を `scriptSrc` または `styleSrc` に追加することで、 `script` または `style` 要素に [`nonce` 属性](https://developer.mozilla.org/ja/docs/Web/HTML/Global_attributes/nonce) を追加できます: ```tsx import { secureHeaders, NONCE } from 'hono/secure-headers' import type { SecureHeadersVariables } from 'hono/secure-headers' // Specify the variable types to infer the `c.get('secureHeadersNonce')`: type Variables = SecureHeadersVariables const app = new Hono<{ Variables: Variables }>() // Set the pre-defined nonce value to `scriptSrc`: app.get( '*', secureHeaders({ contentSecurityPolicy: { scriptSrc: [NONCE, 'https://allowed1.example.com'], }, }) ) // Get the value from `c.get('secureHeadersNonce')`: app.get('/', (c) => { return c.html( {/** contents */} `} Hello! ) }) ``` ### 関数コンポーネントとして動作 `html` は HtmlEscapedString を返すので、 JSX を使わずとも完全に機能するコンポーネントとして動作できます。 #### `memo` の代わりに `html` を使って処理を高速化する ```typescript const Footer = () => html`
My Address...
` ``` ### props の受け取りと値の埋め込み ```typescript interface SiteData { title: string description: string image: string children?: any } const Layout = (props: SiteData) => html` ${props.title} ${props.children} ` const Content = (props: { siteData: SiteData; name: string }) => (

Hello {props.name}

) app.get('/', (c) => { const props = { name: 'World', siteData: { title: 'Hello <> World', description: 'This is a description', image: 'https://example.com/image.png', }, } return c.html() }) ``` ## `raw()` ```ts app.get('/', (c) => { const name = 'John "Johnny" Smith' return c.html(html`

I'm ${raw(name)}.

`) }) ``` ## Tips これらのライブラリのおかげで、 Visual Studio Code や vim もテンプレートリテラルを HTML として解釈し、シンタックスハイライトやフォーマットを適用できます。 - - # JWT 認証ヘルパー このヘルパーは、 JSON Web Token (JWT) のエンコード、デコード、署名、検証のための関数を提供します。 JWT は、 Web アプリケーションで認証や認可の目的で広く使われています。 このヘルパーは、様々な暗号化アルゴリズムをサポートする堅牢な JWT 機能を提供します。 ## Import このヘルパーを使用するには、次のようにインポートします: ```ts import { decode, sign, verify } from 'hono/jwt' ``` ::: info [JWT ミドルウェア](/docs/middleware/builtin/jwt) も `hono/jwt` から `jwt` 関数をインポートします。 ::: ## `sign()` この関数は、ペイロードをエンコードし、指定されたアルゴリズムとシークレットで署名することで、 JWT トークンを生成します。 ```ts sign( payload: unknown, secret: string, alg?: 'HS256'; ): Promise; ``` ### 例 ```ts import { sign } from 'hono/jwt' const payload = { sub: 'user123', role: 'admin', exp: Math.floor(Date.now() / 1000) + 60 * 5, // Token expires in 5 minutes } const secret = 'mySecretKey' const token = await sign(payload, secret) ``` ### オプション
#### payload: `unknown` 署名される JWT のペイロードです。 [ペイロードの検証](#payload-validation) にあるような他のクレームを含めることができます。 #### secret: `string` JWT の検証や署名に使用されるシークレットキーです。 #### alg: [AlgorithmTypes](#supported-algorithmtypes) JWT の署名や検証に使用されるアルゴリズムです。 デフォルトは HS256 です。 ## `verify()` この関数は、 JWT トークンが本物でまだ有効であるかを確認します。 トークンが改ざんされていないことを保証し、 [ペイロードの検証](#payload-validation) を追加した場合にのみ有効性をチェックします。 ```ts verify( token: string, secret: string, alg: 'HS256'; issuer?: string | RegExp; aud?: string | string[] | RegExp; ): Promise; ``` ### 例 ```ts import { verify } from 'hono/jwt' const tokenToVerify = 'token' const secretKey = 'mySecretKey' const decodedPayload = await verify(tokenToVerify, secretKey, 'HS256') console.log(decodedPayload) ``` ### オプション
#### token: `string` 検証対象の JWT トークンです。 #### secret: `string` JWT の検証や署名に使用されるシークレットキーです。 #### alg: [AlgorithmTypes](#supported-algorithmtypes) JWT の署名や検証に使用されるアルゴリズムです。 #### issuer: `string | RegExp` JWT の検証に使用される期待される issuer です。 #### aud: `string | string[] | RegExp` JWT の検証に使用される期待されるオーディエンスです。 これが設定されている場合、トークンは `aud` クレームを含んでいなければならず、少なくとも1つのオーディエンス値が一致する必要があります。 ## `decode()` この関数は、署名の検証を行わずに JWT トークンをデコードします。 トークンからヘッダーとペイロードを抽出して返します。 ```ts decode(token: string): { header: any; payload: any }; ``` ### 例 ```ts import { decode } from 'hono/jwt' // Decode the JWT token const tokenToDecode = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOiAidXNlcjEyMyIsICJyb2xlIjogImFkbWluIn0.JxUwx6Ua1B0D1B0FtCrj72ok5cm1Pkmr_hL82sd7ELA' const { header, payload } = decode(tokenToDecode) console.log('Decoded Header:', header) console.log('Decoded Payload:', payload) ``` ### オプション
#### token: `string` デコード対象の JWT トークンです。 > `decode` 関数を使うと、検証を行わ _**ずに**_ JWT トークンのヘッダーとペイロードを調査できます。 これは、デバッグや JWT トークンからの情報抽出に役立ちます。 ## ペイロードの検証 JWT トークンの検証時には、以下のペイロード検証が行われます: - `exp`: トークンの有効期限が切れていないかチェックされます。 - `nbf`: トークンが指定された時刻より前に使用されていないかチェックされます。 - `iat`: トークンが未来に発行されていないかチェックされます。 - `iss`: トークンが信頼できる issuer によって発行されたものかチェックされます。 - `aud`: `aud` 検証パラメータが設定されている場合、トークンが許可されたオーディエンス向けのものであるかチェックされます。 検証時にこれらのチェックを行いたい場合は、 JWT ペイロードにこれらのフィールドをオブジェクトとして含めてください。 ## カスタムエラー型 このモジュールは、 JWT 関連のエラーを処理するためのカスタムエラー型も定義しています。 - `JwtAlgorithmNotImplemented`: リクエストされた JWT アルゴリズムが実装されていないことを示します。 - `JwtTokenInvalid`: JWT トークンが無効であることを示します。 - `JwtTokenNotBefore`: トークンが有効日より前に使用されていることを示します。 - `JwtTokenExpired`: トークンの有効期限が切れていることを示します。 - `JwtTokenIssuedAt`: トークン内の "iat" クレームが正しくないことを示します。 - `JwtTokenIssuer`: トークン内の "iss" クレームが正しくないことを示します。 - `JwtPayloadRequiresAud`: `aud` 検証が設定されている場合に `aud` クレームが必要であることを示します。 - `JwtTokenAudience`: トークンの `aud` クレームが期待されるオーディエンスと一致しないことを示します。 - `JwtTokenSignatureMismatched`: トークン内の署名の不一致を示します。 ## サポートされている AlgorithmTypes このモジュールは、以下の JWT 暗号化アルゴリズムをサポートしています: - `HS256`: HMAC using SHA-256 - `HS384`: HMAC using SHA-384 - `HS512`: HMAC using SHA-512 - `RS256`: RSASSA-PKCS1-v1_5 using SHA-256 - `RS384`: RSASSA-PKCS1-v1_5 using SHA-384 - `RS512`: RSASSA-PKCS1-v1_5 using SHA-512 - `PS256`: RSASSA-PSS using SHA-256 and MGF1 with SHA-256 - `PS384`: RSASSA-PSS using SHA-386 and MGF1 with SHA-386 - `PS512`: RSASSA-PSS using SHA-512 and MGF1 with SHA-512 - `ES256`: ECDSA using P-256 and SHA-256 - `ES384`: ECDSA using P-384 and SHA-384 - `ES512`: ECDSA using P-521 and SHA-512 - `EdDSA`: EdDSA using Ed25519 # プロキシヘルパー Proxy Helper は、 Hono アプリケーションを (リバース) プロキシとして使用する際に役立つ関数を提供します。 ## Import ```ts import { Hono } from 'hono' import { proxy } from 'hono/proxy' ``` ## `proxy()` `proxy()` は、プロキシのための `fetch()` API ラッパーです。 引数と戻り値は `fetch()` と同じです (プロキシ固有のオプションを除く)。 `Accept-Encoding` ヘッダーは、現在のランタイムが処理できるエンコーディングに置き換えられます。 不要なレスポンスヘッダーは削除され、ハンドラーから送信できる `Response` オブジェクトが返されます。 ### 例 シンプルな使い方: ```ts app.get('/proxy/:path', (c) => { return proxy(`http://${originServer}/${c.req.param('path')}`) }) ``` 複雑な使い方: ```ts app.get('/proxy/:path', async (c) => { const res = await proxy( `http://${originServer}/${c.req.param('path')}`, { headers: { ...c.req.header(), // optional, specify only when forwarding all the request data (including credentials) is necessary. 'X-Forwarded-For': '127.0.0.1', 'X-Forwarded-Host': c.req.header('host'), Authorization: undefined, // do not propagate request headers contained in c.req.header('Authorization') }, } ) res.headers.delete('Set-Cookie') return res }) ``` または、 `c.req` を引数として渡すこともできます。 ```ts app.all('/proxy/:path', (c) => { return proxy(`http://${originServer}/${c.req.param('path')}`, { ...c.req, // optional, specify only when forwarding all the request data (including credentials) is necessary. headers: { ...c.req.header(), 'X-Forwarded-For': '127.0.0.1', 'X-Forwarded-Host': c.req.header('host'), Authorization: undefined, // do not propagate request headers contained in c.req.header('Authorization') }, }) }) ``` `customFetch` オプションで、デフォルトのグローバル `fetch` 関数をオーバーライドできます: ```ts app.get('/proxy', (c) => { return proxy('https://example.com/', { customFetch, }) }) ``` ### Connection ヘッダーの処理 デフォルトでは、 `proxy()` は Hop-by-Hop ヘッダーインジェクション攻撃を防ぐために `Connection` ヘッダーを無視します。 `strictConnectionProcessing` オプションで、厳密な RFC 9110 準拠を有効にできます: ```ts // Default behavior (recommended for untrusted clients) app.get('/proxy/:path', (c) => { return proxy(`http://${originServer}/${c.req.param('path')}`, c.req) }) // Strict RFC 9110 compliance (use only in trusted environments) app.get('/internal-proxy/:path', (c) => { return proxy(`http://${internalServer}/${c.req.param('path')}`, { ...c.req, strictConnectionProcessing: true, }) }) ``` ### `ProxyFetch` `proxy()` の型は `ProxyFetch` として定義されており、以下の通りです ```ts interface ProxyRequestInit extends Omit { raw?: Request customFetch?: (request: Request) => Promise strictConnectionProcessing?: boolean headers?: | HeadersInit | [string, string][] | Record | Record } interface ProxyFetch { ( input: string | URL | Request, init?: ProxyRequestInit ): Promise } ``` # ルートヘルパー Route Helper は、デバッグやミドルウェア開発のための拡張されたルーティング情報を提供します。 マッチしたルートや現在処理中のルートに関する詳細な情報にアクセスできます。 ## インポート ```ts import { Hono } from 'hono' import { matchedRoutes, routePath, baseRoutePath, basePath, } from 'hono/route' ``` ## 使い方 ### 基本的なルート情報 ```ts const app = new Hono() app.get('/posts/:id', (c) => { const currentPath = routePath(c) // '/posts/:id' const routes = matchedRoutes(c) // Array of matched routes return c.json({ path: currentPath, totalRoutes: routes.length, }) }) ``` ### サブアプリケーションでの使用 ```ts const app = new Hono() const apiApp = new Hono() apiApp.get('/posts/:id', (c) => { return c.json({ routePath: routePath(c), // '/posts/:id' baseRoutePath: baseRoutePath(c), // '/api' basePath: basePath(c), // '/api' (with actual params) }) }) app.route('/api', apiApp) ``` ## `matchedRoutes()` ミドルウェアを含む、現在のリクエストにマッチしたすべてのルートの配列を返します。 ```ts app.all('/api/*', (c, next) => { console.log('API middleware') return next() }) app.get('/api/users/:id', (c) => { const routes = matchedRoutes(c) // Returns: [ // { method: 'ALL', path: '/api/*', handler: [Function] }, // { method: 'GET', path: '/api/users/:id', handler: [Function] } // ] return c.json({ routes: routes.length }) }) ``` ## `routePath()` 現在のハンドラーに登録されたルートのパスパターンを返します。 ```ts app.get('/posts/:id', (c) => { console.log(routePath(c)) // '/posts/:id' return c.text('Post details') }) ``` ### インデックスパラメータの使用 `Array.prototype.at()` と同様に、インデックスパラメータを渡すことで特定の位置のルートパスを取得できます。 ```ts app.all('/api/*', (c, next) => { return next() }) app.get('/api/users/:id', (c) => { console.log(routePath(c, 0)) // '/api/*' (first matched route) console.log(routePath(c, -1)) // '/api/users/:id' (last matched route) return c.text('User details') }) ``` ## `baseRoutePath()` ルーティングで指定された現在のルートのベースパスパターンを返します。 ```ts const subApp = new Hono() subApp.get('/posts/:id', (c) => { return c.text(baseRoutePath(c)) // '/:sub' }) app.route('/:sub', subApp) ``` ### インデックスパラメータの使用 `Array.prototype.at()` と同様に、インデックスパラメータを渡すことで特定の位置の ベースルートパスを取得できます。 ```ts app.all('/api/*', (c, next) => { return next() }) const subApp = new Hono() subApp.get('/users/:id', (c) => { console.log(baseRoutePath(c, 0)) // '/' (first matched route) console.log(baseRoutePath(c, -1)) // '/api' (last matched route) return c.text('User details') }) app.route('/api', subApp) ``` ## `basePath()` 実際のリクエストから埋め込まれたパラメータを含むベースパスを返します。 ```ts const subApp = new Hono() subApp.get('/posts/:id', (c) => { return c.text(basePath(c)) // '/api' (for request to '/api/posts/123') }) app.route('/:sub', subApp) ``` # SSG ヘルパー SSG ヘルパーは Hono アプリケーションから静的サイトを作成します。 登録されたルートのコンテンツを取得し、静的なファイルとして保存します。 ## 使い方 ### 手動 このようなシンプルな Hono のアプリケーションがある時: ```tsx // index.tsx const app = new Hono() app.get('/', (c) => c.html('Hello, World!')) app.use('/about', async (c, next) => { c.setRenderer((content) => { return c.html(

{content}

) }) await next() }) app.get('/about', (c) => { return c.render( <> Hono SSG PageHello! ) }) export default app ``` Node.js では、このようなビルドスクリプトを書きます: ```ts // build.ts import app from './index' import { toSSG } from 'hono/ssg' import fs from 'fs/promises' toSSG(app, fs) ``` スクリプトを実行して、ファイルが出力されます: ```bash ls ./static about.html index.html ``` ### Vite プラグイン Vite プラグインである `@hono/vite-ssg` を使うと、簡単に処理を行うことができます。 詳しくは、以下のページを御覧ください: https://github.com/honojs/vite-plugins/tree/main/packages/ssg ## toSSG `toSSG` は静的サイトを作成するために使うメイン関数で、アプリケーションとファイルシステムモジュールを引数として受け取ります。 詳しくはこのようなものです: ### 入力 toSSG の引数は ToSSGInterface です。 ```ts export interface ToSSGInterface { ( app: Hono, fsModule: FileSystemModule, options?: ToSSGOptions ): Promise } ``` - `app` には、ルートを登録した `new Hono()` を指定します。 - `fs` には、 `node:fs/promise` のようなオブジェクトを指定します。 ```ts export interface FileSystemModule { writeFile(path: string, data: string | Uint8Array): Promise mkdir( path: string, options: { recursive: boolean } ): Promise } ``` ### Deno や Bun でアダプタを使用する Deno や Bun で SSG を行いたい場合、 `@hono/deno` と `@hono/bun` パッケージから `toSSG` 関数が提供されています。 Deno: ```ts import { toSSG } from '@hono/deno' toSSG(app) // The second argument is an option typed `ToSSGOptions`. ``` Bun: ```ts import { toSSG } from '@hono/bun' toSSG(app) // The second argument is an option typed `ToSSGOptions`. ``` ### オプション オプションは ToSSGOptions インターフェースで指定されます。 ```ts export interface ToSSGOptions { dir?: string concurrency?: number extensionMap?: Record plugins?: SSGPlugin[] } ``` - `dir` は静的サイトの出力先です。 デフォルトは `./static` です。 - `concurrency` は同時に処理・出力されるファイルの数です。 デフォルトは `2` です。 - `extensionMap` は `Content-Type` を key に、拡張子の文字列を value に持つオブジェクトです。 出力するファイルの拡張子を決めるために使われます。 - `plugins` は静的サイトジェネレータの機能を拡張する SSG プラグインの配列です。 ### 出力 `toSSG` はこのような型の戻り値を返します。 ```ts export interface ToSSGResult { success: boolean files: string[] error?: Error } ``` ## ファイルを作成する ### ルートとファイル名 このようなルールがルートと作成されるファイル名に適応されます。 デフォルトの出力ディレクトリの `./static` ではこのようになります: - `/` -> `./static/index.html` - `/path` -> `./static/path.html` - `/path/` -> `./static/path/index.html` ### 拡張子 拡張子はそれぞれのルートが返す `Content-Type` に依存します。 例えば、 `c.html` で返されたレスポンスは `.html` として保存されます。 拡張子をカスタマイズしたい場合は、 `extensionMap` オプションを指定してください。 ```ts import { toSSG, defaultExtensionMap } from 'hono/ssg' // Save `application/x-html` content with `.html` toSSG(app, fs, { extensionMap: { 'application/x-html': 'html', ...defaultExtensionMap, }, }) ``` スラッシュで終わるパスはファイルタイプに関係なく index.拡張子 として保存されることに注意してください。 ```ts // save to ./static/html/index.html app.get('/html/', (c) => c.html('html')) // save to ./static/text/index.txt app.get('/text/', (c) => c.text('text')) ``` ## ミドルウェア これから紹介するビルトインミドルウェアは SSG の処理を補助します。 ### ssgParams Next.js の `generateStaticParams` のようなことをしたい場合に使います。 例: ```ts app.get( '/shops/:id', ssgParams(async () => { const shops = await getShops() return shops.map((shop) => ({ id: shop.id })) }), async (c) => { const shop = await getShop(c.req.param('id')) if (!shop) { return c.notFound() } return c.render(

{shop.name}

) } ) ``` ### isSSGContext `isSSGContext` は、現在のアプリケーションが `toSSG` によってトリガーされた SSG コンテキスト内で実行されている場合に `true` を返すヘルパー関数です。 ```ts app.get('/page', (c) => { if (isSSGContext(c)) { return c.text('This is generated by SSG') } return c.text('This is served dynamically') }) ``` ### disableSSG `disableSSG` ミドルウェアを指定されたルートは `toSSG` のファイル生成から除外されます。 ```ts app.get('/api', disableSSG(), (c) => c.text('an-api')) ``` ### onlySSG `onlySSG` ミドルウェアを指定されたルートは `toSSG` 後の `c.notFound()` でオーバーライドされます。 ```ts app.get('/static-page', onlySSG(), (c) => c.html(

Welcome to my site

)) ``` ## プラグイン プラグインを使用すると、静的サイト生成プロセスの機能を拡張できます。 プラグインはフックを使用して、異なる段階で生成プロセスをカスタマイズします。 ### デフォルトプラグイン デフォルトでは、 `toSSG` は 200 以外のステータスレスポンス (リダイレクト、エラー、404 など) をスキップする `defaultPlugin` を使用します。 これにより、成功しなかったレスポンスのファイル生成が防止されます。 ```ts import { toSSG, defaultPlugin } from 'hono/ssg' // defaultPlugin is automatically applied when no plugins specified toSSG(app, fs) // Equivalent to: toSSG(app, fs, { plugins: [defaultPlugin] }) ``` カスタムプラグインを指定した場合、 `defaultPlugin` は自動的には含まれません。 デフォルトの挙動を維持しながらカスタムプラグインを追加するには、明示的に含めてください: ```ts toSSG(app, fs, { plugins: [defaultPlugin, myCustomPlugin], }) ``` ### リダイレクトプラグイン `redirectPlugin` は、 HTTP リダイレクトレスポンス (301 、 302 、 303 、 307 、 308) を返すルートに対して、 HTML リダイレクトページを生成します。 生成される HTML には `` タグと canonical リンクが含まれます。 ```ts import { toSSG, redirectPlugin, defaultPlugin } from 'hono/ssg' toSSG(app, fs, { plugins: [redirectPlugin(), defaultPlugin()], }) ``` 例えば、アプリに次のルートがある場合: ```ts app.get('/old', (c) => c.redirect('/new')) ``` `redirectPlugin` は、 `/new` への meta refresh リダイレクトを含む HTML ファイルを `/old.html` として生成します。 > [!NOTE] > `defaultPlugin` と併用する場合は、 `redirectPlugin` を `defaultPlugin` の**前に**配置してください。 `defaultPlugin` は 200 以外のレスポンスをスキップするため、先に配置すると `redirectPlugin` がリダイレクトレスポンスを処理できなくなります。 ### フックの型 プラグインは、次のフックを使用して `toSSG` プロセスをカスタマイズできます: ```ts export type BeforeRequestHook = (req: Request) => Request | false export type AfterResponseHook = (res: Response) => Response | false export type AfterGenerateHook = ( result: ToSSGResult ) => void | Promise ``` - **BeforeRequestHook**: 各リクエストの処理前に呼び出されます。 `false` を返すとそのルートをスキップします。 - **AfterResponseHook**: 各レスポンスの受信後に呼び出されます。 `false` を返すとファイル生成をスキップします。 - **AfterGenerateHook**: 生成プロセス全体の完了後に呼び出されます。 ### プラグインインターフェース ```ts export interface SSGPlugin { beforeRequestHook?: BeforeRequestHook | BeforeRequestHook[] afterResponseHook?: AfterResponseHook | AfterResponseHook[] afterGenerateHook?: AfterGenerateHook | AfterGenerateHook[] } ``` ### 基本的なプラグインの例 GET リクエストのみをフィルタする場合: ```ts const getOnlyPlugin: SSGPlugin = { beforeRequestHook: (req) => { if (req.method === 'GET') { return req } return false }, } ``` ステータスコードでフィルタする場合: ```ts const statusFilterPlugin: SSGPlugin = { afterResponseHook: (res) => { if (res.status === 200 || res.status === 500) { return res } return false }, } ``` 生成されたファイルをログに出力する場合: ```ts const logFilesPlugin: SSGPlugin = { afterGenerateHook: (result) => { if (result.files) { result.files.forEach((file) => console.log(file)) } }, } ``` ### 高度なプラグインの例 `sitemap.xml` ファイルを生成するサイトマッププラグインを作成する例です: ```ts // plugins.ts import fs from 'node:fs/promises' import path from 'node:path' import type { SSGPlugin } from 'hono/ssg' import { DEFAULT_OUTPUT_DIR } from 'hono/ssg' export const sitemapPlugin = (baseURL: string): SSGPlugin => { return { afterGenerateHook: (result, fsModule, options) => { const outputDir = options?.dir ?? DEFAULT_OUTPUT_DIR const filePath = path.join(outputDir, 'sitemap.xml') const urls = result.files.map((file) => new URL(file, baseURL).toString() ) const siteMapText = ` ${urls.map((url) => `${url}`).join('\n')} ` fsModule.writeFile(filePath, siteMapText) }, } } ``` プラグインの適用: ```ts import app from './index' import { toSSG } from 'hono/ssg' import { sitemapPlugin } from './plugins' toSSG(app, fs, { plugins: [ getOnlyPlugin, statusFilterPlugin, logFilesPlugin, sitemapPlugin('https://example.com'), ], }) ``` # Streaming ヘルパー Streaming ヘルパーは、 SSE (Server-Sent Events) などのストリーミングレスポンスのためのメソッドを提供します。 ## Import ```ts import { Hono } from 'hono' import { stream, streamText, streamSSE } from 'hono/streaming' ``` ## `stream()` `stream()` は簡素なストリーミングレスポンスを `Response` オブジェクトとして返します。 ```ts app.get('/stream', (c) => { return stream(c, async (stream) => { // Write a process to be executed when aborted. stream.onAbort(() => { console.log('Aborted!') }) // Write a Uint8Array. await stream.write(new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f])) // Pipe a readable stream. await stream.pipe(anotherReadableStream) }) }) ``` ## `streamText()` `Content-Type: text/plain` 、 `Transfer-Encoding: chunked` 、 `X-Content-Type-Options: nosniff` をヘッダーにもつストリーミングレスポンスを返します。 ```ts app.get('/streamText', (c) => { return streamText(c, async (stream) => { // Write a text with a new line ('\n'). await stream.writeln('Hello') // Wait 1 second. await stream.sleep(1000) // Write a text without a new line. await stream.write(`Hono!`) }) }) ``` ::: warning Cloudflare Workers 向けのアプリケーションを開発している場合、ストリーミングは Wrangler 上ではうまく動作しないことがあります。 その場合は、 `Content-Encoding` ヘッダーに `Identity` を追加してください。 ```ts app.get('/streamText', (c) => { c.header('Content-Encoding', 'Identity') return streamText(c, async (stream) => { // ... }) }) ``` ::: ## `streamSSE()` サーバー送信イベント (SSE: Server-Sent Events) をシームレスにストリーミングできます。 ```ts const app = new Hono() let id = 0 app.get('/sse', async (c) => { return streamSSE(c, async (stream) => { while (!stream.aborted) { const message = `It is ${new Date().toISOString()}` await stream.writeSSE({ data: message, event: 'time-update', id: String(id++), }) await stream.sleep(1000) } }) }) ``` ## Error Handling Streaming ヘルパーの第3引数はエラー・ハンドラです。 この引数はオプションで、指定しなければエラーはコンソールエラーとして出力されます。 ```ts app.get('/stream', (c) => { return stream( c, async (stream) => { // Write a process to be executed when aborted. stream.onAbort(() => { console.log('Aborted!') }) // Write a Uint8Array. await stream.write( new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]) ) // Pipe a readable stream. await stream.pipe(anotherReadableStream) }, (err, stream) => { stream.writeln('An error occurred!') console.error(err) } ) }) ``` コールバックが実行された後、ストリームは自動的に閉じられます。 ::: warning Streaming ヘルパーのコールバック関数内でエラーが発生した場合、 `onError` イベントは発火しません。 `onError` はレスポンスが送信される前にエラーをハンドルし、レスポンスを上書きするためのフックです。 しかしながら、コールバック関数が実行された時点でストリームは既に開始されているので、上書きすることはできません。 ::: # テストヘルパー Testing Helper は、 Hono アプリケーションのテストを容易にするための関数を提供します。 ## Import ```ts import { Hono } from 'hono' import { testClient } from 'hono/testing' ``` ## `testClient()` `testClient()` 関数は、 Hono のインスタンスを第1引数に取り、 [Hono Client](/docs/guides/rpc#client) と同様に、 Hono アプリケーションのルートに応じた型が付けられたオブジェクトを返します。 これにより、テスト内でエディタの自動補完を使いながら、型安全に定義したルートを呼び出せます。 **型推論に関する重要な注意:** `testClient` がルートの型を正しく推論し、自動補完を提供するためには、 **`Hono` インスタンスに対して直接メソッドチェーンでルートを定義する必要があります**。 型推論は、チェーンされた `.get()` 、 `.post()` などの呼び出しを通じて型が流れることに依存しています。 Hono インスタンスを作成した後にルートを個別に定義した場合 ("Hello World" の例でよく見られる `const app = new Hono(); app.get(...)` のようなパターン)、 `testClient` は特定のルートに必要な型情報を持たず、型安全なクライアントの機能を利用できません。 **例:** この例では、 `.get()` メソッドが `new Hono()` の呼び出しに直接チェーンされているため動作します: ```ts // index.ts const app = new Hono().get('/search', (c) => { const query = c.req.query('q') return c.json({ query: query, results: ['result1', 'result2'] }) }) export default app ``` ```ts // index.test.ts import { Hono } from 'hono' import { testClient } from 'hono/testing' import { describe, it, expect } from 'vitest' // Or your preferred test runner import app from './app' describe('Search Endpoint', () => { // Create the test client from the app instance const client = testClient(app) it('should return search results', async () => { // Call the endpoint using the typed client // Notice the type safety for query parameters (if defined in the route) // and the direct access via .$get() const res = await client.search.$get({ query: { q: 'hono' }, }) // Assertions expect(res.status).toBe(200) expect(await res.json()).toEqual({ query: 'hono', results: ['result1', 'result2'], }) }) }) ``` テストにヘッダーを含めたい場合は、呼び出しの第2パラメータとして渡します。 第2パラメータは `RequestInit` オブジェクトとして `init` プロパティも受け取ることができ、ヘッダー、メソッド、ボディなどを設定できます。 `init` プロパティの詳細は [こちら](/docs/guides/rpc#init-option) をご覧ください。 ```ts // index.test.ts import { Hono } from 'hono' import { testClient } from 'hono/testing' import { describe, it, expect } from 'vitest' // Or your preferred test runner import app from './app' describe('Search Endpoint', () => { // Create the test client from the app instance const client = testClient(app) it('should return search results', async () => { // Include the token in the headers and set the content type const token = 'this-is-a-very-clean-token' const res = await client.search.$get( { query: { q: 'hono' }, }, { headers: { Authorization: `Bearer ${token}`, 'Content-Type': `application/json`, }, } ) // Assertions expect(res.status).toBe(200) expect(await res.json()).toEqual({ query: 'hono', results: ['result1', 'result2'], }) }) }) ``` # WebSocket ヘルパー WebSocket ヘルパーは、 Hono アプリケーションのサーバーサイド WebSocket のヘルパーです。 現在、 Cloudflare Workers / Pages 、 Deno 、 Bun および Node.js アダプタが利用可能です。 ## Import The adapter for each runtime is a separate package (`@hono/cloudflare-workers`, `@hono/deno`, `@hono/bun`, ...). Install the one for your runtime. ::: code-group ```ts [Cloudflare Workers] import { Hono } from 'hono' import { upgradeWebSocket } from '@hono/cloudflare-workers' ``` ```ts [Deno] import { Hono } from 'hono' import { upgradeWebSocket } from '@hono/deno' ``` ```ts [Bun] import { Hono } from 'hono' import { upgradeWebSocket, websocket } from '@hono/bun' // ... export default { fetch: app.fetch, websocket, } ``` ```ts [Node.js] import { serve, upgradeWebSocket } from '@hono/node-server' import { Hono } from 'hono' import { WebSocketServer } from 'ws' ``` ::: Node.js では、 WebSocket サポートは `@hono/node-server` に組み込まれています。 有効にするには、 `ws` と、 TypeScript を使用している場合は `@types/ws` をインストールしてください。 次に、 `{ noServer: true }` を指定して `WebSocketServer` を作成し、 `websocket` オプション経由で `serve()` に渡します。 `@hono/node-ws` は非推奨です。 ## `upgradeWebSocket()` `upgradeWebSocket()` は WebSocket をハンドルするためのハンドラを返します。 ```ts const app = new Hono() app.get( '/ws', upgradeWebSocket((c) => { return { onMessage(event, ws) { console.log(`Message from client: ${event.data}`) ws.send('Hello from server!') }, onClose: () => { console.log('Connection closed') }, } }) ) ``` 使えるイベント一覧: - `onOpen` - Cloudflare Workers では現在動きません - `onMessage` - `onClose` - `onError` ::: warning WebSocket ヘルパーを使用するルートでヘッダを変更するミドルウェア ( CORS など) を使用している場合、イミュータブルなヘッドを変更できないというエラーが発生する可能性があります。 これは、 `upgradeWebSocket()` が内部的にヘッダを変更するためです。 なので、 WebSocket ヘルパーとミドルウェアを同時に使用している場合は注意してください。 ::: ## RPC モード WebSocket ヘルパーで定義されたハンドラーは、 RPC モードをサポートしています。 ```ts // server.ts const wsApp = app.get( '/ws', upgradeWebSocket((c) => { //... }) ) export type WebSocketApp = typeof wsApp // client.ts const client = hc('http://localhost:8787') const socket = client.ws.$ws() // A WebSocket object for a client ``` ## 例 WebSocket を使った例: ### サーバーとクライアント ```ts // server.ts import { Hono } from 'hono' import { upgradeWebSocket } from '@hono/cloudflare-workers' const app = new Hono().get( '/ws', upgradeWebSocket(() => { return { onMessage: (event) => { console.log(event.data) }, } }) ) export default app ``` ```ts // client.ts import { hc } from 'hono/client' import type app from './server' const client = hc('http://localhost:8787') const ws = client.ws.$ws(0) ws.addEventListener('open', () => { setInterval(() => { ws.send(new Date().toString()) }, 1000) }) ``` ### Bun で JSX を使用する場合 ```tsx import { Hono } from 'hono' import { upgradeWebSocket, websocket } from '@hono/bun' import { html } from 'hono/html' const app = new Hono() app.get('/', (c) => { return c.html(
{html` `} ) }) const ws = app.get( '/ws', upgradeWebSocket((c) => { let intervalId return { onOpen(_event, ws) { intervalId = setInterval(() => { ws.send(new Date().toString()) }, 200) }, onClose() { clearInterval(intervalId) }, } }) ) export default { fetch: app.fetch, websocket, } ``` ### Node.js ```ts import { serve, upgradeWebSocket } from '@hono/node-server' import { Hono } from 'hono' import { WebSocketServer } from 'ws' const app = new Hono() app.get( '/ws', upgradeWebSocket(() => ({ onMessage(event, ws) { ws.send(event.data) }, })) ) const wss = new WebSocketServer({ noServer: true }) serve({ fetch: app.fetch, websocket: { server: wss }, }) ``` # ベストプラクティス Hono はとても柔軟です。 あなたの好きなようにアプリを書くことが出来ます。 しかし、従ったほうが良いベストプラクティスもあります。 ## できるだけ "コントローラー" を作らないでください 極力、 "Ruby on Rails のようなコントローラー" は作るべきではありません。 ```ts // 🙁 // A RoR-like Controller const booksList = (c: Context) => { return c.json('list books') } app.get('/books', booksList) ``` 問題は型に関係しています。 例えば、複雑なジェネリクスを書かない限り、コントローラーではパスパラメータを推論できません。 ```ts // 🙁 // A RoR-like Controller const bookPermalink = (c: Context) => { const id = c.req.param('id') // Can't infer the path param return c.json(`get ${id}`) } ``` そのため、 RoR-like なコントローラーを作る必要はなく、パス定義の直後にハンドラを書くべきです。 ```ts // 😃 app.get('/books/:id', (c) => { const id = c.req.param('id') // Can infer the path param return c.json(`get ${id}`) }) ``` ## `hono/factory` の `factory.createHandlers()` それでも RoR-like なコントローラーを作りたい場合、 [`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) ``` ## 大きなアプリケーションを作る "Ruby on Rails のようなコントローラー" を作ること無く大きなアプリケーションを作るには `app.route()` を使います。 アプリケーションに `/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') // Typed correctly ``` 詳しくは、 [RPC のページ](/docs/guides/rpc#using-rpc-with-larger-applications) を御覧ください。 ## HEAD リクエストのベストプラクティス ### Hono の HEAD 処理を理解する Hono は、自動的に HEAD リクエストを GET リクエストに変更し、レスポンスボディを除去することで HEAD リクエストを処理します。 この振る舞いはフレームワークのディスパッチ層に組み込まれており、ルートマッチングが起きる前に行われます。 ### ✅ 動作する: HEAD リクエスト用に GET ルートを使用する ```typescript // GOOD: この GET ルートは自動的に HEAD リクエストを処理します app.get('/api/users', async (c) => { const users = await getUsers() c.header('X-Total-Count', users.length.toString()) return c.json(users) }) // HEAD /api/users リクエストは次の内容を返します: // - GET と同じヘッダ (X-Total-Count を含む) // - Status 200 // - No body (null) ``` ### ✅ 動作する: HEAD 固有のロジック用にミドルウェアを使用する ```typescript // GOOD: HEAD が異なる振る舞いが必要な際にミドルウェアを使用する app.use('/api/resource', async (c, next) => { await next() // ハンドラの後処理で HEAD 固有のヘッダを追加 if (c.req.method === 'HEAD') { c.header('X-HEAD-Processed', 'true') // HEAD 用に過度なボディの内容を計算しない c.res = new Response(null, c.res) } }) ``` ### ❌ 動作しない: HEAD 専用のハンドラを生成してみる ```typescript // BAD: これは期待通りには動作しません app.head('/api/users', (c) => { // このハンドラは決して呼ばれません c.header('X-Custom', 'value') return c.text('ignored') }) // BAD: on() を使用しても動作しません app.on('HEAD', '/api/users', (c) => { // ルートマッチングする前に GET に変換されます }) ``` ### パフォーマンスの考慮 - **多くの HEAD リクエストを期待している場合、 GET ハンドラで重い処理を避ける**: HEAD を検知し、ボディの生成をスキップするためにミドルウェアを使用します - **キャッシュヘッダは同様に動作します**: HEAD レスポンスは GET と同じキャッシュルールに従います - **ミドルウェアの互換性**: 大抵のミドルウェアは、 HEAD を処理します。 しかし、(圧縮のような) ボディを処理するミドルウェアは自動的に HEAD リクエストをスキップします ### HEAD リクエストをテストする ```typescript // 常に GET と HEAD 両方のレスポンスをテストします it('handles HEAD requests correctly', async () => { const getRes = await app.request('/api/users') const headRes = await app.request('/api/users', { method: 'HEAD' }) expect(headRes.status).toBe(getRes.status) expect(headRes.headers.get('X-Total-Count')).toBe( getRes.headers.get('X-Total-Count') ) expect(headRes.body).toBe(null) }) ``` ### 注記 - 自動的な HEAD の変換は、GET と HEAD レスポンス間でヘッダの一貫性を保証します - この振る舞いは、すべての Hono ランタイム (Cloudflare Workers, Deno, Bun, Node.js) で一貫性があります - HEAD と GET で全く異なるロジックが必要な場合、フレームワークの HEAD 処理をオーバーライドするよりもエンドポイントを分けて使用することを検討してください # 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 ``` ::: ## 一般的に使用される引数 | Argument | Description | Example | | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------ | | `--template