hono の使い方

Web framework built on Web Standards

v4.12.10/週MITWeb API
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

Hono の使い方 — Web Standards ベースの超高速Webフレームワーク

一言でいうと

Hono(炎🔥)は、Web Standards API 上に構築された超軽量・超高速なWebフレームワークです。Cloudflare Workers、Deno、Bun、AWS Lambda、Node.js など、あらゆるJavaScriptランタイムで同一コードが動作します。

どんな時に使う?

  • エッジコンピューティング環境でのAPI構築 — Cloudflare Workers や Fastly Compute など、エッジランタイム上でREST APIやBFFを構築したい場合。Express では動かない環境でも Hono なら動きます
  • マルチランタイム対応のWebアプリケーション — 開発はNode.js、本番はBunやDeno、デプロイ先はAWS Lambdaなど、ランタイムを柔軟に切り替えたいプロジェクト
  • 軽量で高速なAPIサーバー — バンドルサイズが12kB未満(hono/tiny)で依存ゼロ。コールドスタートが重要なサーバーレス環境や、パフォーマンスを最優先するAPIサーバーに最適

インストール

# npm
npm install hono

# yarn
yarn add hono

# pnpm
pnpm add hono

プロジェクトのスキャフォールディングには以下が便利です:

npm create hono@latest

基本的な使い方

最もシンプルなHonoアプリケーションの例です。

import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => {
  return c.text('Hello, Hono!')
})

app.get('/json', (c) => {
  return c.json({ message: 'Hello, Hono!', timestamp: Date.now() })
})

app.post('/posts', async (c) => {
  const body = await c.req.json<{ title: string; content: string }>()
  return c.json({ id: 1, ...body }, 201)
})

export default app

Node.js で起動する場合は @hono/node-server を使います:

import { serve } from '@hono/node-server'
import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => c.text('Hello from Node.js!'))

serve({
  fetch: app.fetch,
  port: 3000,
})

console.log('Server is running on http://localhost:3000')

よく使うAPI

1. ルーティング(HTTPメソッド & パスパラメータ)

import { Hono } from 'hono'

const app = new Hono()

// 基本的なHTTPメソッド
app.get('/users', (c) => c.json([]))
app.post('/users', (c) => c.json({ created: true }, 201))
app.put('/users/:id', (c) => c.json({ updated: true }))
app.delete('/users/:id', (c) => c.json({ deleted: true }))

// パスパラメータの取得
app.get('/users/:id', (c) => {
  const id = c.req.param('id')
  return c.json({ id })
})

// ワイルドカード
app.get('/files/*', (c) => {
  const path = c.req.path
  return c.text(`File path: ${path}`)
})

// クエリパラメータ
app.get('/search', (c) => {
  const query = c.req.query('q')
  const page = c.req.query('page') ?? '1'
  return c.json({ query, page: Number(page) })
})

2. ミドルウェア

Hono には豊富なビルトインミドルウェアが用意されています。

import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { prettyJSON } from 'hono/pretty-json'
import { bearerAuth } from 'hono/bearer-auth'
import { secureHeaders } from 'hono/secure-headers'

const app = new Hono()

// グローバルミドルウェア
app.use('*', logger())
app.use('*', secureHeaders())
app.use('*', prettyJSON())

// 特定パスにCORS
app.use('/api/*', cors({
  origin: 'https://example.com',
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
}))

// 認証が必要なルート
app.use('/admin/*', bearerAuth({ token: 'my-secret-token' }))

app.get('/admin/dashboard', (c) => {
  return c.json({ message: 'Welcome, admin!' })
})

// カスタムミドルウェア
app.use('*', async (c, next) => {
  const start = Date.now()
  await next()
  const duration = Date.now() - start
  c.header('X-Response-Time', `${duration}ms`)
})

3. ルートグループ化(app.route / app.basePath

import { Hono } from 'hono'

// サブアプリケーションとしてルートを分割
const api = new Hono()

api.get('/users', (c) => c.json({ users: [] }))
api.get('/users/:id', (c) => {
  const id = c.req.param('id')
  return c.json({ id, name: 'Taro' })
})

const posts = new Hono()
posts.get('/', (c) => c.json({ posts: [] }))
posts.post('/', async (c) => {
  const body = await c.req.json()
  return c.json(body, 201)
})

// メインアプリにマウント
const app = new Hono().basePath('/api/v1')
app.route('/users', api)
app.route('/posts', posts)

// /api/v1/users, /api/v1/posts でアクセス可能

export default app

4. バリデーション(hono/validator & Zod連携)

import { Hono } from 'hono'
import { validator } from 'hono/validator'

const app = new Hono()

// 組み込みバリデータ
app.post(
  '/posts',
  validator('json', (value, c) => {
    const { title, content } = value as { title?: string; content?: string }
    if (!title || typeof title !== 'string') {
      return c.json({ error: 'title is required' }, 400)
    }
    if (!content || typeof content !== 'string') {
      return c.json({ error: 'content is required' }, 400)
    }
    return { title, content }
  }),
  (c) => {
    const { title, content } = c.req.valid('json')
    return c.json({ id: 1, title, content }, 201)
  }
)

Zod と @hono/zod-validator を組み合わせると、より型安全なバリデーションが可能です:

import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'

const postSchema = z.object({
  title: z.string().min(1).max(100),
  content: z.string().min(1),
  tags: z.array(z.string()).optional(),
})

const app = new Hono()

app.post('/posts', zValidator('json', postSchema), (c) => {
  const data = c.req.valid('json') // 型が自動推論される
  return c.json({ id: 1, ...data }, 201)
})

@hono/zod-validator は別途 npm install @hono/zod-validator zod が必要です。

5. 型安全なRPCクライアント(hc

Hono の大きな特徴の一つが、サーバー側のルート定義からクライアント側の型を自動生成できる RPC 機能です。

// server.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'

const app = new Hono()
  .get('/api/users', (c) => {
    return c.json({ users: [{ id: 1, name: 'Taro' }] })
  })
  .post(
    '/api/users',
    zValidator('json', z.object({ name: z.string() })),
    (c) => {
      const { name } = c.req.valid('json')
      return c.json({ id: 2, name }, 201)
    }
  )

export type AppType = typeof app
export default app
// client.ts
import { hc } from 'hono/client'
import type { AppType } from './server'

const client = hc<AppType>('http://localhost:3000')

// 完全に型安全なAPIコール
const res = await client.api.users.$get()
const data = await res.json() // { users: { id: number; name: string }[] }

const createRes = await client.api.users.$post({
  json: { name: 'Hanako' }, // 型チェックされる
})

類似パッケージとの比較

特徴HonoExpressFastifyElysia
バンドルサイズ~12kB (tiny)~200kB~400kB~50kB
TypeScriptファーストクラス型定義別途良好ファーストクラス
Web Standards準拠部分的
マルチランタイム✅ (全主要ランタイム)Node.js中心Node.js中心Bun専用
エッジ対応
ミドルウェア豊富なビルトイン巨大なエコシステムプラグイン方式独自エコシステム
RPC クライアント✅ 組み込み✅ (Eden Treaty)
依存関係ゼロ~30~15~5

選定の目安:

  • Express からの移行・エッジ対応が必要 → Hono
  • Node.js 専用で最大スループット → Fastify
  • Bun 専用で最高のDX → Elysia
  • 既存のExpressエコシステムを活用 → Express

注意点・Tips

ランタイムごとのエントリポイント

Hono はマルチランタイム対応ですが、起動方法はランタイムによって異なります。

// Cloudflare Workers / Bun / Deno — export default でOK
export default app

// Node.js — @hono/node-server が必要
import { serve } from '@hono/node-server'
serve({ fetch: app.fetch, port: 3000 })

// AWS Lambda — @hono/aws-lambda が必要
import { handle } from 'hono/aws-lambda'
export const handler = handle(app)

c.req.json() は一度しか読めない

Request body は Web Standards の仕様上、一度しか消費できません。ミドルウェアで body を読んだ場合、ハンドラで再度読むことはできません。バリデーションミドルウェアを使えば c.req.valid() で安全に取得できます。

ルート定義のチェーンを忘れない(RPC使用時)

RPC クライアントの型推論を正しく機能させるには、ルート定義をメソッドチェーンで書く必要があります。

// ✅ 正しい — チェーンで型が伝播する
const app = new Hono()
  .get('/api/users', (c) => c.json([]))
  .post('/api/users', (c) => c.json({}, 201))

// ❌ 型が失われる
const app = new Hono()
app.get('/api/users', (c) => c.json([]))
app.post('/api/users', (c) => c.json({}, 201))

hono/tiny プリセットでさらに軽量化

バンドルサイズを極限まで削りたい場合は hono/tiny を使えます。ルーターが PatternRouter に切り替わり、RegExpRouter ほどの速度は出ませんが、サイズは大幅に削減されます。

import { Hono } from 'hono/tiny'

環境変数・Bindings の型定義

Cloudflare Workers の Bindings や環境変数に型を付けるには、ジェネリクスを活用します。

type Bindings = {
  DATABASE_URL: string
  MY_KV: KVNamespace
}

const app = new Hono<{ Bindings: Bindings }>()

app.get('/', (c) => {
  const dbUrl = c.env.DATABASE_URL // 型安全
  return c.text(dbUrl)
})

まとめ

Hono は「Web Standards に準拠し、どこでも動く」という明確な設計思想を持つ、モダンなWebフレームワークです。ゼロ依存・超軽量でありながら、ミドルウェア、バリデーション、型安全なRPCクライアントまで備えており、エッジコンピューティング時代のAPIサーバー構築において第一選択肢となるフレームワークと言えます。Express に慣れた開発者であれば、ほぼ学習コストなく移行でき、TypeScript との親和性の高さに驚くはずです。

比較記事