🔥

Next.js Route HandlerからHonoへ:API設計が楽になった理由

2 か月前
0

この記事は、ひとりでつくるSaaS - 設計・実装・運用の記録 Advent Calendar 2025 の12日目の記事です。

昨日の記事では「MPAからSPAに移行した理由」について書きました。この記事では、Next.js Route HandlerからHonoに移行した理由と、その効果について解説します。

🎯 なぜRoute Handlerから移行したのか

Route Handlerは手軽にAPIを作成できますが、プロジェクトが成長するにつれて課題が見えてきました。

Route Handlerの課題

1. ディレクトリ構造の制約

Route Handlerでは、app/api/配下のディレクトリ構造がそのままURLパスになります。

app/api/ ├── users/ │ ├── route.ts → GET /api/users │ └── [id]/ │ └── route.ts → GET /api/users/123 ├── contents/ │ ├── route.ts → GET /api/contents │ └── [id]/ │ ├── route.ts → GET /api/contents/456 │ └── comments/ │ └── route.ts → GET /api/contents/456/comments

エンドポイントが増えるにつれ、app/api/配下が肥大化していきます。ユーティリティ関数やバリデーションを共有したいとき、ファイルの配置場所に迷うことも多くなりました。

2. コードの重複

route.tsファイルで似たようなバリデーションやエラーハンドリングを書くことになりがちです。

// app/api/users/route.ts export async function POST(request: NextRequest) { try { const body = await request.json(); // バリデーション if (!body.name || typeof body.name !== 'string') { return NextResponse.json({ error: 'Invalid name' }, { status: 400 }); } // 処理... } catch (error) { return NextResponse.json({ error: 'Internal error' }, { status: 500 }); } } // app/api/contents/route.ts export async function POST(request: NextRequest) { try { const body = await request.json(); // 同じようなバリデーション... if (!body.title || typeof body.title !== 'string') { return NextResponse.json({ error: 'Invalid title' }, { status: 400 }); } // 処理... } catch (error) { return NextResponse.json({ error: 'Internal error' }, { status: 500 }); } }

3. APIドキュメントの手動管理

OpenAPIドキュメントを作成しようとすると、実装とは別に手動で定義ファイルを書く必要があります。実装を変更したらドキュメントも更新する必要があり、乖離が発生しやすい状況でした。

🔥 Honoを選んだ理由

Honoは軽量で高速なWebフレームワークです。

https://hono.dev/

以下の点が決め手になりました。

1. ディレクトリ構造の自由度

HonoをNext.jsに統合すると、app/api/には接続用の最小限のコードだけを置き、API本体はserver/api/で自由に整理できます。

app/api/ └── [[...route]]/ └── route.ts # Honoへの接続のみ(数行) server/api/ ├── index.ts # Honoアプリ本体 ├── routes/ │ ├── users.ts # ユーザー関連API │ ├── contents.ts # コンテンツ関連API │ └── admin.ts # 管理者API └── middleware/ ├── auth.ts # 認証ミドルウェア └── error.ts # エラーハンドリング

機能ごとにファイルを整理でき、共通処理も適切な場所に配置できます。

2. Zod OpenAPIによる自動ドキュメント生成

@hono/zod-openapiを使うと、Zodスキーマからリクエスト・レスポンスの型を定義し、同時にOpenAPIドキュメントを自動生成できます。

import { createRoute, z } from '@hono/zod-openapi'; // リクエスト・レスポンスのスキーマ定義 const CreateUserSchema = z.object({ name: z.string().min(1).openapi({ example: '田中太郎' }), email: z.string().email().openapi({ example: 'tanaka@example.com' }), }); const UserResponseSchema = z.object({ id: z.string().openapi({ example: 'user_123' }), name: z.string(), email: z.string(), createdAt: z.string().datetime(), }); // ルート定義(型とドキュメントが同時に生成される) const createUserRoute = createRoute({ method: 'post', path: '/users', request: { body: { content: { 'application/json': { schema: CreateUserSchema } }, }, }, responses: { 201: { description: 'ユーザー作成成功', content: { 'application/json': { schema: UserResponseSchema } }, }, 400: { description: 'バリデーションエラー', }, }, });

実装とドキュメントが常に同期されるため、乖離の心配がありません。

3. ミドルウェアによる共通処理

認証やエラーハンドリングをミドルウェアとして定義し、再利用できます。

// server/api/middleware/auth.ts import { createMiddleware } from 'hono/factory'; export const authMiddleware = createMiddleware(async (c, next) => { const session = await getSession(c.req.header('Authorization')); if (!session) { return c.json({ error: 'Unauthorized' }, 401); } c.set('user', session.user); await next(); });
// server/api/index.ts import { OpenAPIHono } from '@hono/zod-openapi'; import { authMiddleware } from './middleware/auth'; const app = new OpenAPIHono(); // 認証が必要なルートにミドルウェアを適用 app.use('/users/*', authMiddleware); app.use('/contents/*', authMiddleware); // 公開APIはミドルウェアなし app.route('/public', publicRoutes);

⚡ 実装方法

Next.jsとの統合

HonoをNext.jsに統合するには、hono/vercelアダプタを使います。

// app/api/[[...route]]/route.ts import { handle } from 'hono/vercel'; import { app } from '@/server/api'; export const GET = handle(app); export const POST = handle(app); export const PUT = handle(app); export const DELETE = handle(app);

[[...route]]はキャッチオールセグメントで、/api/*配下のすべてのリクエストをHonoにルーティングします。この数行だけでNext.jsとの接続が完了します。

認証APIとの分離

Better Authのような認証ライブラリを使う場合は、認証エンドポイントを別に分けることもできます。

app/api/ ├── [[...route]]/ # Honoへのプロキシ(メインAPI) ├── auth/ # Better Auth(認証専用) │ └── [...all]/ └── webhooks/ # Webhook(Stripe等) └── stripe/

処理の性質に応じてエンドポイントを分離することで、各部分が独立して管理できます。

エラーハンドリング

Honoでは、エラーハンドリングを一箇所で定義できます。

// server/api/index.ts import { OpenAPIHono } from '@hono/zod-openapi'; const app = new OpenAPIHono(); // Zodバリデーションエラーのハンドリング app.onError((err, c) => { if (err instanceof z.ZodError) { return c.json({ error: 'Validation Error', details: err.errors, }, 400); } // その他のエラー console.error(err); return c.json({ error: 'Internal Server Error' }, 500); }); // 404ハンドリング app.notFound((c) => { return c.json({ error: 'Not Found' }, 404); });

Route Handlerでは各ファイルに書いていたエラー処理が、1箇所にまとまります。

🎉 移行の効果

Route HandlerからHonoへの移行で得られた効果をまとめます。

項目Before(Route Handler)After(Hono)
ディレクトリ構造URL構造に制約される自由に整理可能
バリデーション手動で記述Zodで宣言的に定義
型安全性手動で型定義Zodから自動推論
APIドキュメント手動管理自動生成
エラー処理各ファイルで重複ミドルウェアで一元化

開発効率の向上

  • エンドポイント追加が楽に: 新しいルートを追加するとき、既存のスキーマやミドルウェアを再利用できる
  • 型エラーの早期発見: リクエスト・レスポンスの型がZodスキーマから推論される
  • ドキュメント更新が不要: 実装を変更すれば自動的にドキュメントも更新される

OpenAPIドキュメントの活用

定義したルートからOpenAPIドキュメントを自動生成できます。

// server/api/index.ts app.doc('/doc', { openapi: '3.0.0', info: { title: 'My API', version: '1.0.0', }, });

/api/docにアクセスするとOpenAPI仕様のJSONが取得できます。このJSONをSwagger UIやApidogなどのツールにインポートすれば、エンドポイントの一覧確認やリクエストのテストができます。

テスタビリティの向上

Honoのアプリケーションはフレームワーク非依存なので、テストが書きやすくなります。

import { app } from '@/server/api'; describe('Users API', () => { it('should create a user', async () => { const res = await app.request('/users', { method: 'POST', body: JSON.stringify({ name: 'Test', email: 'test@example.com' }), headers: { 'Content-Type': 'application/json' }, }); expect(res.status).toBe(201); }); });

💡 移行のポイント

1. 段階的に移行する

すべてを一度に移行する必要はありません。新しいエンドポイントからHonoで実装し、既存のRoute Handlerは徐々に移行していく方法もあります。

2. 認証・Webhookは分離を検討

Better AuthやStripe Webhookのように、ライブラリが専用のハンドラを提供している場合は、無理にHonoに統合せず、別エンドポイントとして維持するのも選択肢です。

✅ まとめ

Route HandlerからHonoへの移行で改善したことをまとめます。

解決した課題:

  • ディレクトリ構造の制約 → server/api/で自由に整理
  • コードの重複 → ミドルウェアとスキーマの再利用
  • ドキュメントの手動管理 → Zod OpenAPIで自動生成

得られた効果:

  • 型安全性の向上(Zodによる自動推論)
  • 開発効率の向上(エンドポイント追加が容易)
  • テスタビリティの向上(フレームワーク非依存)

HonoはNext.jsと相性が良く、Route Handlerの課題を解消しながら、より構造化されたAPI開発が可能になります。

明日は「Vercel最適化」について解説します。


シリーズの他の記事

  • 12/11: なぜMPAからSPAに移行したのか:App Routerリファクタリング実践
  • 12/13: Vercel最適化:ビルド時間短縮とレスポンス改善の実践

コメント

0
0
0
Next.js Route HandlerからHonoへ:API設計が楽になった理由
0
投稿
0
フォロワー
0
いいね

プロパティ

ページ
テクノロジー
TECH

関連コンテンツ