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 もフロントもやらないといけないと考えると結構面倒。