株式会社オブライト
Software Dev2026-04-08

Hono実装ガイド — Zodバリデーション・OpenAPI・JWT認証を組み合わせた本番品質API構築【2026年版】

HonoでZodバリデーション・JWT認証・OpenAPI自動生成を統合した本番品質APIの実装方法を体系的に解説。CORS、レート制限、エラーハンドリング、ロギング、Swagger UI統合まで網羅。


本番品質APIとは何か? HonoでZod・JWT・OpenAPIを統合する方法

本番品質APIに最低限必要な要素は「バリデーション・認証・OpenAPI仕様・エラーハンドリング・ロギング・レート制限」の6つです。Honoはこれらをすべて軽量かつ型安全に実装できるフレームワークです。本記事では各要素の実装方法を体系的に解説します。

本番APIアーキテクチャ全体像

Loading diagram...

Zodバリデーション — @hono/zod-validatorの使い方

`@hono/zod-validator`を使うと、リクエストのJSON・クエリパラメータ・ヘッダー・Cookieなどを型安全に検証できます。バリデーション失敗時は自動的に400エラーを返し、成功時は`c.req.valid()`で型付きデータにアクセスできます。

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

const app = new Hono()

const userSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().int().min(0).max(150)
})

app.post('/users', zValidator('json', userSchema), (c) => {
  const user = c.req.valid('json') // 型: { name: string; email: string; age: number }
  return c.json({ id: 1, ...user }, 201)
})

バリデーション対象一覧

Honoでバリデーション可能なリクエスト要素は以下の通りです。

ターゲット第1引数取得方法
JSONボディ`'json'``c.req.valid('json')`
フォームデータ`'form'``c.req.valid('form')`
クエリパラメータ`'query'``c.req.valid('query')`
パスパラメータ`'param'``c.req.valid('param')`
HTTPヘッダー`'header'``c.req.valid('header')`
Cookie`'cookie'``c.req.valid('cookie')`

バリデーション失敗時のカスタムエラーレスポンス

ts
app.post(
  '/users',
  zValidator('json', userSchema, (result, c) => {
    if (!result.success) {
      return c.json(
        {
          error: 'Validation failed',
          details: result.error.flatten()
        },
        422
      )
    }
  }),
  (c) => {
    const user = c.req.valid('json')
    return c.json({ id: 1, ...user }, 201)
  }
)

JWT認証ミドルウェアの実装

Honoには`hono/jwt`が標準で含まれており、追加パッケージなしでJWT認証を実装できます。`jwt()`ミドルウェアを特定パスに適用し、検証済みペイロードは`c.get('jwtPayload')`で取得します。

ts
import { Hono } from 'hono'
import { jwt } from 'hono/jwt'

const app = new Hono()

// /auth/* 以下のルートにJWT認証を適用
app.use('/auth/*', jwt({
  secret: process.env.JWT_SECRET!,
  alg: 'HS256'
}))

app.get('/auth/me', (c) => {
  const payload = c.get('jwtPayload')
  return c.json(payload)
})

JWTトークン発行(ログインエンドポイント)

ts
import { sign } from 'hono/jwt'

app.post('/login', zValidator('json', z.object({
  email: z.string().email(),
  password: z.string().min(8)
})), async (c) => {
  const { email, password } = c.req.valid('json')

  // DB照合(擬似コード)
  const user = await db.findUserByEmail(email)
  if (!user || !(await verifyPassword(password, user.passwordHash))) {
    return c.json({ error: 'Invalid credentials' }, 401)
  }

  const token = await sign(
    {
      sub: user.id,
      email: user.email,
      exp: Math.floor(Date.now() / 1000) + 60 * 60 // 1時間
    },
    process.env.JWT_SECRET!,
    'HS256'
  )

  return c.json({ token })
})

JWT対応アルゴリズム一覧

Honoが対応するJWT署名アルゴリズムは以下の通りです。

アルゴリズム種別推奨用途
HS256HMAC内部API、シンプルな認証
HS384HMACHS256より高セキュリティ
HS512HMAC最高強度HMAC
RS256RSA公開鍵配布が必要な場合
RS384RSARS256より高セキュリティ
RS512RSA最高強度RSA
ES256ECDSA短い鍵で高セキュリティ
EdDSAEd25519最新推奨、高速・小サイズ

その他の認証方式 — Bearer Auth・Basic Auth

JWTのほかに、HonoはBearer AuthとBasic Authも標準サポートしています。

認証方式インポート主な用途
JWT`hono/jwt`ユーザー認証、SPA/モバイル
Bearer Auth`hono/bearer-auth`API Keyによる認証
Basic Auth`hono/basic-auth`管理画面、開発環境
ts
import { bearerAuth } from 'hono/bearer-auth'
import { basicAuth } from 'hono/basic-auth'

// Bearer Auth(APIキー)
app.use('/api/*', bearerAuth({ token: process.env.API_KEY! }))

// Basic Auth
app.use('/admin/*', basicAuth({
  username: 'admin',
  password: process.env.ADMIN_PASSWORD!
}))

@hono/zod-openapi — OpenAPI仕様の自動生成

`@hono/zod-openapi`を使うと、Zodスキーマから直接OpenAPI 3.0仕様を自動生成できます。`OpenAPIHono`クラスと`createRoute()`を組み合わせることで、コードとドキュメントを常に同期した状態に保てます。

ts
import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi'

const app = new OpenAPIHono()

const UserCreateSchema = z.object({
  name: z.string().min(1).openapi({ example: 'Taro Yamada' }),
  email: z.string().email().openapi({ example: 'taro@example.com' })
})

const UserResponseSchema = z.object({
  id: z.number().openapi({ example: 1 }),
  name: z.string(),
  email: z.string()
})

const createUserRoute = createRoute({
  method: 'post',
  path: '/users',
  tags: ['Users'],
  request: {
    body: {
      content: {
        'application/json': { schema: UserCreateSchema }
      }
    }
  },
  responses: {
    201: {
      content: { 'application/json': { schema: UserResponseSchema } },
      description: 'User created successfully'
    },
    422: {
      description: 'Validation error'
    }
  }
})

app.openapi(createUserRoute, (c) => {
  const { name, email } = c.req.valid('json')
  return c.json({ id: 1, name, email }, 201)
})

// OpenAPI仕様JSONエンドポイント
app.doc('/openapi.json', {
  openapi: '3.0.0',
  info: { title: 'My Production API', version: '1.0.0' }
})

Swagger UI統合 — @hono/swagger-ui

ts
import { swaggerUI } from '@hono/swagger-ui'

// Swagger UIを /ui で提供
app.get('/ui', swaggerUI({ url: '/openapi.json' }))

// 開発環境のみUIを有効化する例
if (process.env.NODE_ENV !== 'production') {
  app.get('/ui', swaggerUI({ url: '/openapi.json' }))
}

CORS・レート制限・ロギングの設定

Loading diagram...
ts
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { rateLimiter } from 'hono-rate-limiter'

const app = new Hono()

// ロギング
app.use('*', logger())

// CORS設定
app.use('/api/*', cors({
  origin: ['https://example.com', 'https://app.example.com'],
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowHeaders: ['Authorization', 'Content-Type'],
  maxAge: 86400
}))

// レート制限(1分間に60リクエスト)
app.use('/api/*', rateLimiter({
  windowMs: 60 * 1000,
  limit: 60,
  keyGenerator: (c) => c.req.header('x-forwarded-for') ?? 'unknown'
}))

グローバルエラーハンドリング — HTTPException

ts
import { HTTPException } from 'hono/http-exception'

// カスタムエラーをスロー
app.get('/resource/:id', async (c) => {
  const resource = await db.find(c.req.param('id'))
  if (!resource) {
    throw new HTTPException(404, { message: 'Resource not found' })
  }
  return c.json(resource)
})

// グローバルエラーハンドラー
app.onError((err, c) => {
  if (err instanceof HTTPException) {
    return c.json(
      { error: err.message, status: err.status },
      err.status
    )
  }
  console.error('Unexpected error:', err)
  return c.json({ error: 'Internal Server Error' }, 500)
})

本番デプロイ前チェックリスト

本番環境へデプロイする前に以下の項目をすべて確認してください。

カテゴリチェック項目優先度
セキュリティJWT_SECRETが十分な長さ(32文字以上)か必須
セキュリティCORS originが本番URLのみに制限されているか必須
セキュリティ機密情報が環境変数で管理されているか必須
バリデーション全エンドポイントにzValidatorが適用されているか必須
エラー処理onErrorハンドラーが設定されているか必須
エラー処理スタックトレースが本番レスポンスに含まれていないか必須
パフォーマンスレート制限が適切に設定されているか推奨
パフォーマンス不要なミドルウェアが無効化されているか推奨
ドキュメントOpenAPI仕様が最新の状態か推奨
ドキュメントSwagger UIが本番環境で無効化されているか推奨
テスト主要エンドポイントの統合テストが通過しているか必須
ロギング構造化ログが出力されているか推奨

よくある質問(FAQ)

Q1. `@hono/zod-validator`と`@hono/zod-openapi`の違いは? `@hono/zod-validator`はバリデーションのみを担当する軽量パッケージです。`@hono/zod-openapi`はバリデーションに加えてOpenAPI仕様の自動生成機能を含む上位互換パッケージです。OpenAPI仕様が不要な場合は前者の方がバンドルサイズを小さく保てます。 Q2. JWTのリフレッシュトークンはどう実装する? アクセストークン(有効期限15〜60分)とリフレッシュトークン(有効期限7〜30日)を分けて発行し、リフレッシュトークンはデータベースに保存します。`/auth/refresh`エンドポイントでリフレッシュトークンを検証後、新しいアクセストークンを発行するのが標準的なパターンです。 Q3. 複数ルートに異なるJWT設定を適用できますか? はい。`app.use('/admin/*', jwt({ secret: ADMIN_SECRET }))` と `app.use('/api/*', jwt({ secret: API_SECRET }))` のように、パスごとに異なる設定を適用できます。 Q4. Zodでネストされたオブジェクトをバリデーションする方法は? `z.object()`をネストするだけです。例: `z.object({ address: z.object({ city: z.string(), zip: z.string().regex(/^\d{7}$/) }) })` のように記述します。 Q5. レート制限をユーザーIDごとに適用できますか? `keyGenerator`関数をカスタマイズすることで可能です。認証後のルートでは `keyGenerator: (c) => c.get('jwtPayload').sub` のようにJWTペイロードのユーザーIDをキーとして使用できます。 Q6. OpenAPI仕様をTypeScriptの型として利用できますか? `@hono/zod-openapi`では`createRoute()`の戻り値から型を推論できます。また`openapi-typescript`などのツールでOpenAPI JSONから型定義を自動生成することも可能です。 Q7. Honoのテストはどう書く? `@hono/testing`(または`hono/testing`)の`testClient()`を使うと型安全なHTTPクライアントでテストできます。VitestやJestと組み合わせて、実際のHTTPサーバーを起動せずにハンドラーを直接テストできます。

Oflightの技術支援サービス

Honoを活用した本番品質APIの設計・開発・運用保守を支援しています。バリデーション設計、認証基盤構築、OpenAPI仕様の整備、CloudflareWorkers/Vercel Edgeへのデプロイ最適化まで、トータルでサポートします。詳しくはソフトウェア開発サービスをご覧ください。

お気軽にご相談ください

お問い合わせ