Chapter 1: Supabaseアーキテクチャ理解 🏥
📚 目次に戻る: 📖 学習ガイド
⬅️ 前の章: なし(最初の章)
➡️ 次の章: Chapter 2: 認証システムの実装
🎯 学習レベル: 🌱 基礎 | 🚀 応用 | 💪 発展
⏱️ 推定学習時間: 3-5時間
—
🎯 この章で学ぶこと(初心者向け)
この章では、「Webアプリを作るのに必要なもの」 から始めて、Supabaseがどのようにそれらを解決してくれるかを学びます。
- 🌱 初心者: Webアプリに必要な基本要素がわかる
- 🚀 中級者: Supabaseの各コンポーネントの役割がわかる
- 💪 上級者: 内部の仕組みまで理解して設計に活かせる
💡 まずは身近な例から:「ブログアプリ」を作るとしたら?
想像してみてください。あなたがシンプルなブログアプリを作りたいとします:
✅ 記事を書いて保存したい
✅ 保存した記事を表示したい
✅ ログインした人だけが記事を書けるようにしたい
✅ 記事が更新されたらリアルタイムで反映したい
🤔 従来の方法だと…
一つ一つ別々のサービスやツールを用意する必要がありました:
flowchart TD
A[フロントエンド<br/>React/Vue] --> B[サーバー<br/>Express/FastAPI]
B --> C[データベース<br/>MySQL/PostgreSQL]
B --> D[認証システム<br/>自作or Auth0]
B --> E[リアルタイム<br/>WebSocket サーバー]
F[開発者😰] --> G[5つのシステムを<br/>別々に設定・管理]
問題点:
- 🔧 設定が複雑(5つのシステム × 設定ファイル)
- 🐛 バグが起きやすい(連携部分でエラー)
- ⏰ 開発に時間がかかる
- 💰 運用コストが高い
🎉 Supabaseなら…
1つのサービスで全て解決!
flowchart TD
A[フロントエンド<br/>React/Vue] --> B[Supabase]
B --> C[データ保存 ✅<br/>PostgreSQL]
B --> D[認証 ✅<br/>Supabase Auth]
B --> E[リアルタイム ✅<br/>Realtime]
B --> F[API ✅<br/>PostgREST]
G[開発者😊] --> H[1つのサービスで<br/>全部完結!]
メリット:
- ⚡ 設定が簡単(1つのダッシュボードで管理)
- 🔒 セキュリティが自動で組み込まれている
- 🚀 開発が早い(数時間でプロトタイプ完成)
- 💝 無料から始められる
🧩 Supabaseの中身を覗いてみよう
📱 まずは簡単に:「3つの基本機能」
Supabaseは主に3つの機能でできています:
flowchart LR
A[あなたのアプリ] --> B[Supabase]
subgraph B[Supabase]
C[📄 データ保存<br/>PostgreSQL]
D[🔗 API自動生成<br/>PostgREST]
E[⚡ リアルタイム<br/>Realtime]
end
例:ブログアプリでの役割
機能 | 何をするもの? | ブログアプリでの例 |
---|---|---|
📄 PostgreSQL | データを保存する | 記事のタイトル・内容を保存 |
🔗 PostgREST | API を自動で作る | 記事の取得・保存のAPI を自動作成 |
⚡ Realtime | リアルタイム更新 | 記事が更新されると即座に画面も更新 |
🔍 もう少し詳しく:「内部の仕組み」
実際には、以下のような流れで動いています:
flowchart TB
Client[📱 あなたのアプリ<br/>React/Vue/モバイル] --> Kong[🚪 入口<br/>Kong Gateway]
Kong --> PostgREST[🔗 API<br/>PostgREST]
Kong --> Realtime[⚡ リアルタイム<br/>Realtime Server]
Kong --> Auth[🔐 認証<br/>Supabase Auth]
PostgREST --> PostgreSQL[📄 データベース<br/>PostgreSQL]
Realtime --> PostgreSQL
Auth --> PostgreSQL
PostgreSQL --> WAL[📝 変更ログ<br/>Write-Ahead Log]
WAL --> Realtime
style Client fill:#e1f5fe
style PostgreSQL fill:#f3e5f5
style Kong fill:#e8f5e8
各コンポーネントの役割(簡単版):
- 🚪 Kong Gateway: 入口の門番(どのAPIを呼ぶかを振り分け)
- 🔗 PostgREST: API を自動で作成してくれる魔法のツール
- ⚡ Realtime: データが変わったら即座にお知らせ
- 🔐 Supabase Auth: ログイン・ログアウトを管理
- 📄 PostgreSQL: 全てのデータを保存する倉庫
📄 PostgreSQL:「データの倉庫」を詳しく見てみよう
🤔 そもそもデータベースって何?
データベース = あなたのアプリが使うデータを保存する場所
ブログアプリの例で考えてみましょう:
📝 記事のデータ
- タイトル: "初めてのSupabase"
- 内容: "今日からSupabaseを使って..."
- 作成日: 2024-06-03
- 作者: user_123
👤 ユーザーのデータ
- 名前: "田中太郎"
- メールアドレス: "tanaka@example.com"
- パスワード: (暗号化された文字列)
これらのデータを整理して保存・取得するのがデータベースの仕事です。
🏆 なぜPostgreSQLが選ばれるの?
Supabaseが数あるデータベースの中からPostgreSQLを選んだ理由:
🔰 初心者でもわかるメリット
特徴 | 初心者への影響 | 具体例 |
---|---|---|
📚 標準SQL | 覚えやすい | 他のシステムでも使える知識 |
🔒 安全性 | 勝手にデータが消えない | バックアップ・復元が確実 |
🚀 高性能 | アプリが速く動く | ユーザーを待たせない |
🛠️ 拡張可能 | 成長に対応できる | 小さく始めて大きく育てられる |
🔧 開発者向けメリット
PostgreSQLは「多機能なスイスアーミーナイフ」のようなデータベースです:
- ✅ ACID保証: データが確実に保存される(途中で電源が切れても大丈夫)
- ✅ 高度なクエリ: 複雑な検索・集計が簡単にできる
- ✅ JSON対応: 従来のデータとモダンなデータ両方を扱える
- ✅ 拡張機能: 必要に応じて機能を追加できる
他のデータベースとの違い
| 特徴 | PostgreSQL | MySQL | MongoDB | Firebase | |——|:———-:|:—–:|:——-:|:——–:| | ACID保証 | ✅ 完全 | ⚠️ 部分的 | ⚠️ 部分的 | ❌ なし | | RLS対応 | ✅ ネイティブ | ❌ なし | ❌ なし | ⚠️ 限定的 | | JSON処理 | ✅ 高性能 | ⚠️ 基本的 | ✅ 高性能 | ✅ 高性能 | | 拡張性 | ✅ 豊富 | ⚠️ 限定的 | ⚠️ 限定的 | ❌ なし | | SQL標準 | ✅ 高準拠 | ⚠️ 独自拡張 | ❌ なし | ❌ なし |
Supabase固有の拡張機能:
pg_graphql
: GraphQLエンドポイント自動生成により、REST APIに加えてGraphQLも利用可能pgsodium
: データベースレベルでの暗号化・復号化により、機密データの安全な処理を実現pg_stat_statements
: クエリパフォーマンス監視により、ボトルネックの早期発見と最適化が可能
-- 拡張機能の確認
SELECT name, default_version, installed_version
FROM pg_available_extensions
WHERE name IN ('pg_graphql', 'pgsodium', 'pg_stat_statements');
🔗 PostgREST:「API自動生成の魔法」
🤔 APIって何?そしてなぜ必要?
API(Application Programming Interface) = アプリとデータベースをつなぐ橋
ブログアプリの例で考えてみましょう:
flowchart LR
A[📱 ブログアプリ] --> B[❓どうやって<br/>データを取得?] --> C[📄 データベース]
D[記事を表示したい] --> E[API: 記事データを取得]
F[新しい記事を保存したい] --> G[API: 記事データを保存]
😰 従来の方法:「手作業でAPI を作る」
通常、開発者がこんなコードを書く必要がありました:
// 記事一覧を取得するAPI (手作業で作成)
app.get('/api/articles', async (req, res) => {
try {
// 1. データベースに接続
const connection = await getDBConnection();
// 2. SQLクエリを書く
const query = 'SELECT * FROM articles ORDER BY created_at DESC';
const articles = await connection.query(query);
// 3. 結果を返す
res.json(articles);
} catch (error) {
// 4. エラーハンドリング
res.status(500).json({ error: 'Failed to fetch articles' });
}
});
// 記事を作成するAPI (また手作業で作成)
app.post('/api/articles', async (req, res) => {
// また同じような手順を繰り返し...
});
// 記事を更新するAPI (また手作業で作成)
app.put('/api/articles/:id', async (req, res) => {
// また同じような手順を繰り返し...
});
// 記事を削除するAPI (また手作業で作成)
app.delete('/api/articles/:id', async (req, res) => {
// また同じような手順を繰り返し...
});
問題点:
- 😱 時間がかかる: 1つのテーブルに4つのAPI × 複数テーブル = 大量のコード
- 🐛 バグが起きやすい: 手動でコードを書くのでミスが発生
- 🔄 重複作業: 似たようなコードを何度も書く
🎉 PostgREST:「API が自動で完成!」
PostgRESTを使えば、コードを書かずにAPIが完成します:
flowchart TD
A[📄 データベースにテーブル作成] --> B[🎯 PostgRESTが自動でAPI生成]
B --> C[✅ 記事取得API 完成]
B --> D[✅ 記事作成API 完成]
B --> E[✅ 記事更新API 完成]
B --> F[✅ 記事削除API 完成]
style A fill:#e3f2fd
style B fill:#fff3e0
style C fill:#e8f5e8
style D fill:#e8f5e8
style E fill:#e8f5e8
style F fill:#e8f5e8
魔法のような動作:
- 📊 テーブルを作る → API が自動で生成される
- 🔄 テーブルを変更する → API も自動で更新される
- 🚀 すぐに使える → フロントエンドから即座に呼び出し可能
例:articlesテーブルを作ると…
-- テーブルを作成するだけ
CREATE TABLE articles (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
content TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
-- RLSを有効化(セキュリティ向上)
ALTER TABLE articles ENABLE ROW LEVEL SECURITY;
自動で以下のAPIが生成されます:
✅ GET /articles # 記事一覧取得
✅ GET /articles?id=eq.1 # 特定記事取得
✅ POST /articles # 記事作成
✅ PATCH /articles?id=eq.1 # 記事更新
✅ DELETE /articles?id=eq.1 # 記事削除
メリット:
- ⚡ 即座に完成: テーブル作成と同時にAPI完成
- 🎯 一貫性: 全てのAPIが同じ品質で統一
- 🔧 自動更新: テーブル変更でAPIも自動更新
- 🚀 高性能: 最適化済みのSQLクエリを直接実行
従来のAPI開発手法との違い
| 項目 | PostgREST | 従来のフレームワーク | |——|:———:|:——————:| | コード量 | ✅ ゼロ | ❌ 大量(CRUD毎に実装) | | 開発時間 | ✅ 即時 | ❌ 数日〜数週間 | | 一貫性 | ✅ 自動保証 | ⚠️ 手動実装が必要 | | バグリスク | ✅ 最小 | ❌ 実装ミスのリスク | | 保守性 | ✅ スキーマ駆動 | ⚠️ コードとDBの同期が必要 | | パフォーマンス | ✅ 最適化済み | ⚠️ ORM経由で性能低下 |
Express.js/FastAPIとの比較例:
// 従来の手法 (Express.js)
app.get('/tasks', async (req, res) => {
const { limit, offset, completed } = req.query;
let query = 'SELECT * FROM tasks WHERE 1=1';
const params = [];
if (completed !== undefined) {
query += ' AND completed = $' + (params.length + 1);
params.push(completed === 'true');
}
if (limit) {
query += ' LIMIT $' + (params.length + 1);
params.push(parseInt(limit));
}
if (offset) {
query += ' OFFSET $' + (params.length + 1);
params.push(parseInt(offset));
}
try {
const result = await db.query(query, params);
res.json(result.rows);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// PostgREST(自動生成)
// GET /tasks?completed=eq.true&limit=10&offset=0
// → 上記と同等の機能が自動で利用可能
-- テーブル作成でAPIが自動生成される
CREATE TABLE tasks (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
completed BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
対応するREST APIが自動生成:
GET /rest/v1/tasks # SELECT
POST /rest/v1/tasks # INSERT
PATCH /rest/v1/tasks?id=eq.1 # UPDATE
DELETE /rest/v1/tasks?id=eq.1 # DELETE
クエリ翻訳メカニズム:
- URLパラメーター → WHERE句変換
- HTTPヘッダー → SELECT句・JOIN句構築
- JSONペイロード → INSERT/UPDATE値設定
Realtime: リアルタイム通信
何を行うものか
Supabase Realtimeは、データベースの変更をリアルタイムでクライアントアプリケーションに配信するElixir/Phoenix製のWebSocketサーバーです。PostgreSQLのWAL(Write-Ahead Log)を監視し、データ変更を即座に接続されたクライアントに通知します。
実装基盤: Elixir/Phoenix Channels(高並行性・耐障害性に優れたアクター モデル) 通信プロトコル: WebSocket (Phoenix Socket.js)
何がもたらされるか
- リアルタイム同期: データ変更が全クライアントに即座に反映
- スケーラブル通信: 数千〜数万の同時接続をサポート
- 低レイテンシ: データベース変更からクライアント反映まで数ミリ秒
- 自動再接続: ネットワーク障害時の自動復旧機能
従来のリアルタイム実装との違い
| 項目 | Supabase Realtime | Socket.io + Redis | WebSocket + Polling | |——|:—————–:|:—————–:|:——————-:| | 設定の複雑さ | ✅ ゼロ設定 | ❌ 複雑な設定 | ⚠️ 中程度 | | データ同期 | ✅ 自動(DB駆動) | ❌ 手動実装 | ❌ 手動実装 | | スケーラビリティ | ✅ 水平拡張 | ⚠️ Redis制限 | ❌ 単一サーバー | | 障害復旧 | ✅ 自動 | ⚠️ 部分的 | ❌ 手動 | | メモリ効率 | ✅ 高効率 | ⚠️ 中程度 | ❌ 非効率 |
従来手法との比較例:
// 従来の実装(Socket.io + 手動通知)
// 1. データ更新
await db.query('UPDATE tasks SET completed = true WHERE id = ?', [taskId]);
// 2. 手動でクライアントに通知(実装が必要)
io.emit('task_updated', {
id: taskId,
completed: true,
updated_at: new Date()
});
// 3. クライアント側でも手動実装が必要
socket.on('task_updated', (data) => {
// UIの更新処理を手動実装
updateTaskInUI(data);
});
// Supabase Realtime(自動)
// 1. データ更新(通常の方法)
await supabase
.from('tasks')
.update({ completed: true })
.eq('id', taskId);
// 2. 通知は自動(設定のみ)
const subscription = supabase
.channel('tasks_channel')
.on('postgres_changes', {
event: 'UPDATE',
schema: 'public',
table: 'tasks'
}, (payload) => {
// データ変更が自動で通知される
console.log('Updated:', payload.new);
})
.subscribe();
// → 実装コードが90%削減
データフロー(WALベース):
- PostgreSQLがWAL(Write-Ahead Log)に変更記録: 全てのデータ変更が自動記録
- Realtime ServerがWALをtail監視: リアルタイムでログを読み取り
- 変更をWebSocketで配信: 関連するクライアントに即座に通知
- クライアント側で自動反映: アプリケーションのUIが自動更新
// リアルタイム購読の内部実装
const subscription = supabase
.channel('tasks_channel')
.on('postgres_changes',
{ event: '*', schema: 'public', table: 'tasks' },
(payload) => console.log(payload)
)
.subscribe();
WAL設定(PostgreSQL):
-- レプリケーション設定確認
SHOW wal_level; -- replica以上が必要
SHOW max_replication_slots;
SHOW max_wal_senders;
1.2 Row Level Security (RLS) の設計思想
セキュリティモデル
RLSはPostgreSQLの機能を活用した行レベルアクセス制御です。
基本概念:
- Policy: 行レベルでの条件式
- Role: PostgreSQLユーザーロール
- Context:
auth.uid()
などの実行時情報
ポリシー実装パターン
1. オーナーシップベース
-- テーブル設定
CREATE TABLE user_documents (
id BIGSERIAL PRIMARY KEY,
user_id UUID REFERENCES auth.users(id),
title TEXT NOT NULL,
content TEXT
);
ALTER TABLE user_documents ENABLE ROW LEVEL SECURITY;
-- ポリシー定義
CREATE POLICY "Users can view own documents" ON user_documents
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can insert own documents" ON user_documents
FOR INSERT WITH CHECK (auth.uid() = user_id);
2. ロールベース
-- 組織テーブル
CREATE TABLE organizations (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE user_org_roles (
user_id UUID REFERENCES auth.users(id),
org_id BIGINT REFERENCES organizations(id),
role TEXT CHECK (role IN ('admin', 'member', 'viewer')),
PRIMARY KEY (user_id, org_id)
);
-- 複合ポリシー
CREATE POLICY "Org members can view" ON organizations
FOR SELECT USING (
EXISTS (
SELECT 1 FROM user_org_roles
WHERE user_id = auth.uid()
AND org_id = organizations.id
)
);
パフォーマンス考慮事項
インデックス戦略:
-- RLSクエリ最適化用インデックス
CREATE INDEX idx_user_documents_user_id ON user_documents(user_id);
CREATE INDEX idx_user_org_roles_user_id ON user_org_roles(user_id);
実行計画確認:
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM user_documents WHERE title ILIKE '%report%';
1.3 セルフホスト vs クラウドの運用観点比較
技術的差異
要素 | セルフホスト | Supabase Cloud |
---|---|---|
インフラ管理 | 手動 | フルマネージド |
スケーリング | 手動設定 | 自動 + 手動 |
バックアップ | 独自実装必要 | 自動 + PITR |
監視 | Prometheus等を構築 | 組み込み済み |
アップデート | 手動実行 | 自動適用 |
コスト分析
セルフホスト:
月額運用コスト =
インフラ費用 +
運用工数 × 時間単価 +
可用性リスク × 影響度
推定工数:
- 初期構築: 40-80時間
- 月次運用: 8-16時間
- 障害対応: 不定期(2-8時間/件)
セキュリティ責任分界点
セルフホスト責任範囲:
- OS・ミドルウェア更新
- ネットワーク設定
- データ暗号化
- アクセス制御
- ログ監査
クラウド責任範囲(Supabase):
- 物理インフラ
- プラットフォーム更新
- 基盤監視
- 自動バックアップ
1.4 開発環境構築(Docker Compose)
プロジェクト構造
supabase-project/
├── docker-compose.yml
├── supabase/
│ ├── config.toml
│ ├── seed.sql
│ └── migrations/
│ └── 001_initial_schema.sql
├── apps/
│ ├── flet-client/
│ ├── edge-functions/
│ └── fastapi-server/
└── scripts/
└── setup.sh
Docker Compose設定
# docker-compose.yml
version: '3.8'
services:
supabase-db:
image: supabase/postgres:15.1.0.117
environment:
POSTGRES_PASSWORD: your-super-secret-and-long-postgres-password
POSTGRES_DB: postgres
volumes:
- ./supabase/volumes/db/data:/var/lib/postgresql/data
- ./supabase/volumes/db/init:/docker-entrypoint-initdb.d
ports:
- "54322:5432"
supabase-studio:
image: supabase/studio:20231123-fb9c70e
environment:
SUPABASE_URL: http://localhost:54321
SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY}
SUPABASE_SERVICE_KEY: ${SUPABASE_SERVICE_KEY}
ports:
- "3000:3000"
supabase-kong:
image: kong:2.8.1
environment:
KONG_DATABASE: "off"
KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
volumes:
- ./supabase/volumes/api/kong.yml:/var/lib/kong/kong.yml:ro
ports:
- "54321:8000"
- "54323:8001"
supabase-auth:
image: supabase/gotrue:v2.99.0
environment:
GOTRUE_API_HOST: 0.0.0.0
GOTRUE_API_PORT: 9999
GOTRUE_DB_DRIVER: postgres
GOTRUE_DB_DATABASE_URL: postgres://postgres:your-super-secret-and-long-postgres-password@supabase-db:5432/postgres?search_path=auth
GOTRUE_SITE_URL: http://localhost:3000
GOTRUE_URI_ALLOW_LIST: http://localhost:3000
GOTRUE_JWT_SECRET: your-super-secret-jwt-token-with-at-least-32-characters-long
depends_on:
- supabase-db
supabase-rest:
image: postgrest/postgrest:v11.2.0
environment:
PGRST_DB_URI: postgres://postgres:your-super-secret-and-long-postgres-password@supabase-db:5432/postgres
PGRST_DB_SCHEMAS: public,storage,graphql_public
PGRST_DB_ANON_ROLE: anon
PGRST_JWT_SECRET: your-super-secret-jwt-token-with-at-least-32-characters-long
depends_on:
- supabase-db
supabase-realtime:
image: supabase/realtime:v2.25.35
environment:
PORT: 4000
DB_HOST: supabase-db
DB_PORT: 5432
DB_USER: postgres
DB_PASSWORD: your-super-secret-and-long-postgres-password
DB_NAME: postgres
DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'
DB_ENC_KEY: supabaserealtime
API_JWT_SECRET: your-super-secret-jwt-token-with-at-least-32-characters-long
SECRET_KEY_BASE: UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq
depends_on:
- supabase-db
command: >
bash -c "
/app/bin/migrate &&
/app/bin/realtime eval 'Realtime.Release.seeds(Realtime.Repo)' &&
/app/bin/server
"
初期化スクリプト
#!/bin/bash
# scripts/setup.sh
set -e
echo "🚀 Supabase開発環境セットアップ開始"
# 環境変数設定
if [ ! -f .env ]; then
echo "📝 環境変数ファイル作成"
cat > .env << EOF
SUPABASE_URL=http://localhost:54321
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
SUPABASE_SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU
EOF
fi
# Docker環境起動
echo "🐳 Docker Compose起動"
docker-compose up -d
# データベース接続待機
echo "⏳ PostgreSQL起動待機"
until docker-compose exec supabase-db pg_isready; do
sleep 2
done
# マイグレーション実行
echo "📊 初期スキーマ作成"
docker-compose exec supabase-db psql -U postgres -d postgres -f /docker-entrypoint-initdb.d/001_initial_schema.sql
echo "✅ セットアップ完了"
echo "🌐 Supabase Studio: http://localhost:3000"
echo "🔗 API Endpoint: http://localhost:54321"
基本スキーマ
-- supabase/migrations/001_initial_schema.sql
-- 拡張機能有効化
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- プロファイルテーブル
CREATE TABLE profiles (
id UUID REFERENCES auth.users(id) PRIMARY KEY,
display_name TEXT,
avatar_url TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- RLS有効化
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
-- ポリシー設定
CREATE POLICY "Users can view own profile" ON profiles
FOR SELECT USING (auth.uid() = id);
CREATE POLICY "Users can update own profile" ON profiles
FOR UPDATE USING (auth.uid() = id);
-- 関数: プロファイル自動作成
CREATE OR REPLACE FUNCTION handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO profiles (id, display_name)
VALUES (NEW.id, NEW.email);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- トリガー: ユーザー作成時の自動プロファイル作成
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION handle_new_user();
環境確認
# 各サービス状態確認
curl -s http://localhost:54321/rest/v1/ | jq '.version'
curl -s http://localhost:54321/auth/v1/settings | jq '.external'
# データベース接続確認
psql "postgresql://postgres:your-super-secret-and-long-postgres-password@localhost:54322/postgres" -c "SELECT version();"
🔧 トラブルシューティング
よくある問題と解決方法
1. 接続エラー
問題: “Failed to connect to Supabase”
// エラー例
Error: getaddrinfo ENOTFOUND your-project.supabase.co
解決方法:
// 1. 環境変数を確認
console.log('SUPABASE_URL:', process.env.SUPABASE_URL);
console.log('SUPABASE_ANON_KEY:', process.env.SUPABASE_ANON_KEY);
// 2. 正しい接続方法
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.SUPABASE_URL || '',
process.env.SUPABASE_ANON_KEY || ''
);
// 3. 接続テスト
async function testConnection() {
try {
const { data, error } = await supabase
.from('profiles')
.select('count')
.limit(1);
if (error) throw error;
console.log('Connection successful');
} catch (error) {
console.error('Connection failed:', error);
}
}
2. RLSポリシーエラー
問題: “new row violates row-level security policy”
デバッグ方法:
-- RLSポリシーの確認
SELECT
schemaname,
tablename,
policyname,
permissive,
roles,
cmd,
qual,
with_check
FROM pg_policies
WHERE tablename = 'your_table_name';
-- ポリシーのテスト
SET LOCAL role TO 'authenticated';
SET LOCAL request.jwt.claim.sub TO 'user-uuid-here';
SELECT * FROM your_table_name;
3. Docker環境の問題
問題: “Container exited with code 1”
チェックリスト:
# 1. ログ確認
docker-compose logs supabase-db
docker-compose logs supabase-auth
# 2. ポート競合確認
sudo lsof -i :54321
sudo lsof -i :54322
# 3. 環境リセット
docker-compose down -v
docker-compose up -d
# 4. ヘルスチェック
curl http://localhost:54321/rest/v1/
4. API認証エラー
問題: “Invalid API key”
解決方法:
// ヘッダー確認
const headers = {
'apikey': process.env.SUPABASE_ANON_KEY,
'Authorization': `Bearer ${process.env.SUPABASE_ANON_KEY}`,
'Content-Type': 'application/json',
'Prefer': 'return=minimal'
};
// エラーハンドリング付きリクエスト
async function apiRequest() {
try {
const response = await fetch(`${SUPABASE_URL}/rest/v1/profiles`, {
headers
});
if (!response.ok) {
const error = await response.text();
throw new Error(`API Error: ${response.status} - ${error}`);
}
return await response.json();
} catch (error) {
console.error('Request failed:', error);
throw error;
}
}
パフォーマンスのトラブルシューティング
1. 遅いクエリの特定
-- 遅いクエリの確認
SELECT
query,
calls,
total_time,
mean_time,
max_time
FROM pg_stat_statements
WHERE mean_time > 100 -- 100ms以上
ORDER BY mean_time DESC
LIMIT 10;
-- インデックスの確認
SELECT
schemaname,
tablename,
indexname,
indexdef
FROM pg_indexes
WHERE tablename = 'your_table_name';
2. リアルタイム接続の問題
// 接続状態の監視
const subscription = supabase
.channel('test_channel')
.on('system', { event: '*' }, (payload) => {
console.log('System event:', payload);
})
.on('postgres_changes',
{ event: '*', schema: 'public' },
(payload) => console.log('Change:', payload)
)
.subscribe((status, err) => {
if (status === 'SUBSCRIBED') {
console.log('Successfully subscribed');
} else if (status === 'CHANNEL_ERROR') {
console.error('Subscription error:', err);
// 再接続ロジック
setTimeout(() => {
subscription.subscribe();
}, 5000);
}
});
まとめ
本章では、Supabaseの内部アーキテクチャと基本概念を技術的観点から解説しました。トラブルシューティングセクションで示したように、問題解決のためには各コンポーネントの役割を理解することが重要です。次章では、これらの基盤上での認証・認可設計について詳述します。
📝 Chapter 1 学習まとめ
📊 学習進捗トラッキング
この章の学習進捗を以下のチェックリストで確認してください:
🌱 基礎理解(必須)
- Webアプリに必要な基本要素(データベース・API・認証・リアルタイム)を説明できる
- Supabaseの3つの基本コンポーネント(PostgreSQL・PostgREST・Realtime)の役割を理解した
- 従来の開発手法とSupabaseの違い・メリットを説明できる
- RLS(Row Level Security)の基本概念を理解した
🚀 応用理解(推奨)
- PostgreSQLのACID保証・拡張機能の重要性を理解した
- PostgRESTのAPI自動生成メカニズムを理解した
- Realtimeのメッセージング・WebSocket通信の仕組みを理解した
- Kong Gatewayの役割・API ルーティングを理解した
💪 発展理解(上級者向け)
- WAL(Write-Ahead Log)ベースのリアルタイム通信の仕組みを理解した
- Supabase固有の拡張機能(pg_graphql・pgsodium等)の用途を理解した
- Docker Compose環境でのSupabase開発環境を構築できる
- 他のBaaS(Firebase・AWS Amplify等)との技術的差異を説明できる
🔧 実践スキル(確認推奨)
- Supabase Studioの基本操作ができる
- 簡単なテーブル作成・RLSポリシー設定ができる
- 環境変数・接続設定を理解した
- 基本的なSQLクエリ・PostgREST APIを試すことができる
✅ 習得できたスキル
- ✅ Supabaseの基本構成(PostgreSQL + PostgREST + Realtime)理解
- ✅ Row Level Security(RLS)の基本概念理解
- ✅ Docker環境でのSupabase開発環境構築
- ✅ Webアプリケーションに必要な要素の理解
🎯 重要ポイントの復習
| 概念 | 説明 | 実際の例 |
|:—–|:—–|:———|
| PostgreSQL | データベース本体(記事データ保存) | ブログ記事・ユーザー情報の保存 |
| PostgREST | APIサーバー(REST APIを自動生成) | /posts
APIで記事取得 |
| Realtime | リアルタイム更新(変更を即座に通知) | 記事投稿の瞬間にページ更新 |
| RLS | 行レベルセキュリティ(データアクセス制御) | 作成者のみが自分の記事を編集可能 |
🔄 次章への準備
Chapter 2で学ぶ認証システムの基礎となる概念を確認しましょう:
- ✅ ユーザー認証の必要性理解
- ✅ JWT(JSONWebToken)の基本概念
- ✅ RLSポリシーの役割理解
💡 実践演習
🌱 基礎演習(必須)
- 概念整理: 以下の用語を自分の言葉で説明してみてください
- PostgreSQL
- PostgREST
- Realtime
- RLS(Row Level Security)
- 仕組み理解: ブログアプリの例で、「記事を投稿してからリアルタイムで他のユーザーに表示される」までの流れを説明してください
🚀 応用演習(推奨)
-
比較分析: 従来の開発手法(例:React + Express + MySQL)とSupabaseを使った開発の違いを表にまとめてください
-
設計思考: あなたが作りたいアプリケーションを想定して、Supabaseのどの機能が必要かリストアップしてください
💪 発展演習(上級者向け)
-
環境構築: Docker Composeを使ってローカルSupabase環境を構築し、基本的な動作確認を行ってください
-
技術調査: Supabase以外のBaaS(Firebase・AWS Amplify等)と比較して、どのような場面でSupabaseが有利かまとめてください
📝 演習の解答例・解説
解答例を確認(クリックして展開)
**1. 概念整理の解答例** - **PostgreSQL**: データを保存・管理するデータベース。高機能で安全性が高い - **PostgREST**: データベースのテーブルから自動でAPIを作ってくれるツール - **Realtime**: データが変更されたら即座にアプリに通知してくれる仕組み - **RLS**: データベースレベルで「誰がどのデータを見られるか」を制御する機能 **2. 仕組み理解の解答例** 1. ユーザーがブログ記事を投稿 → PostgreSQLに保存 2. PostgreSQLがWAL(変更ログ)に記録 3. Realtimeサーバーが変更を検知 4. WebSocketで他のユーザーのブラウザに通知 5. 自動でUIが更新される🚀 次章予告:認証システムの実装
Chapter 2では、「病院のカルテシステム」を例に、以下を学習します:
- 🔐 JWT認証: 医師・看護師・患者の身分確認システム
- 🛡️ RLSポリシー: 「患者は自分のカルテのみ閲覧可能」な制御
- 👥 ロール管理: 医師・看護師・管理者の権限分けシステム
💡 実用例: 「患者Aさんは自分のカルテしか見られないが、担当医師は担当患者全員のカルテを見られる」システムを作成
📍 ナビゲーション
- 📚 目次: 📖 学習ガイド
- ➡️ 次の章: Chapter 2: 認証システムの実装
- 🏠 関連章: Chapter 3-5: アーキテクチャパターン
-
🔧 リソース: 動作検証 トラブルシューティング