mcpField
H
hono

hono

github.com/midudev開発7.0k57 回インストール2 週間前に更新
リポジトリ

hono について

Hono の Web アプリを構築、検査するコーディングエージェント向けのスキルです。Hono CLI を使ったルート確認、リクエストテスト、スナップショット、バッチ検証の手順も含みます。

主な機能

  1. 01Hono のルーティングとコンテキスト API の参照
  2. 02ミドルウェアとリクエスト検証の解説
  3. 03Hono CLI によるルート確認
  4. 04サーバーを起動せずにリクエストをテスト
  5. 05スナップショットとバッチによる回帰検証

活用シーン

  1. 01Hono で Web アプリや API を構築
  2. 02アプリのルート確認とテストリクエスト送信
  3. 03既存レスポンスを記録してルート変更を検証
  4. 04状態を保つリクエストフローを一括テスト

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/midudev/autoskills --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(cf CLI)と 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 以降が必要です。

コメント