hono について
hono は、Hono アプリ開発向けの AI スキルです。API リファレンスを含み、Hono CLI によるアプリの確認とテストを案内します。
主な機能
- 01Hono API リファレンスを内蔵
- 02最新ドキュメントの取得方法を案内
- 03CLI でルートを確認
- 04サーバー不要のリクエスト・一括テスト
- 05レスポンスの記録と再テスト
活用シーン
- 01Hono のルートの作成・変更
- 02サーバーを起動せずにエンドポイントをテスト
- 03API の動作を記録して変更を検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ honojs/ skills --skill honoインストール後、Claude Code で/honoを入力して読み込まれたか確認します。
詳細
Hono スキル
Hono のウェブアプリケーションを構築します。このスキルは、AI 向けに API の知識をインラインで提供します。Hono CLI を使ってアプリを調査、テストしてください。
最新のドキュメント
このインラインリファレンスにない詳細については、https://hono.dev から最新のドキュメントを取得してください。https://hono.dev/llms.txt からドキュメントページのインデックスを取得し、Accept: text/markdown ヘッダーを付けてページを取得すると、Markdown で受け取れます。
curl -H "Accept: text/markdown" https://hono.dev/docs/helpers/cookie
Hono CLI
使い捨てのスクリプトや開発サーバーではなく、Hono CLI で検証してください。これは開発用依存関係です(create-hono から作成したプロジェクトにはすでに含まれています)。app.request() を使ってアプリをプロセス内で読み込み、JSON を出力します。見つからない場合はインストールしてから、ヘルプを確認してください。
npm install -D @hono/cli@next
npx hono --help
冒頭の注記に従ってください。JSON 出力の契約とワークフローについて説明しています。npx hono <command> --help には、各コマンド(routes、request、batch、snapshot、benchmark、ssg、init)の例と注記があります。
注記:
-
npx hono routesはルートを一覧表示し、npx hono request /pathは1つのリクエストを送信します。サーバーは不要です。アプリがデフォルトのパス(src/index.ts)にない場合は、すべてのコマンドにファイルを渡してください:npx hono request / src/app.ts。CLI 引数に認証情報を直接渡さないでください。機密情報には環境変数を使用してください。 -
Cloudflare バインディング(KV、D1、R2、vars)は自動的に
c.envに渡されます。サーバーもフラグも不要です。cloudflare.config.ts(cfCLI)と Vite 設定があるプロジェクトでは、request、batch、snapshotはデフォルトで Vite 開発サーバー経由で実行され、c.envにはローカルのバインディングが設定されます。src/index.*がない Vite プロジェクト(Vite プラグインで構築されたアプリ)でも、requestとbatchはデフォルトで Vite を使用します(snapshotには引き続きファイルが必要です)。また、--runtime viteで明示的に選択できます。wrangler 設定があるプロジェクトでは、アプリは実際のローカルバインディングを読み込んで Node.js 上で実行されます。そこで Workers の完全なランタイムを使うには、--runtime workerdを追加してください(ファイル引数は渡さず、エントリポイントは wrangler 設定のmainです)。--trace、--watch、およびファイル引数を指定すると、アプリは Node.js 上で実行されます。この場合、cfプロジェクトのc.envは空です。 -
routesとsnapshotは、cloudflare:workersをインポートするアプリ(Durable Objects)も読み込みます。ルートの読み込み中は、インポートがスタブに置き換えられます。 -
既存のディレクトリに Hono アプリを追加するには、
npx hono init --template cloudflare-workers(またはnodejs、bun、denoなど。--templateを指定しない場合は、ディレクトリ内のファイルから検出されます)を実行してから、依存関係をインストールしてください。create-honoのテンプレートがコピーされ、既存のファイルが上書きされることはありません。空のディレクトリでは、npm create hono@nextを実行しても同様の処理が行われ、プロンプトが表示されます。 -
複数のリクエストを実行する場合や、状態を保持するフロー(POST してから、返された ID を使用するなど)では、1回の
hono batch -呼び出しで実行してください。1行につき JSON オブジェクトを1つ記述します。値をsaveし、後続のステップで{{id}}として使用します。ステップ間で同じアプリインスタンスを共有します。各ステップでexpectを使って期待するステータスと本文を宣言してください(本文はディープな部分一致です)。概要に"failed": 0と表示されるまで繰り返します。npx hono batch - <<'EOF' {"method":"POST","path":"/users","body":{"name":"Alice"},"save":{"id":".id"},"expect":{"status":201}} {"path":"/users/{{id}}","expect":{"status":200,"body":{"name":"Alice"}}} EOF -
既存のルートを変更する前に、現在の動作を記録してください:
npx hono snapshotは、バッチ JSONL の行として結果を出力します(実際のレスポンスがexpectになります)。その行を保存し、変更を加えた後、"failed": 0になるまでnpx hono batch -で再実行してください。 -
大規模な API では、
npx hono snapshot --status-onlyとnpx hono batch - --compactを使用してください。同じ手順で、出力を大幅に小さくできます。失敗したステップにはdiffが含まれます。そこに示された内容を修正してください。
Hono API リファレンス
アプリコンストラクター
import { Hono } from 'hono'
const app = new Hono()
// With TypeScript generics
type AppEnv = {
Bindings: Env // generated from the bindings config — see "Environment (Cloudflare Workers)"
Variables: { user: User }
}
const app = new Hono<AppEnv>()
ルーティングメソッド
app.get('/path', handler)
app.post('/path', handler)
app.put('/path', handler)
app.delete('/path', handler)
app.patch('/path', handler)
app.options('/path', handler)
app.all('/path', handler) // all HTTP methods
app.on('PURGE', '/path', handler) // custom method
app.on(['PUT', 'DELETE'], '/path', handler) // multiple methods
ルーティングパターン
// Path parameters
app.get('/user/:name', (c) => {
const name = c.req.param('name')
return c.json({ name })
})
// Multiple params
app.get('/posts/:id/comments/:commentId', (c) => {
const { id, commentId } = c.req.param()
})
// Optional parameters
app.get('/api/animal/:type?', (c) => c.text('Animal!'))
// Wildcards
app.get('/wild/*/card', (c) => c.text('Wildcard'))
// Regexp constraints
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
const { date, title } = c.req.param()
})
// Chained routes
app
.get('/endpoint', (c) => c.text('GET'))
.post((c) => c.text('POST'))
.delete((c) => c.text('DELETE'))
ルートのグループ化
// Using route()
const api = new Hono()
api.get('/users', (c) => c.json([]))
const app = new Hono()
app.route('/api', api) // mounts at /api/users
// Using basePath()
const app = new Hono().basePath('/api')
app.get('/users', (c) => c.json([])) // GET /api/users
エラー処理
app.notFound((c) => c.json({ message: 'Not Found' }, 404))
app.onError((err, c) => {
console.error(err)
return c.json({ message: 'Internal Server Error' }, 500)
})
コンテキスト(c)
レスポンスメソッド
c.text('Hello') // text/plain
c.json({ message: 'Hello' }) // application/json
c.html('<h1>Hello</h1>') // text/html
c.redirect('/new-path') // 302 redirect
c.redirect('/new-path', 301) // 301 redirect
c.body('raw body', 200, headers) // raw response
c.notFound() // 404 response
ヘッダーとステータス
c.status(201)
c.header('X-Custom', 'value')
c.header('Cache-Control', 'no-store')
変数(リクエスト単位のデータ)
// In middleware
c.set('user', { id: 1, name: 'Alice' })
// In handler
const user = c.get('user')
// or
const user = c.var.user
環境(Cloudflare Workers)
Bindings 型を手書きしてはいけません。生成し、バインディングを変更するたびに再実行してください。プロジェクトがどのツールを使っているかは、ファイルから確認できます。
cf CLI プロジェクト(cloudflare.config.ts。現在、create-hono が cloudflare-workers 向けに生成する形式です):バインディングは、cf/config の bindings ヘルパーを使って cloudflare.config.ts で宣言します。ローカルバインディングには ID は不要です。ユーザーから依頼されない限り、Cloudflare リソース(KV、D1、R2)を作成したり、ID を設定したりしないでください。その後、npm run typecheck(= cf workers types && tsc。.cloudflare/types に書き込みます)を実行し、生成された Env を使用します。
// cloudflare.config.ts
import { bindings, defineConfig } from 'cf/config'
import * as entrypoint from './src/index.ts' with { type: 'cf-worker' }
export default defineConfig({
worker: {
name: 'my-app',
compatibilityDate: '2026-10-06',
entrypoint,
env: {
MY_KV: bindings.kv(),
MY_VAR: bindings.text('my-variable'),
},
},
})
const app = new Hono<{ Bindings: Env }>()
スクリプトは cf dev、cf build、cf deploy です。この種のプロジェクトには wrangler 設定ファイルがありません。追加しないでください。また、Hono プロジェクトで cf init を実行しないでください(Hono では自動設定が停止します)。
wrangler プロジェクト(wrangler.jsonc または wrangler.toml):バインディングは wrangler 設定ファイルに記述します。npm run cf-typegen(= wrangler types --env-interface CloudflareBindings)を実行すると、Workers のランタイム型とともに CloudflareBindings を宣言する worker-configuration.d.ts が生成されます。cf-typegen スクリプトがない場合は、npx wrangler types を実行し、代わりに宣言されるインターフェース(Env。Cloudflare.Env としても利用できます)を使用してください。@cloudflare/workers-types はどちらの方法でも不要です。インストールしないでください。
const app = new Hono<{ Bindings: CloudflareBindings }>()
app.get('/', async (c) => {
const value = await c.env.MY_KV.get('key')
const db = c.env.MY_DB
c.executionCtx.waitUntil(promise)
})
レンダラー
app.use(async (c, next) => {
c.setRenderer((content) =>
c.html(
<html><body>{content}</body></html>
)
)
await next()
})
app.get('/', (c) => c.render(<h1>Hello</h1>))
HonoRequest(c.req)
c.req.param('id') // path parameter
c.req.param() // all path params as object
c.req.query('page') // query string parameter
c.req.query() // all query params; values are string | undefined
c.req.queries('tags') // multiple values: ?tags=A&tags=B → ['A', 'B']
c.req.header('Authorization') // request header
c.req.header() // all headers (keys are lowercase)
// Body parsing
await c.req.json<{ title: string }>() // parse JSON body (unknown without the type argument)
await c.req.text() // parse text body
await c.req.formData() // parse as FormData
await c.req.parseBody() // parse multipart/form-data or urlencoded
await c.req.arrayBuffer() // parse as ArrayBuffer
await c.req.blob() // parse as Blob
// Validated data (used with validator middleware)
c.req.valid('json')
c.req.valid('query')
c.req.valid('form')
c.req.valid('param')
// Properties
c.req.url // full URL string
c.req.path // pathname
c.req.method // HTTP method
c.req.raw // underlying Request object
ミドルウェア
組み込みミドルウェアの使用
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { basicAuth } from 'hono/basic-auth'
import { prettyJSON } from 'hono/pretty-json'
import { secureHeaders } from 'hono/secure-headers'
import { etag } from 'hono/etag'
import { compress } from 'hono/compress'
import { poweredBy } from 'hono/powered-by'
import { timing } from 'hono/timing'
import { cache } from 'hono/cache'
import { bearerAuth } from 'hono/bearer-auth'
import { jwt } from 'hono/jwt'
import { jwk } from 'hono/jwk'
import { csrf } from 'hono/csrf'
import { ipRestriction } from 'hono/ip-restriction'
import { bodyLimit } from 'hono/body-limit'
import { timeout } from 'hono/timeout'
import { requestId } from 'hono/request-id'
import { methodOverride } from 'hono/method-override'
import { methodNotAllowed } from 'hono/method-not-allowed'
import { languageDetector } from 'hono/language'
import { some, every, except } from 'hono/combine'
import { contextStorage, getContext } from 'hono/context-storage'
import { trailingSlash, trimTrailingSlash } from 'hono/trailing-slash'
// Registration
app.use(logger()) // all routes
app.use('/api/*', cors()) // specific path
app.post('/api/*', basicAuth({ username: 'admin', password: 'secret' }))
カスタムミドルウェア
// Inline
app.use(async (c, next) => {
const start = Date.now()
await next()
const elapsed = Date.now() - start
c.res.headers.set('X-Response-Time', `${elapsed}ms`)
})
// Reusable with createMiddleware
import { createMiddleware } from 'hono/factory'
const auth = createMiddleware(async (c, next) => {
const token = c.req.header('Authorization')
if (!token) return c.json({ error: 'Unauthorized' }, 401)
await next()
})
app.use('/api/*', auth)
ミドルウェアの実行順序
ミドルウェアは登録順に実行されます。await next() は次のミドルウェア/ハンドラーを呼び出し、next() の後のコードは戻る途中で実行されます。
Request → mw1 before → mw2 before → handler → mw2 after → mw1 after → Response
app.use(async (c, next) => {
// before handler
await next()
// after handler
})
バリデーション
バリデーション対象:json、form、query、header、param、cookie。
Zod バリデーター
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const schema = z.object({
title: z.string().min(1),
body: z.string()
})
app.post('/posts', zValidator('json', schema), (c) => {
const data = c.req.valid('json') // fully typed
return c.json(data, 201)
})
Valibot/Standard Schema バリデーター
import { sValidator } from '@hono/standard-validator'
import * as v from 'valibot'
const schema = v.object({ name: v.string(), age: v.number() })
app.post('/users', sValidator('json', schema), (c) => {
const data = c.req.valid('json')
return c.json(data, 201)
})
JSX
hono-jsx スキルを使って UI を構築してください(jsxRenderer を使ったレイアウト、Vite、クライアントサイドコード)。このセクションでは構文のみを説明します。
セットアップ
tsconfig.json で設定します。
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}
または、プラグマを使用します:/** @jsxImportSource hono/jsx */
重要: JSX を使うファイルの拡張子は .tsx にする必要があります。.ts を .tsx に変更しないと、コンパイラーでエラーになります。
コンポーネント
import type { PropsWithChildren } from 'hono/jsx'
const Layout = (props: PropsWithChildren) => (
<html>
<head>
<title>My App</title>
</head>
<body>{props.children}</body>
</html>
)
const UserCard = ({ name }: { name: string }) => (
<div class="card">
<h2>{name}</h2>
</div>
)
app.get('/', (c) => {
return c.html(
<Layout>
<UserCard name="Alice" />
</Layout>
)
})
jsxRenderer ミドルウェア
レイアウトには jsxRenderer ミドルウェアを使用してください。詳細は https://hono.dev/docs/middleware/builtin/jsx-renderer を参照してください。
非同期コンポーネント
const UserList = async () => {
const users = await fetchUsers()
return (
<ul>
{users.map((u) => (
<li>{u.name}</li>
))}
</ul>
)
}
フラグメント
const Items = () => (
<>
<li>Item 1</li>
<li>Item 2</li>
</>
)
ストリーミング
import { stream, streamText, streamSSE } from 'hono/streaming'
// Basic stream
app.get('/stream', (c) => {
return stream(c, async (stream) => {
stream.onAbort(() => console.log('Aborted'))
await stream.write(new Uint8Array([0x48, 0x65]))
await stream.pipe(readableStream)
})
})
// Text stream
app.get('/stream-text', (c) => {
return streamText(c, async (stream) => {
await stream.writeln('Hello')
await stream.sleep(1000)
await stream.write('World')
})
})
// Server-Sent Events
app.get('/sse', (c) => {
return streamSSE(c, async (stream) => {
let id = 0
while (true) {
await stream.writeSSE({
data: JSON.stringify({ time: new Date().toISOString() }),
event: 'time-update',
id: String(id++)
})
await stream.sleep(1000)
}
})
})
app.request() を使ったテスト
HTTP サーバーを起動せずにエンドポイントをテストできます。
// GET
const res = await app.request('/posts')
expect(res.status).toBe(200)
expect(await res.json()).toEqual({ posts: [] })
// POST with JSON
const res = await app.request('/posts', {
method: 'POST',
body: JSON.stringify({ title: 'Hello' }),
headers: { 'Content-Type': 'application/json' }
})
// POST with FormData
const formData = new FormData()
formData.append('name', 'Alice')
const res = await app.request('/users', { method: 'POST', body: formData })
// With mock env (Cloudflare Workers bindings)
const res = await app.request('/api/data', {}, { KV: mockKV, DATABASE: mockDB })
// Using Request object
const req = new Request('http://localhost/api', { method: 'DELETE' })
const res = await app.request(req)
Hono クライアント(RPC)
サーバーとクライアントで型を共有する、型安全な API クライアントです。
重要:型推論を機能させるには、ルートをチェーンしなければなりません。チェーンしない場合、クライアントはルート型を推論できません。
// Server: routes MUST be chained to preserve types
const route = app
.post('/posts', zValidator('json', schema), (c) => {
return c.json({ ok: true }, 201)
})
.get('/posts', (c) => {
return c.json({ posts: [] })
})
export type AppType = typeof route
// Client: use hc() with the exported type
import { hc } from 'hono/client'
import type { AppType } from './server'
const client = hc<AppType>('http://localhost:8787/')
const res = await client.posts.$post({ json: { title: 'Hello' } })
const data = await res.json() // fully typed
型ユーティリティ:
import type { InferRequestType, InferResponseType } from 'hono/client'
type ReqType = InferRequestType<typeof client.posts.$post>
type ResType = InferResponseType<typeof client.posts.$post, 200>
ヘルパー
ヘルパーは、hono/<helper-name> からインポートするユーティリティ関数です。
import { getConnInfo } from 'hono/conninfo'
import { getCookie, setCookie, deleteCookie } from 'hono/cookie'
import { css, Style } from 'hono/css'
import { createFactory } from 'hono/factory'
import { html, raw } from 'hono/html'
import { stream, streamText, streamSSE } from 'hono/streaming'
import { testClient } from 'hono/testing'
import { upgradeWebSocket } from '@hono/cloudflare-workers' // or another @hono/<runtime> adapter package
利用可能なヘルパー:Accepts、Adapter、ConnInfo、Cookie、css、Dev、Factory、html、JWT、Proxy、Route、SSG、Streaming、Testing、WebSocket。
詳細は https://hono.dev/docs/helpers/<helper-name> を参照してください(Accept: text/markdown を指定して取得してください)。
Factory
createFactory を使ってアプリの Env 型(上記の AppEnv)を一度定義し、アプリ、ミドルウェア、ハンドラー間で共有します。
import { createFactory } from 'hono/factory'
const factory = createFactory<AppEnv>()
// Create app (AppEnv is inherited)
const app = factory.createApp()
// Create middleware (AppEnv is inherited, no need to pass generics)
const mw = factory.createMiddleware(async (c, next) => {
await next()
})
// Create handlers separately (preserves type inference)
const handlers = factory.createHandlers(logger(), (c) => c.json({ message: 'Hello' }))
app.get('/api', ...handlers)
ベストプラクティス
- パスパラメーターを適切に型推論させるため、ハンドラーはルート定義内にインラインで記述してください。
- 大規模なアプリを機能ごとに整理するには
app.route()を使用してください。Rails スタイルのコントローラーは使わないでください。 createFactory()を使って、Env 型をアプリ、ミドルウェア、ハンドラー間で共有してください。c.set()/c.get()を使って、ミドルウェアとハンドラー間でデータを受け渡してください。- 複数のリクエスト部分(param + query + json)を検証するには、バリデーターをチェーンしてください。
- RPC 用にアプリの型をエクスポートします:
export type AppType = typeof routes - テストには
app.request()を使用してください。サーバーの起動は不要です。
アダプター
Hono は複数のランタイムで動作します。デフォルトのエクスポートは Cloudflare Workers、Deno、Bun で使用できます。Node.js では、Node アダプターを使用してください。
// Cloudflare Workers / Deno / Bun
export default app
// Node.js
import { serve } from '@hono/node-server'
serve(app)
ランタイム固有のヘルパー(serveStatic、upgradeWebSocket、getConnInfo、Lambda ハンドラー)は、Hono v5 以降、個別のパッケージに含まれています:@hono/cloudflare-workers、@hono/bun、@hono/deno、@hono/aws-lambda、@hono/lambda-edge、@hono/netlify、@hono/vercel、@hono/service-worker。使用するランタイムに対応したパッケージをインストールし、そこからインポートしてください。hono/<runtime> は v4 にのみ存在します。hono/adapter(env()、getRuntimeKey())は引き続き hono に含まれています。Hono v5 は ESM 専用であり、Node.js 22.12 以降が必要です。