データベース設計とは?個人開発で最初に学ぶべき理由
データベース設計とは、アプリケーションが扱うデータをどのように構造化し、保存するかを決める作業です。具体的にはテーブル(データの入れ物)の構成、カラム(項目)の定義、テーブル間の関係性(リレーション)の設計を行います。
「家を建てる前の設計図」——DB設計が重要な理由
DB設計はよく「家の基礎工事」に例えられます。基礎がしっかりしていれば、後からリフォーム(機能追加)も容易です。しかし基礎に問題があると、壁を壊して作り直す(テーブル構造の変更 + 既存データのマイグレーション)ことになり、開発工数が3〜5倍に膨れ上がるケースも珍しくありません。
ShiftBの受講生データでは、DB設計に最初の2〜3時間を投資した受講生は、そうでない受講生と比べてリリースまでの総開発時間が平均32%短いという結果が出ています。
個人開発こそDB設計が必要な3つの理由
| 理由 | 詳細 | 影響 |
|---|
| 1人で保守する | チーム開発なら他のメンバーがカバーできるが、個人開発はすべて自分 | 破綻したDBの修正に週末丸々2日が消える |
| あとから変えにくい | ユーザーデータが入った状態でのテーブル構造変更はリスクが高い | データ移行ミスでユーザーデータ消失の危険 |
| パフォーマンスに直結 | インデックスのない検索、不適切なリレーションは即座に速度低下に | ユーザー数100人で既にレスポンス3秒超え |
DB設計のスキルはAI時代でも不変
バイブコーディングでコード生成が自動化されても、「何のデータを、どう構造化するか」を決めるのは人間の仕事です。AIにDB設計を依頼しても、ビジネスロジックや将来の拡張性を判断できるのは開発者だけ。むしろAI時代だからこそ、AIが生成したスキーマを正しく評価・修正できる目が重要になっています。
この記事で扱う技術スタック
本記事では、ShiftBのコア技術スタックである以下の構成を前提に解説します。
| レイヤー | 技術 | 役割 |
|---|
| フロントエンド | Next.js + TypeScript | UIとServer Actions |
| データベース | Supabase(PostgreSQL) | データ保存・認証・リアルタイム |
| ORM / クライアント | Supabase Client | 型安全なDB操作 |
| AI開発ツール | Claude Code | DB設計の初期案生成・レビュー支援 |
個人開発 vs チーム開発 — DB設計の判断基準はこう変わる
多くのDB設計入門記事は業務システムやチーム開発を前提としていますが、個人開発では判断基準が大きく異なります。ここでは個人開発に特化した設計判断のフレームワークを紹介します。
規模感の違い——テーブル5〜15個が個人開発のスイートスポット
ShiftB受講生が開発したアプリ87個を分析した結果、テーブル数の分布は以下の通りでした。
| テーブル数 | 割合 | アプリの例 | 複雑度 |
|---|
| 1〜4個 | 15% | ブログ、メモアプリ | 低(初心者向け) |
| 5〜10個 | 52% | タスク管理、ECサイト、予約アプリ | 中(最も多い) |
| 11〜15個 | 25% | SaaS、SNS、マーケットプレイス | 高(中級者向け) |
| 16個以上 | 8% | 大規模SaaS、ERP的なサービス | 非常に高い |
テーブル数5〜10個が個人開発のスイートスポットです。これ以上増える場合は、機能を絞るか、段階的にリリースすることを検討しましょう。
正規化の「ちょうどいい」レベル
教科書的には「第3正規形まで正規化する」のが定石ですが、個人開発では過度な正規化がむしろ害になることがあります。
| 正規化レベル | 個人開発での推奨 | 理由 |
|---|
| 第1正規形 | 必須 | 繰り返し項目の排除は最低限必要 |
| 第2正規形 | 必須 | 部分関数従属の排除でデータの不整合を防ぐ |
| 第3正規形 | 推移的依存のあるものだけ | すべて分離すると結合(JOIN)が増えて速度低下。パフォーマンスとのバランスが重要 |
| BCNF・第4正規形 | 不要(ほぼ) | 個人開発規模では過剰。チーム開発の大規模システム向け |
僕がShiftBの受講生にアドバイスしているのは、「第2正規形は必ずやる。第3正規形は将来のクエリパターンを考えて判断する」というルールです。
個人開発DB設計の3つの原則
ShiftBで教えている個人開発のDB設計原則は以下の3つです。
- YAGNI(You Ain't Gonna Need It):今使わないテーブル・カラムは作らない。将来必要になったら追加する
- 画面から逆算する:UIで表示するデータを先に決め、そこからテーブルを設計する
- 1テーブル1責務:1つのテーブルに複数の意味のデータを混ぜない

AI時代のアプリ開発コース
ShiftBのAIシフトコースは、Claude Codeで本格的なWebアプリケーションを作りながらAI駆動開発を身につけるコースです。ただ作って終わりではなく、コードを読め、セキュリティに責任を持って提案でき、要件定義などの上流工程まで担えるところまで現役エンジニアがサポートします。
AIシフトコースを見る →データベース設計の基礎知識 — 正規化・ER図・テーブル定義
ここからは、DB設計に必要な基礎知識を個人開発者が実際に使う範囲に絞って解説します。教科書的な網羅性ではなく、実践で必要十分な知識に焦点を当てます。
正規化とは?——データの重複を排除する技術
正規化(Normalization)とは、データの冗長性を排除し、整合性を保つためにテーブルを分割する技術です。同じデータを複数の場所に保存しないことがゴールです。
たとえば「注文テーブル」に顧客名・顧客メール・顧客住所を毎回入れるのは非正規形です。顧客情報は「顧客テーブル」に分離し、注文テーブルからは顧客IDで参照する——これが正規化の基本的な考え方です。
ER図(Entity-Relationship Diagram)の読み方・書き方
ER図はテーブル間の関係を視覚化した図です。個人開発で最低限知っておくべきリレーションは3種類だけです。
| リレーション | 記号 | 例 | 実装方法 |
|---|
| 1対1 | 1 — 1 | ユーザー ↔ プロフィール | 外部キー + UNIQUE制約 |
| 1対多 | 1 — * | ユーザー → 投稿(1人が複数の投稿を持つ) | 子テーブルに外部キー |
| 多対多 | * — * | 投稿 ↔ タグ | 中間テーブル(post_tags)で表現 |
ER図を手書きする必要はありません。Claude Codeにテーブル構造を伝えると、Mermaid記法でER図を自動生成してくれます(後述)。
テーブル定義の基本要素
各テーブルは以下の要素で定義します。
- テーブル名:英語の複数形(例:
users、tasks、projects) - 主キー(Primary Key):各行を一意に識別するカラム。Supabaseでは
id uuid DEFAULT gen_random_uuid() が標準 - 外部キー(Foreign Key):他のテーブルの主キーを参照するカラム
- データ型:
text、integer、boolean、timestamptz、jsonbなど - 制約(Constraints):
NOT NULL、UNIQUE、CHECK、DEFAULT - インデックス:検索速度を向上させるための索引
個人開発で必ず入れるべき「共通カラム」
ShiftBでは、すべてのテーブルに以下の共通カラムを入れることを推奨しています。
-- すべてのテーブルに共通で入れるカラム
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
-- ユーザーに紐づくテーブルには追加
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE
created_atとupdated_atは「いつデータが作られたか・更新されたか」を記録するもので、デバッグやデータ分析で必ず役立ちます。後から追加するのは大変なので、最初から入れておきましょう。
カラムのデータ型——迷ったときのチートシート
| データの種類 | 推奨データ型 | 避けるべき型 | 理由 |
|---|
| ID | uuid | serial | 連番はセキュリティリスク(URLで推測可能) |
| 名前・タイトル | text | varchar(255) | PostgreSQLではtextとvarcharの性能差はない |
| 金額 | integer(円単位) | float | 浮動小数点は丸め誤差が生じる |
| 日時 | timestamptz | timestamp | タイムゾーン付きでないと国際化時に破綻 |
| 真偽値 | boolean | integer(0/1) | PostgreSQLネイティブの真偽値型を使う |
| ステータス | text + CHECK制約 | enum型 | enum型は後から値の追加・削除が面倒 |
| 柔軟な構造データ | jsonb | json | jsonbはインデックス対応で検索が速い |
AI × Supabaseで始めるDB設計の実践ワークフロー
2026年現在、DB設計はAIとの協業で劇的に効率化できます。ここではClaude CodeとSupabaseを使った、ShiftB推奨のDB設計ワークフローを紹介します。
Step 1:画面設計からデータを洗い出す(所要時間:30分)
DB設計の出発点は画面(UI)です。データベースを直接考えるのではなく、 「ユーザーが画面で何を見るか・何を入力するか」から逆算してデータを洗い出します。
具体的には以下の手順です。
- アプリの主要画面を5〜8個リストアップする
- 各画面で表示するデータをすべて書き出す
- 各画面で入力・操作するデータをすべて書き出す
- 洗い出したデータをグルーピングする(→ これがテーブルの候補になる)
Step 2:Claude CodeにDB設計の初期案を生成させる(所要時間:10分)
洗い出したデータを元に、Claude CodeにDB設計を依頼します。以下のようなプロンプトが効果的です。
以下のアプリのデータベース設計を作成してください。
## アプリ概要
タスク管理SaaS。チームでタスクを管理できる。
## 主要画面と必要なデータ
1. ダッシュボード:プロジェクト一覧、各プロジェクトのタスク数
2. タスク一覧:タスク名、ステータス、担当者、期限、優先度
3. タスク詳細:コメント、添付ファイル、履歴
4. 設定:プロフィール、通知設定
## 技術スタック
- Supabase(PostgreSQL)
- Next.js + TypeScript
## 要件
- RLSポリシーも含めてください
- マイグレーションSQLで出力してください
- 共通カラム(id, created_at, updated_at)は全テーブルに含めてください
Claude Codeは上記の情報から、テーブル構造・リレーション・RLSポリシー・インデックスまで含めた完全なマイグレーションSQLを生成してくれます。生成時間はわずか1〜2分です。
Step 3:人間がレビューする——AIの設計を鵜呑みにしない(所要時間:1時間)
AIが生成したDB設計は80%は正しいですが、残りの20%に致命的な問題が含まれることがあります。以下のチェックリストでレビューしましょう。
| チェック項目 | 確認ポイント | AIがよくやるミス |
|---|
| テーブルの粒度 | 1テーブル1責務になっているか | 1つのテーブルに詰め込みすぎる |
| リレーション | 外部キーとON DELETEの挙動は正しいか | CASCADE指定の漏れ、不適切なSET NULL |
| RLSポリシー | 認証されたユーザーだけが自分のデータにアクセスできるか | RLSの有効化忘れ、ポリシーの条件が緩い |
| インデックス | 頻繁に検索するカラムにインデックスがあるか | 外部キーのインデックス漏れ |
| JSONB列 | 検索・集計しないデータだけがJSONBになっているか | 構造化すべきデータをJSONBに入れがち |
Step 4:Supabase CLIでマイグレーションを実行する(所要時間:15分)
レビューが完了したら、Supabase CLIでマイグレーションを作成・適用します。
# マイグレーションファイルを作成
npx supabase migration new create_initial_tables
# 生成されたSQLファイルにAIの出力を貼り付け
# supabase/migrations/YYYYMMDDHHMMSS_create_initial_tables.sql
# ローカルDBに適用して動作確認
npx supabase db reset
# 型定義を自動生成
npx supabase gen types typescript --local > src/types/database.types.ts
supabase gen typesコマンドが生成する型定義ファイルにより、TypeScriptの型チェックがDB構造と完全に同期します。テーブルのカラム名を間違えた場合、コンパイル時にエラーが出るため、実行時のバグを未然に防げます。
Step 5:ER図を自動生成して全体を俯瞰する(所要時間:5分)
Claude Codeに「今のテーブル構造をMermaid記法のER図にして」と依頼するだけで、リレーションを含む図を自動生成できます。SupabaseのダッシュボードにもER図ビューアがあるので、視覚的に確認しましょう。

AI時代のアプリ開発コース
ShiftBのAIシフトコースは、Claude Codeで本格的なWebアプリケーションを作りながらAI駆動開発を身につけるコースです。ただ作って終わりではなく、コードを読め、セキュリティに責任を持って提案でき、要件定義などの上流工程まで担えるところまで現役エンジニアがサポートします。
AIシフトコースを見る →個人開発でやりがちなDB設計の失敗パターン5選
ShiftBで150名以上の受講生のDB設計をレビューしてきた中で、特に頻出する失敗パターンを5つ紹介します。
失敗1:「とりあえずJSONBに入れる」症候群
SupabaseのPostgreSQLではjsonb型が使えるため、「構造が決まっていないデータはとりあえずJSONBで」と考えがちです。しかし、JSONBに入れたデータは検索・ソート・集計が非効率になります。
判断基準:そのデータでWHEREやORDER BYを使う可能性があるなら、独立したカラムにすべきです。JSONBは「ユーザー設定」「メタデータ」など、表示はするが検索はしないデータにだけ使いましょう。
失敗2:過度な正規化でJOIN地獄
教科書通りに正規化しすぎると、1つのデータを取得するために5テーブル以上のJOINが必要になることがあります。個人開発では1回のクエリでJOINは3テーブルまでを目安にしましょう。
それ以上になる場合は、ビュー(VIEW)やSupabaseのRPC(Remote Procedure Call)で複雑なクエリをラップし、フロントエンドからはシンプルに呼び出せるようにするのがベストプラクティスです。
失敗3:RLS(Row Level Security)の設定忘れ
Supabaseはクライアントから直接DBにアクセスできる仕組みのため、RLSを有効にしないと全データが丸見えになります。ShiftB受講生の初回レビューで約40%がRLSの設定漏れを指摘されています。
テーブルを作成したら、必ず以下を実行してください。
-- RLSを有効化(必須!)
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
-- 自分のデータだけ読める
CREATE POLICY "Users can read own tasks"
ON tasks FOR SELECT
USING (auth.uid() = user_id);
-- 自分のデータだけ作成・更新・削除できる
CREATE POLICY "Users can manage own tasks"
ON tasks FOR ALL
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
失敗4:インデックスを一切追加しない
Supabaseは主キーとUNIQUE制約のカラムには自動でインデックスを作成しますが、外部キーには自動でインデックスが作られません。
たとえばtasksテーブルのuser_idにインデックスがないと、RLSのauth.uid() = user_idチェックでフルテーブルスキャンが走り、データが増えるとレスポンスが100倍以上遅くなるケースがあります。
-- 外部キーには必ずインデックスを追加
CREATE INDEX idx_tasks_user_id ON tasks(user_id);
CREATE INDEX idx_tasks_project_id ON tasks(project_id);
-- ステータスでフィルタすることが多い場合
CREATE INDEX idx_tasks_status ON tasks(status);
失敗5:論理削除と物理削除を混在させる
「削除したデータを後から復元したい」と考えてdeleted_atカラム(論理削除)を導入するケースがありますが、個人開発では原則として物理削除(DELETE)を推奨します。
論理削除は、すべてのクエリにWHERE deleted_at IS NULLを追加する必要があり、漏れると削除済みデータが表示されるバグにつながります。個人開発では、削除前に確認ダイアログを出す方がシンプルで安全です。
実践チュートリアル:タスク管理SaaSのDB設計をゼロから作る
ここからは、実際にタスク管理SaaSのDB設計を一から作る手順を解説します。個人開発で最も需要の高いアプリタイプの1つであり、ShiftBの受講生も多く取り組んでいるテーマです。
要件定義:何を作るか明確にする
まず、アプリの主要機能を定義します。
- ユーザー登録・ログイン(Supabase Authを利用)
- プロジェクトの作成・管理
- タスクの作成・編集・削除・ステータス変更
- タスクへのコメント機能
- タスクにラベル(タグ)を付ける機能
テーブル設計:5つのテーブルで実現する
上記の要件から、以下の5テーブル + 1中間テーブルで設計します。
-- 1. プロフィール(auth.usersの拡張)
CREATE TABLE profiles (
id uuid PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
name text NOT NULL,
avatar_url text,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
-- 2. プロジェクト
CREATE TABLE projects (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
name text NOT NULL,
description text,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
-- 3. タスク
CREATE TABLE tasks (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
title text NOT NULL,
description text,
status text NOT NULL DEFAULT 'todo'
CHECK (status IN ('todo', 'in_progress', 'done')),
priority text NOT NULL DEFAULT 'medium'
CHECK (priority IN ('low', 'medium', 'high')),
due_date date,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
-- 4. コメント
CREATE TABLE comments (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
task_id uuid NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
content text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
-- 5. ラベル
CREATE TABLE labels (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
name text NOT NULL,
color text NOT NULL DEFAULT '#2788f5',
created_at timestamptz NOT NULL DEFAULT now()
);
-- 6. タスクとラベルの中間テーブル(多対多)
CREATE TABLE task_labels (
task_id uuid NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
label_id uuid NOT NULL REFERENCES labels(id) ON DELETE CASCADE,
PRIMARY KEY (task_id, label_id)
);
インデックスとRLSを追加する
-- インデックス(外部キー + よく検索するカラム)
CREATE INDEX idx_projects_user_id ON projects(user_id);
CREATE INDEX idx_tasks_project_id ON tasks(project_id);
CREATE INDEX idx_tasks_user_id ON tasks(user_id);
CREATE INDEX idx_tasks_status ON tasks(status);
CREATE INDEX idx_comments_task_id ON comments(task_id);
-- RLS有効化
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
ALTER TABLE comments ENABLE ROW LEVEL SECURITY;
ALTER TABLE labels ENABLE ROW LEVEL SECURITY;
ALTER TABLE task_labels ENABLE ROW LEVEL SECURITY;
-- RLSポリシー(例:projects)
CREATE POLICY "Users can manage own projects"
ON projects FOR ALL
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
-- RLSポリシー(例:tasks — プロジェクト経由で権限チェック)
CREATE POLICY "Users can manage tasks in own projects"
ON tasks FOR ALL
USING (
EXISTS (
SELECT 1 FROM projects
WHERE projects.id = tasks.project_id
AND projects.user_id = auth.uid()
)
);
型定義を生成してフロントエンドと連携する
マイグレーションを適用したら、supabase gen typesで型定義を生成します。これにより、Next.jsのコードでテーブルのカラム名やデータ型を間違えるとTypeScriptがコンパイルエラーで教えてくれるようになります。
// TypeScriptの型定義が自動生成される
import { Database } from "@/types/database.types";
type Task = Database["public"]["Tables"]["tasks"]["Row"];
// → { id: string; project_id: string; user_id: string;
// title: string; description: string | null;
// status: "todo" | "in_progress" | "done";
// priority: "low" | "medium" | "high";
// due_date: string | null;
// created_at: string; updated_at: string; }
DB設計の良い例・悪い例 — Next.js × Supabaseの実コード付き
最後に、ShiftBの受講生レビューで実際に見かけた良い設計と悪い設計を、フロントエンドのコードも含めて比較します。
悪い例:1テーブルに全部入れる「God Table」パターン
-- ❌ 悪い例:すべてを1テーブルに詰め込む
CREATE TABLE everything (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_name text,
user_email text,
project_name text,
task_title text,
task_status text,
comment text,
label text, -- カンマ区切りで複数ラベルを格納 😱
created_at timestamptz DEFAULT now()
);
この設計の問題点は明確です。
- ユーザー名を変更するとき、全行を更新する必要がある
- ラベルがカンマ区切りのため、特定ラベルでの検索が不可能(LIKE検索は遅い)
- データの重複が大量に発生し、ストレージが無駄になる
- SupabaseのRLSが正しく機能しない(どの行がどのユーザーのものか不明確)
良い例:責務で分割した正規化テーブル
-- ✅ 良い例:前セクションのタスク管理SaaSの設計
-- profiles, projects, tasks, comments, labels, task_labels
-- それぞれが1つの責務を持ち、外部キーで関連付け
// Next.jsのServer Actionでの利用例
async function getTasksWithLabels(projectId: string) {
const supabase = await createClient();
const { data, error } = await supabase
.from("tasks")
.select(`
*,
comments(count),
task_labels(
labels(name, color)
)
`)
.eq("project_id", projectId)
.order("created_at", { ascending: false });
return data;
}
正規化されたテーブルではSupabaseのリレーションクエリを使って、1回のAPIコールで関連データをまとめて取得できます。コードもシンプルで、型安全です。
悪い例:ステータスをbooleanで管理する
-- ❌ 悪い例:booleanでステータスを管理
CREATE TABLE tasks (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
title text NOT NULL,
is_done boolean DEFAULT false, -- 「進行中」を表現できない
is_urgent boolean DEFAULT false -- 優先度が2値しかない
);
-- ✅ 良い例:text + CHECK制約で拡張性を確保
CREATE TABLE tasks (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
title text NOT NULL,
status text NOT NULL DEFAULT 'todo'
CHECK (status IN ('todo', 'in_progress', 'review', 'done')),
priority text NOT NULL DEFAULT 'medium'
CHECK (priority IN ('low', 'medium', 'high', 'urgent'))
);
booleanは「今は2択だから」と使いがちですが、ほぼ確実に後から選択肢が増えます。text + CHECK制約なら、値を追加するだけでマイグレーションも簡単です。
良い設計のチェックリスト
DB設計が完了したら、以下の項目を確認しましょう。
- 各テーブルに明確な責務がある(1テーブル1エンティティ)
- 外部キーにはすべてインデックスが付いている
- RLSが全テーブルで有効化されている
- 共通カラム(id, created_at, updated_at)がある
- JOINが3テーブル以内で収まるクエリ設計になっている
- 型定義(supabase gen types)が最新になっている
よくある質問(FAQ)
Q1. DB設計の経験がまったくありません。いきなりSupabaseで始めて大丈夫ですか?
大丈夫です。Supabaseはダッシュボード上でテーブルを作成でき、SQLを直接書かなくてもGUIで操作できます。まずはGUIで試しに1〜2個テーブルを作ってみて、慣れてきたらマイグレーションファイル(SQL)で管理する流れがおすすめです。ShiftB受講生の90%以上がDB未経験から始めて、平均2週間で自分のアプリのDB設計をこなせるようになっています。
Q2. PrismaとSupabase Clientのどちらを使うべきですか?
個人開発ならSupabase Clientを推奨します。理由は3つあります。① Supabaseの機能(RLS、リアルタイム、ストレージ)とシームレスに連携できる、②supabase gen typesで型定義を自動生成できる、③ 追加ライブラリのインストールが不要。Prismaは大規模チーム開発やマルチDB対応が必要な場合に検討しましょう。
Q3. テーブルを後から変更するのは大変ですか?
データが少ない段階なら比較的簡単です。Supabase CLIのsupabase migration newで変更用のSQLを書き、supabase db pushで適用するだけです。ただし、ユーザーデータが入った本番環境での変更は慎重に行う必要があります。カラムの追加はリスクが低い、カラムの削除やデータ型の変更はリスクが高いという原則を覚えておきましょう。
Q4. NoSQL(Firebase Firestoreなど)とRDB(Supabase/PostgreSQL)のどちらがいいですか?
個人開発SaaSなら、RDB(Supabase)を強く推奨します。NoSQLはリレーション(テーブル間の関連付け)が弱いため、ECサイトやタスク管理のようなデータ間の関係が複雑なアプリには不向きです。Firestoreで「ユーザーの注文一覧を注文日順で取得し、各注文の商品名も表示する」といったクエリを書くと、Supabaseの5〜10倍のコード量になることも珍しくありません。
Q5. テーブル数が多くなりすぎた場合、どう判断すればいいですか?
テーブル数が15を超えたら、機能を絞るか段階的リリースを検討しましょう。ShiftBでは、MVP(最小限の機能のリリース版)ではテーブル数5〜8個に収めることを推奨しています。「あったらいいな」の機能は後から追加するのが個人開発のセオリーです。テーブルを追加するときは、supabase migration newで別のマイグレーションファイルを作成すれば、既存のテーブルに影響を与えずに拡張できます。
Q6. jsonb型はどういう場面で使うべきですか?
検索・ソート・集計しないデータにだけ使いましょう。具体的には、ユーザーの通知設定({"email": true, "push": false})や UIのカスタマイズ設定など、「保存して表示するだけ」のデータが適しています。 逆に、「ステータスが'done'のタスクを検索したい」のようなWHERE句で使うデータは独立したカラムにするべきです。
Q7. DB設計をAIに丸投げしても大丈夫ですか?
丸投げはNGですが、初期案の生成は大いに活用すべきです。Claude Codeに要件を渡せば、テーブル構造の80%は正しく生成してくれます。ただし、RLSポリシーの適切さ、インデックスの過不足、ビジネスロジックに沿ったCHECK制約は必ず人間がレビューしてください。「AI生成 → 人間レビュー → 修正 → 適用」のサイクルが最も効率的です。