本文へスキップ
株式会社オブライト
Web Development2026-04-24約6分で読めます

Hono + Inertia + React のフォーム&CRUD実装パターン

Drizzle ORM・Zod・Inertia useForm を組み合わせる【2026年版】

Hono + Inertia + React で業務アプリの主役である CRUD・フォームをどう書くか。Drizzle ORM での DB 接続、Zod による型と入力検証の一気通貫、Inertia の useForm を使ったエラー表示・リダイレクト・フラッシュメッセージまで、実装パターンを実務目線で整理します。


ゴール — 業務アプリの主役を1スタックで完結させる

業務アプリの大半は「一覧 → 作成 → 編集 → 削除」の CRUD と「フォームを投げる」の繰り返しです。本記事ではこれを Hono + Inertia + React の上で、できるだけ少ないコードと少ない概念で実装するパターンを示します。組み合わせる主要ライブラリ:

- Drizzle ORM: TypeScript ファーストの軽量ORM。SQL に近く型安全
- Zod: スキーマ定義 + 型生成 + 入力検証を一発で
- Inertia useForm: クライアント側のフォーム状態とサーバ送信・エラー表示を一体化

全体の流れ

Loading diagram...

Step 1: スキーマを Zod で定義

「ひとつの真実の源」をZodスキーマにするのが2026年のベストプラクティスです。例:ユーザー作成フォーム。

- email: 文字列、メール形式
- name: 文字列、1〜80文字
- role: 'admin' | 'editor' | 'viewer'

Zod の z.object({...}) で書き、z.infer<typeof Schema> で TypeScript 型を生成。サーバ側のバリデーションとクライアント側の型を1ヶ所で管理できます。スキーマは shared/ 等のディレクトリに置き、サーバ・クライアント両方から import できる構造に。

Step 2: Drizzle で DB スキーマを定義

Drizzle ORM は TypeScript で SQL を書く感覚に近いORMです。schema.ts にテーブル定義を書き、マイグレーションは drizzle-kit generate / drizzle-kit push で生成・適用します。

Drizzle のテーブル定義から得られる型を、Zod スキーマと突き合わせるユーティリティ(drizzle-zod パッケージ)も便利です。これで「DB列定義 ⇄ Zod ⇄ TypeScript型」の三角形が自動同期できます。詳細は Drizzle ORM 公式 を参照してください。

Step 3: Hono ハンドラ — index / create / update / delete

業務アプリで最低限欲しいエンドポイントを Hono で書きます。

- GET /users: 一覧。Drizzle で select() し、Inertia レスポンスでページ Users/Index に props として配列を返す
- GET /users/new: 空フォーム表示
- POST /users: Zod で c.req.parseBody() をパース → 失敗ならエラーを返却、成功なら Drizzle で insert()redirect('/users') + フラッシュメッセージ
- PATCH /users/:id: 編集処理(同じくZod→Drizzle→redirect)
- DELETE /users/:id: 削除

Inertia は POST 後に redirect を返すと SPA 内でのナビゲーションを自動処理します。「PRG パターン(Post-Redirect-Get)」を素直に使えるのがこのスタックの大きな強みです。

Step 4: React 側 — useForm でフォームを書く

Inertia の useForm フックは、フォーム状態 / 送信中フラグ / サーバから返ったエラーを一括で扱える便利なフックです。

- data: フォームの値
- setData(key, value): 値の更新
- post(url) / put(url) / delete(url): 送信
- processing: 送信中フラグ
- errors: サーバ側 Zod が返したエラー(フィールド名 → メッセージ)

UI 側では errors.email<input> の下に表示するだけで、サーバ側エラーがそのまま画面に反映されます。React Hook Form を別途入れなくても、十分な実用性が出ます。

Step 5: フラッシュメッセージと共有プロパティ

「保存しました」「削除しました」のようなトースト通知は、Inertia の共有プロパティで実装すると綺麗です。

1. Hono ミドルウェアで、リクエスト処理後にセッションから flash を読み出して共有プロパティに乗せる
2. React 側のレイアウトコンポーネントで usePage().props.flash を見てトースト表示

これで「サーバ側で redirect + flash('saved')」を呼ぶだけで、画面右上にトーストが出る統一動作が組めます。

実装上の注意点

- N+1 問題: 一覧 + 関連データを取るときは Drizzle の with で eager load するか、leftJoin を使う
- サーバ側 Zod を信頼の唯一の源に: クライアント側にも同じZodスキーマを使ってリアルタイム検証できますが、最終判定はサーバ側
- 大きいフォーム: ファイルアップロードを含む場合は useFormtransformmultipart/form-data を使う
- 楽観的更新: Inertia の preserveScroll: trueonly: ['users'] 等の partial reload オプションで体験を磨ける
- トランザクション: 複数テーブルの整合性が必要な処理は Drizzle のトランザクションでまとめる

次回 — 認証

次回は 認証完全ガイド で、Better Auth / Lucia / 自前セッション の選び方と Inertia 統合のパターンを扱います。シリーズ全体は Part 1: スタック概要 を参照ください。

FAQ

Q1: Drizzle じゃなくて Prisma でもいいですか?
A: 使えます。ただ Workers 環境ではバンドルサイズと cold start の観点で Drizzle の方が有利です。Node 環境なら Prisma も実用的です。

Q2: React Hook Form を入れる価値はありますか?
A: 大規模で複雑なフォームでは検討の価値あり。中小規模では useForm 単体で十分なケースが多いです。

Q3: 楽観的更新はどうやる?
A: Inertia の transform と React 側の状態更新を組み合わせるか、TanStack Query を併用するパターンがあります。やりすぎないことが重要。

Q4: ファイルアップロードは?
A: useFormmultipart/form-data をサポートしているため、<input type="file" /> の値を setData に渡すだけで動きます。

参考文献

お気軽にご相談ください

お問い合わせ