Supabase
データベースプラットフォーム
Supabase
概要
Supabaseは、PostgreSQLをベースとしたオープンソースのFirebase代替プラットフォームです。リアルタイムデータベース、認証、ストレージ、Edge Functions、Row Level Security (RLS) を統合し、モダンなWebアプリケーション開発に必要な機能を一元的に提供します。標準SQLサポートと透明性の高いオープンソースアプローチで、開発者コミュニティから強い支持を獲得しています。
詳細
PostgreSQLベースのデータベース
SupabaseはPostgreSQLをコアとして、リレーショナルデータベースの堅牢性と標準SQLの完全サポートを提供します。NoSQLライクな柔軟性も併せ持ち、JSON/JSONBサポートにより複雑なデータ構造も効率的に処理できます。
リアルタイム機能
データベースの変更をリアルタイムで監視し、WebSocketを通じてクライアントに即座に通知します。チャットアプリケーション、ライブダッシュボード、コラボレーションツールの構築が容易になります。
統合認証システム
OAuth プロバイダー(Google、GitHub、Discord など)、メール認証、マジックリンク、多要素認証をサポートします。Row Level Security と組み合わせることで、セキュアなマルチテナントアプリケーションを構築できます。
Edge Functions
Deno ランタイムベースのサーバーレス関数により、グローバルに分散されたエッジでカスタムロジックを実行できます。データベースに近い場所での処理により、低レイテンシを実現します。
メリット・デメリット
メリット
- オープンソース: 完全にオープンソースで、ベンダーロックインのリスクが低い
- PostgreSQL互換: 標準SQLとPostgreSQLの豊富な機能をフル活用可能
- リアルタイム機能: データベース変更の即座な同期とライブアップデート
- 統合プラットフォーム: データベース、認証、ストレージ、Functions を一元管理
- Row Level Security: きめ細かいアクセス制御でセキュアなアプリケーション構築
- 自由度の高いホスティング: セルフホストまたはクラウドホスト選択可能
- 豊富なSDK: JavaScript、TypeScript、Python、Go など多言語サポート
デメリット
- 相対的に新しいプラットフォーム: Firebaseと比較して歴史が浅い
- PostgreSQL学習コスト: NoSQLに慣れた開発者にはSQL学習が必要
- リアルタイム制限: 大規模なリアルタイム機能では性能限界がある可能性
- コンプライアンス: 一部の規制要件では追加設定が必要
- 複雑な設定: 高度な機能利用時にはPostgreSQLの深い知識が必要
参考ページ
- 公式サイト: https://supabase.com/
- ドキュメント: https://supabase.com/docs/
- GitHub: https://github.com/supabase/supabase
- コミュニティ: https://supabase.com/community/
- ブログ: https://supabase.com/blog/
実装の例
セットアップ
# Supabase CLIをインストール
npm install -g supabase
# プロジェクトを初期化
supabase init my-project
cd my-project
# ローカル環境を開始
supabase start
# JavaScriptクライアントをインストール
npm install @supabase/supabase-js
スキーマ設計
-- ユーザープロファイルテーブル
CREATE TABLE profiles (
id UUID REFERENCES auth.users NOT NULL PRIMARY KEY,
updated_at TIMESTAMP WITH TIME ZONE,
username TEXT UNIQUE,
avatar_url TEXT,
website TEXT,
CONSTRAINT username_length CHECK (char_length(username) >= 3)
);
-- RLSを有効化
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
-- ポリシーを設定
CREATE POLICY "Public profiles are viewable by everyone."
ON profiles FOR SELECT
USING ( true );
CREATE POLICY "Users can insert their own profile."
ON profiles FOR INSERT
WITH CHECK ( auth.uid() = id );
CREATE POLICY "Users can update own profile."
ON profiles FOR UPDATE
USING ( auth.uid() = id );
-- 投稿テーブル
CREATE TABLE posts (
id BIGSERIAL PRIMARY KEY,
user_id UUID NOT NULL REFERENCES auth.users(id),
title TEXT NOT NULL,
content TEXT,
published BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- リアルタイム機能を有効化
ALTER PUBLICATION supabase_realtime ADD TABLE posts;
データ操作
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = 'https://your-project.supabase.co'
const supabaseKey = process.env.SUPABASE_ANON_KEY
const supabase = createClient(supabaseUrl, supabaseKey)
// ユーザー登録
async function signUp(email, password) {
const { data, error } = await supabase.auth.signUp({
email: email,
password: password,
})
if (error) {
throw new Error(error.message)
}
return data
}
// データの挿入
async function createPost(title, content, published = false) {
const { data, error } = await supabase
.from('posts')
.insert([
{
title,
content,
published,
user_id: (await supabase.auth.getUser()).data.user?.id
}
])
.select()
if (error) {
throw new Error(error.message)
}
return data[0]
}
// データの取得
async function getPosts(limit = 10) {
const { data, error } = await supabase
.from('posts')
.select(`
*,
profiles (
username,
avatar_url
)
`)
.eq('published', true)
.order('created_at', { ascending: false })
.limit(limit)
if (error) {
throw new Error(error.message)
}
return data
}
// リアルタイム購読
function subscribeToPostChanges(callback) {
const subscription = supabase
.channel('posts')
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'posts'
},
(payload) => {
console.log('New post:', payload.new)
callback(payload.new)
}
)
.subscribe()
return subscription
}
スケーリング
// データベース接続プールの設定
const supabase = createClient(supabaseUrl, supabaseKey, {
db: {
schema: 'public',
},
auth: {
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: true
},
realtime: {
params: {
eventsPerSecond: 10,
},
},
})
// 読み取り専用レプリカの活用
async function getAnalytics() {
const { data, error } = await supabase
.from('posts')
.select('created_at, published')
.gte('created_at', new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString())
if (error) throw error
// データ集計処理
const analytics = data.reduce((acc, post) => {
const date = post.created_at.split('T')[0]
if (!acc[date]) {
acc[date] = { total: 0, published: 0 }
}
acc[date].total++
if (post.published) {
acc[date].published++
}
return acc
}, {})
return analytics
}
// バッチ処理の最適化
async function batchUpdatePosts(postIds, updates) {
const { data, error } = await supabase
.from('posts')
.update(updates)
.in('id', postIds)
.select()
if (error) throw error
return data
}
バックアップ・復旧
# データベースのバックアップ
supabase db dump > backup.sql
# 特定テーブルのバックアップ
supabase db dump --table posts > posts_backup.sql
# マイグレーションの管理
supabase migration new create_posts_table
supabase migration new add_posts_rls
# マイグレーションの適用
supabase db push
# 本番環境との同期
supabase link --project-ref your-project-ref
supabase db push --dry-run # プレビュー
supabase db push # 適用
統合
// Next.js App Router での統合
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
export async function GET() {
const cookieStore = cookies()
const supabase = createRouteHandlerClient({ cookies: () => cookieStore })
const { data: posts, error } = await supabase
.from('posts')
.select('*')
.eq('published', true)
if (error) {
return Response.json({ error: error.message }, { status: 500 })
}
return Response.json(posts)
}
// React での認証状態管理
import { useEffect, useState } from 'react'
import { useSupabaseClient, useUser } from '@supabase/auth-helpers-react'
function AuthComponent() {
const supabase = useSupabaseClient()
const user = useUser()
const [loading, setLoading] = useState(false)
const handleSignIn = async (email, password) => {
setLoading(true)
const { error } = await supabase.auth.signInWithPassword({
email,
password,
})
if (error) {
alert(error.message)
}
setLoading(false)
}
const handleSignOut = async () => {
const { error } = await supabase.auth.signOut()
if (error) {
alert(error.message)
}
}
if (user) {
return (
<div>
<p>Welcome, {user.email}!</p>
<button onClick={handleSignOut}>Sign Out</button>
</div>
)
}
return (
<form onSubmit={(e) => {
e.preventDefault()
const formData = new FormData(e.target)
handleSignIn(
formData.get('email'),
formData.get('password')
)
}}>
<input name="email" type="email" placeholder="Email" required />
<input name="password" type="password" placeholder="Password" required />
<button type="submit" disabled={loading}>
{loading ? 'Loading...' : 'Sign In'}
</button>
</form>
)
}
// Edge Functions の例
import { serve } from 'https://deno.land/[email protected]/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
serve(async (req) => {
const { name } = await req.json()
const supabaseClient = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
)
const { data } = await supabaseClient
.from('profiles')
.select('*')
.eq('username', name)
.single()
return new Response(
JSON.stringify({ user: data }),
{ headers: { 'Content-Type': 'application/json' } },
)
})