
Summary
この文書の要点
- BFFは画面のための集約層であり、業務ルールの本体にしない。
- Hono APIは認証済みユーザーと権限を明示的に受け取る。
- BFFのDTOは画面の都合に寄せ、業務APIの永続化モデルとは別に置く。
- loader、action、Hono handlerのどこで認証と権限を確定するかを明示する。
どこが設計の難所か
React RouterのloaderやactionからHono APIを呼ぶ構成では、画面に必要な複数データをどこで集約するかが問題になります。画面側にAPI呼び出しを散らすと状態管理が複雑になり、API側に画面専用処理を入れすぎると再利用しづらくなります。
BFFは便利ですが、認証、権限、キャッシュ、エラー変換を持つため、責務が膨らみがちです。特に管理画面と利用者画面がある場合、同じAPIを使うべきか、画面ごとにBFFを分けるべきかを早めに決める必要があります。
React Routerのloaderは画面に必要なデータを集めやすく、Honoは軽量なAPI境界を作りやすい組み合わせです。一方で、loaderが直接ドメインの知識を持ちすぎると、画面遷移と業務ルールが絡み合い、テストも再利用も難しくなります。
境界をどう切るか
Hono側はドメイン単位のAPIを提供し、React Router側またはBFF層で画面用に集約します。BFFでは複数APIの呼び出し順、表示用DTOへの変換、軽いキャッシュを扱い、DB更新や権限判定の本体は業務API側に残します。
境界は、画面都合の集約、業務ルール、永続化の三層で考えます。BFFは複数APIの呼び出し、表示用DTO、部分失敗時のUI判断を担い、Hono側は認証済み主体に対する業務操作の契約を保ちます。
実装で効く細部
TypeScriptでは、DBスキーマ型、APIレスポンス型、画面表示型を分けます。BFFからHonoへ渡すリクエストにはユーザーIDだけでなく、権限スコープや監査に必要な情報を含めます。エラーはHTTPステータス、利用者向けメッセージ、内部ログ用コードに分けて扱います。
型共有では、DB型をそのままexportせず、`ApiResponse`、`BffViewModel`、`FormInput`を分けます。Hono側はZodなどで入力を検証し、React Router側は同じschemaを使っても、エラー表示用の整形は画面に閉じ込めます。
- loaderでは読み取り集約を行い、更新系の副作用はactionまたはAPI handlerへ寄せる。
- BFFのキャッシュキーにはユーザーID、権限スコープ、検索条件を含め、権限違いの混入を避ける。
- エラーは利用者向けメッセージ、HTTPステータス、内部ログコードに分ける。
壊れ方を観測する
検証では、loader単位の結合テストとHono API単体のテストを分けます。BFFでは複数APIの一部が失敗した時に画面をどう表示するか、Honoでは権限違反や入力不正が正しく返るかを確認します。
検証では、BFFの結合テストとHono APIの契約テストを分けます。APIが正しいだけでは画面が正しいとは限らないため、一部APIの失敗、権限不足、空データ、遅延時のloader挙動を別ケースとして確認します。
捨てた選択肢とトレードオフ
BFFを厚くすると画面開発は楽になりますが、別画面や外部連携から使いにくくなります。薄くしすぎるとフロントエンドに業務知識が漏れます。判断基準は、その処理が画面表示の都合なのか、業務ルールなのかです。
BFFを厚くすると画面単位の開発は速くなりますが、別画面から同じロジックを使いにくくなります。薄くしすぎるとフロントエンドに業務判断が漏れます。更新ルールと参照集約を分けることが、厚さの判断基準になります。
現場に残す判断軸
React RouterとHonoの組み合わせでは、同じ言語で書けることに甘えず境界を明示することが重要です。BFFは画面の複雑さを吸収し、業務APIは長く使える契約として保つ、という分担が安定します。
React RouterとHonoの良さは、境界を消すことではなく、境界を型で見えるようにできることです。どの型が画面のための型で、どの型がAPI契約なのかを分けると、TypeScriptの利点が保守性に直結します。


