100日チャレンジ day14 (BFF Dashboard)
昨日
100日チャレンジに感化されたので、アレンジして自分でもやってみます。
やりたいこと
- 世の中のさまざまなドメインの簡易実装をつくり、バックエンドの実装に慣れる(dbスキーマ設計や、関数の分割、使いやすいインターフェイスの切り方に慣れる
- 設計力(これはシステムのオーバービューを先に自分で作ってaiに依頼できるようにする
- 生成aiをつかったバイブコーティングになれる
- 実際にやったことはzennのスクラップにまとめ、成果はzennのブログにまとめる(アプリ自体の公開は必須ではないかコードはgithubにおく)
できたもの

BFF 作ったことないので BFF をやる
Day 14: パーソナライズド・ダッシュボード (BFF パターン実装)
1. 概要
このアプリケーションは、Backend for Frontend (BFF) パターンの理解と実践を目的とした、パーソナライズド・ダッシュボードです。
ユーザーごとに最適化された情報を表示するために、フロントエンド (ダッシュボード UI) は専用の BFF エンドポイントのみと通信します。BFF は内部的に複数の独立した「コア API」を呼び出し、取得したデータをフロントエンドの表示に適した形に加工・集約して返します。
2. 機能要件
2.1. コア API 群 (Core APIs)
以下のダミー API を Next.js Route Handlers として実装します。データは Prisma + SQLite で管理します (シードデータで初期投入)。
-
/api/core/profile/[userId](GET):- 指定された
userIdのユーザープロフィール情報 (名前、メールアドレス、簡単な自己紹介文など) を返します。 - 存在しない
userIdの場合は 404 を返します。
- 指定された
-
/api/core/activities/[userId](GET):- 指定された
userIdの最近のアクティビティ履歴 (例: 操作内容、日時) のリストを返します。最新 5 件程度で十分です。 - 該当ユーザーのアクティビティがない場合は空配列を返します。
- 指定された
-
/api/core/notifications(GET):- 全ユーザー共通のお知らせ情報 (タイトル、本文、公開日時) のリストを返します。最新 3 件程度で十分です。
-
/api/core/recommendations/[userId](GET):- 指定された
userIdに基づくおすすめアイテム (商品名、画像 URL、簡単な説明) のリストを返します。ユーザーごとに異なる結果を返すようにシミュレートします (例:userIdの偶奇で内容を変える)。
- 指定された
2.2. BFF (Backend for Frontend) API
ダッシュボード表示専用の BFF エンドポイントを Next.js Route Handler として実装します。
-
/api/bff/dashboard(GET):- クエリパラメータ
userIdを受け取ります。 - 内部で以下の処理を行います:
-
/api/core/profile/[userId]を呼び出し、プロフィール情報を取得します。 -
/api/core/activities/[userId]を呼び出し、アクティビティ履歴を取得します。 -
/api/core/notificationsを呼び出し、お知らせ情報を取得します。 -
/api/core/recommendations/[userId]を呼び出し、おすすめ情報を取得します。
- これらの API 呼び出しは並行して実行することが望ましいです (
Promise.all)。
-
- 各コア API から取得したデータを、フロントエンドが扱いやすい単一の JSON オブジェクトに整形して返します。
- 例:
{ profile: {...}, activities: [...], notifications: [...], recommendations: [...] }
- 例:
- いずれかのコア API 呼び出しに失敗した場合でも、取得できたデータだけでレスポンスを返すか、あるいは BFF としてエラーを返すか検討します (今回は取得できたデータだけで返す方針とします)。
-
userIdが指定されない場合や、必須のコア API (例: profile) がエラーを返した場合は 400 や 500 エラーを返します。
- クエリパラメータ
2.3. フロントエンド (UI)
-
ユーザー選択:
- アプリケーション上部などに、操作対象のユーザーを選択するためのシンプルなドロップダウンリストを設置します (例: User 1, User 2)。
- 選択された
userIdはクライアント側で状態として保持します。
-
ダッシュボード表示 (
/):- ユーザーが選択されると、選択された
userIdをクエリパラメータとして/api/bff/dashboardを呼び出します。 - BFF から返却されたデータを元に、以下の情報をカード形式などで表示します:
- ユーザープロフィール
- 最近のアクティビティ一覧
- お知らせ一覧
- おすすめアイテム一覧
- データのロード中やエラー発生時には適切なフィードバックを表示します。
- ユーザーが選択されると、選択された
3. 非機能要件
- フレームワーク: Next.js (App Router)
- 言語: TypeScript
- データベース: SQLite
- ORM: Prisma
- スタイリング: Tailwind CSS
- API 通信: Fetch API
- 状態管理: React Hooks (useState, useEffect)
- エラーハンドリング: 基本的な
try-catch,console.error - テスト: 手動確認 (UI 操作、API レスポンス確認)
4. データモデル (Prisma スキーマ案)
コア API が利用するデータを管理します。
// prisma/schema.prisma
model User {
id Int @id @default(autoincrement())
name String
email String @unique
bio String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
activities Activity[]
// recommendations は userId の偶奇などで動的に生成するためDBモデル不要
}
model Activity {
id Int @id @default(autoincrement())
userId Int
user User @relation(fields: [userId], references: [id])
action String // 例: "記事 A を読んだ", "商品 B を購入した"
timestamp DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId, timestamp])
}
model Notification {
id Int @id @default(autoincrement())
title String
content String
publishedAt DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([publishedAt])
}
model Recommendation {
id Int @id @default(autoincrement())
itemName String
imageUrl String
description String?
targetUserType String // "even" or "odd" (ユーザーIDの偶奇で出し分け用)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
5. API エンドポイント定義
5.1. コア API
-
GET
/api/core/profile/[userId]- 成功 (200):
{ "id": 1, "name": "User One", "email": "user1@example.com", "bio": "..." } - 失敗 (404):
{ "error": "User not found" }
- 成功 (200):
-
GET
/api/core/activities/[userId]- 成功 (200):
[ { "id": 1, "action": "記事 A を読んだ", "timestamp": "..." }, ... ]
- 成功 (200):
-
GET
/api/core/notifications- 成功 (200):
[ { "id": 1, "title": "お知らせ1", "content": "...", "publishedAt": "..." }, ... ]
- 成功 (200):
-
GET
/api/core/recommendations/[userId]- 成功 (200):
[ { "id": 1, "itemName": "おすすめ商品A", "imageUrl": "...", "description": "..." }, ... ]
- 成功 (200):
5.2. BFF API
-
GET
/api/bff/dashboard?userId=<userId>- 成功 (200):
{ "profile": { "id": 1, "name": "User One", ... }, "activities": [ { "id": 1, "action": "...", ... }, ... ], "notifications": [ { "id": 1, "title": "...", ... }, ... ], "recommendations": [ { "id": 1, "itemName": "...", ... }, ... ] } - 一部失敗時 (例: activities のみ失敗) (200):
{ "profile": { "id": 1, "name": "User One", ... }, "activities": null, // または空配列、エラー情報など "notifications": [ { "id": 1, "title": "...", ... }, ... ], "recommendations": [ { "id": 1, "itemName": "...", ... }, ... ] } - 失敗 (400):
{ "error": "userId is required" } - 失敗 (500):
{ "error": "Failed to fetch mandatory data (e.g., profile)" }
- 成功 (200):
6. 画面構成案
-
ルートページ (
/)- ヘッダー: ユーザー選択ドロップダウン
- メインエリア (ダッシュボード):
- 左上: プロフィールカード
- 左下: おすすめアイテムカード (横スクロール可)
- 右上: お知らせリスト
- 右下: 最近のアクティビティリスト
7. ディレクトリ構成案
/day14_bff_dashboard
├── app/
│ ├── api/
│ │ ├── bff/
│ │ │ └── dashboard/
│ │ │ └── route.ts # BFF API: GET /api/bff/dashboard
│ │ └── core/
│ │ ├── profile/
│ │ │ └── [userId]/
│ │ │ └── route.ts # Core API: GET /api/core/profile/[userId]
│ │ ├── activities/
│ │ │ └── [userId]/
│ │ │ └── route.ts # Core API: GET /api/core/activities/[userId]
│ │ ├── notifications/
│ │ │ └── route.ts # Core API: GET /api/core/notifications
│ │ └── recommendations/
│ │ └── [userId]/
│ │ └── route.ts # Core API: GET /api/core/recommendations/[userId]
│ ├── (pages)/
│ │ └── page.tsx # ダッシュボード UI
│ ├── _lib/ # BFF ロジックやコア API ロジックの共通部分など
│ ├── layout.tsx
│ └── globals.css
├── components/ # 共有 UI コンポーネント (例: UserSelector, DashboardCard)
│ └── ui/
├── lib/ # PrismaClient, 共通ユーティリティ
│ └── db.ts
├── prisma/
│ ├── schema.prisma
│ ├── migrations/
│ └── dev.db
├── public/
├── .env
├── next.config.mjs
├── package.json
├── tsconfig.json
└── README.md # この仕様書を記述
8. 実装手順案
-
プロジェクト初期化:
template->day14_bff_dashboard,package.json更新,README.md更新。 -
Prisma 設定:
schema.prisma定義,npx prisma migrate deploy,npx prisma db seedで初期データ投入 (User, Activity, Notification, Recommendation)。 -
コア API 実装: 各
/api/core/.../route.tsを実装。Prisma を使ってデータを取得し、JSON で返す。 -
BFF API 実装:
/api/bff/dashboard/route.tsを実装。内部でコア API をfetchで並行呼び出し、結果を整形して返す。エラーハンドリングも考慮。 -
UI コンポーネント実装:
-
UserSelector: ユーザー選択ドロップダウン。 -
DashboardCard: 各セクション (Profile, Activity 等) を表示する汎用カードコンポーネント。
-
-
フロントエンド実装:
/app/page.tsxを実装。-
UserSelectorを配置し、ユーザー選択状態を管理。 - 選択された
userIdで/api/bff/dashboardを呼び出す処理を実装 (useEffect)。 - 取得したデータを
DashboardCardを使って表示。 - ローディング状態、エラー状態の表示。
-
- 動作確認: UI 操作、各 API のレスポンス確認。
9. シーケンス図 (ダッシュボード表示フロー)
承知いたしました。以下に「パーソナライズド・ダッシュボード」アプリケーション作成の作業計画を立てます。
作業計画:
-
プロジェクト初期化:
-
templateディレクトリをコピーしてday14_bff_dashboardディレクトリを作成します。 -
day14_bff_dashboardディレクトリに移動します。 -
package.jsonのnameフィールドをday14_bff_dashboardに更新します。 -
README.mdの内容を先ほど作成した仕様書で上書きします。
-
-
データベース設定と初期データ投入:
-
prisma/schema.prismaを仕様書に合わせて更新します (User, Activity, Notification, Recommendation モデル定義)。 -
npx prisma migrate deployを実行してデータベーススキーマを適用します。 -
prisma/seed.tsを作成し、各モデルに初期データ (ユーザー2名分程度、関連データ含む) を投入するスクリプトを記述します。 - (必要であれば
package.jsonの seed スクリプトをts-nodeを使うように修正し、ts-nodeを dev dependency としてインストールします)。 -
npx prisma db seedを実行して初期データを投入します。 - (任意)
npx prisma studioでデータ投入を確認します。
-
-
コア API 実装:
-
/api/core/profile/[userId]/route.tsを実装します (GET)。 -
/api/core/activities/[userId]/route.tsを実装します (GET)。 -
/api/core/notifications/route.tsを実装します (GET)。 -
/api/core/recommendations/[userId]/route.tsを実装します (GET、userId の偶奇で結果を分岐)。 -
curlコマンド等で各コア API の動作を確認します。
-
-
BFF API 実装:
-
/api/bff/dashboard/route.tsを実装します (GET)。 - 内部で
fetchを用いて4つのコア API を並行 (Promise.all) で呼び出します。 - 取得したデータを整形し、単一の JSON レスポンスとして返します。
- コア API の一部失敗時のハンドリングや、必須パラメータ (
userId) のチェックを行います。 -
curlコマンド等で BFF API の動作を確認します (異なるuserIdで結果が変わることも確認)。
-
-
UI コンポーネント実装:
-
components/UserSelector.tsx: ユーザー選択用ドロップダウン。 -
components/DashboardCard.tsx: 各セクション表示用の汎用カード。 - (任意) 必要に応じて
components/ui/に Shadcn/UI などのベースコンポーネントを導入します (例: Card, Select)。
-
-
フロントエンドページ実装:
-
app/page.tsxをクライアントコンポーネント ('use client') として実装します。 -
UserSelectorで選択されたユーザー ID を状態管理します。 - 選択されたユーザー ID に基づき、
/api/bff/dashboardをfetchで呼び出します。 - ローディング状態、エラー状態をハンドリングします。
- 取得したデータを
DashboardCardを用いて画面に表示します (プロフィール、アクティビティ、お知らせ、おすすめ)。 - Tailwind CSS でレイアウトを整えます。
-
-
最終動作確認:
- ブラウザで
http://localhost:3001を表示し、ユーザー切り替え、データ表示、ローディング/エラー表示などを一通り確認します。 - (任意) Playwright などの E2E テストツールで基本的な動作を確認します。
- ブラウザで
この計画に基づき、まずは ステップ 1: プロジェクト初期化 から開始します。

今回はシンプルなので特になし
gemini2.5-exp が全然使い物にならなかったので一度ディレクトリ丸ごと消した
BFF ってフロントエンドの複雑さを切り出したものなんだな。ただこれって誰が管理するの?がめちゃくちゃ揉めそうだな。。。あとapi 変えた時に bff もフロントもやらないといけないと考えると結構面倒。