Supabase

BaaSデータベースPostgreSQLリアルタイム認証ストレージオープンソースFirebase代替EdgeFunctions

BaaSプラットフォーム

Supabase

概要

Supabaseは、オープンソースのFirebase代替として位置づけられている、次世代のBaaS(Backend as a Service)プラットフォームです。PostgreSQLをベースとしたリレーショナルデータベース、リアルタイム機能、認証、ストレージ、Edge Functionsなど、現代的なWebアプリケーション開発に必要なバックエンド機能をすべて提供します。

2020年に設立され、オープンソース志向の開発者コミュニティに急速に普及しており、特にPostgreSQLの堅牢性とオープンソースアプローチを求める開発者に支持されています。SQL-ファーストのアプローチとPostgreSQLの強力な機能を最大限活用できる設計が特徴的です。

詳細

核となる技術要素

PostgreSQL中心のアーキテクチャ

  • 完全管理されたPostgreSQL 15+を採用
  • Row Level Security(RLS)による細かな権限制御
  • 豊富なPostgreSQL拡張機能に対応
  • SQLクエリの直接実行とPostgREST APIの自動生成

リアルタイム機能

  • WebSocketベースのリアルタイムデータ同期
  • Broadcast:チャンネル間でのメッセージ配信
  • Presence:ユーザーのオンライン状態管理
  • Postgres Changes:データベース変更の自動通知

認証システム

  • JWT(JSON Web Token)ベースの認証
  • 30+のOAuthプロバイダー対応(Google、GitHub、Apple等)
  • Magic Link、SMS OTP、メール認証
  • Multi-Factor Authentication(MFA)サポート

ストレージ機能

  • S3互換のオブジェクトストレージ
  • 画像変換とリサイズの自動処理
  • CDN統合による高速配信
  • きめ細かなアクセス制御

Edge Functions

  • Deno 2.1ランタイム対応
  • グローバルエッジデプロイ
  • TypeScript/JavaScriptサポート
  • 2025年新機能:WebSocket、バックグラウンドタスク、エフェメラルストレージ

技術的特徴

開発者体験

  • ダッシュボードからのコード編集・デプロイ
  • AI Assistant統合(SQL生成、パフォーマンス分析)
  • TypeScript型定義の自動生成
  • 複数言語のSDK(JavaScript、Python、Dart、Swift、Kotlin等)

パフォーマンス最適化

  • 地理的ルーティング(2025年4月開始)
  • Dedicated Pooler(PgBouncer)による接続管理
  • 読み込みレプリカ対応
  • クエリ実行計画の可視化

エンタープライズ対応

  • SOC 2 Type II認証取得
  • プロジェクトスコープドロール
  • 組織レベルの権限管理
  • 99.9%のSLA

メリット・デメリット

メリット

SQL-ファーストのアプローチ

  • PostgreSQLの全機能を活用可能
  • 複雑なクエリとJOINに対応
  • インデックス、ビュー、トリガーなどの高度な機能
  • 既存のSQL知識を直接活用

オープンソースと透明性

  • Apache 2.0ライセンス
  • セルフホスティング可能
  • ベンダーロックインの回避
  • コミュニティドリブンの開発

豊富なリアルタイム機能

  • データベース変更の自動検知
  • 低遅延でのデータ同期
  • ブロードキャストとプレゼンス機能
  • WebSocketサポート

開発効率の向上

  • 型安全なAPI自動生成
  • AI Assistant支援
  • ダッシュボードでの直接開発
  • 豊富なテンプレートと例

コスト効率

  • 無料プランでも十分な機能
  • 使用量ベースの柔軟な課金
  • オープンソースによる選択肢

デメリット

学習曲線

  • PostgreSQLとSQLの知識が必要
  • Row Level Security(RLS)の設計複雑性
  • リアルタイム機能の設定が初心者には難しい

制約と限界

  • Firebaseほどの成熟度はまだない
  • NoSQLライクな柔軟性は限定的
  • 一部機能がベータ版段階

運用面での考慮事項

  • セルフホスティング時の運用負荷
  • 大規模トラフィック時のチューニング必要性
  • バックアップとディザスタリカバリの計画要

参考ページ

書き方の例

1. 基本セットアップとプロジェクト設定

# Supabase CLIのインストール
npm install -g @supabase/cli

# 新しいプロジェクトの初期化
supabase init my-project
cd my-project

# ローカル開発環境の起動
supabase start

# 本番プロジェクトとのリンク
supabase link --project-ref your-project-id
// TypeScript/JavaScript クライアントの初期化
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = 'https://your-project.supabase.co'
const supabaseKey = 'your-anon-key'

const supabase = createClient(supabaseUrl, supabaseKey)

// 型安全なクライアント設定
export interface Database {
  public: {
    Tables: {
      profiles: {
        Row: {
          id: string
          username: string | null
          avatar_url: string | null
          website: string | null
          updated_at: string | null
        }
        Insert: {
          id: string
          username?: string | null
          avatar_url?: string | null
          website?: string | null
          updated_at?: string | null
        }
        Update: {
          id?: string
          username?: string | null
          avatar_url?: string | null
          website?: string | null
          updated_at?: string | null
        }
      }
    }
  }
}

const typedSupabase = createClient<Database>(supabaseUrl, supabaseKey)

2. データベース操作(PostgreSQL)

-- テーブルの作成とRow Level Security設定
CREATE TABLE public.profiles (
  id UUID REFERENCES auth.users NOT NULL,
  updated_at TIMESTAMP WITH TIME ZONE,
  username TEXT UNIQUE,
  avatar_url TEXT,
  website TEXT,
  full_name TEXT,
  PRIMARY KEY (id),
  CONSTRAINT username_length CHECK (char_length(username) >= 3)
);

-- Row Level Security有効化
ALTER TABLE public.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 ((SELECT auth.uid()) = id);

CREATE POLICY "Users can update own profile."
  ON profiles FOR UPDATE
  USING ((SELECT auth.uid()) = id);
// TypeScript データベースクエリ
import { supabase } from './supabase'

// レコードの作成
const { data, error } = await supabase
  .from('profiles')
  .insert([
    {
      id: user.id,
      username: 'example_user',
      full_name: 'Example User',
      avatar_url: '/default-avatar.png'
    }
  ])
  .select()

// 複雑なクエリ(JOIN、フィルター、ソート)
const { data: postsWithAuthors } = await supabase
  .from('posts')
  .select(`
    id,
    title,
    content,
    created_at,
    profiles!inner (
      username,
      avatar_url
    )
  `)
  .eq('status', 'published')
  .order('created_at', { ascending: false })
  .limit(10)

// リアルタイムクエリ
const { data: todos } = await supabase
  .from('todos')
  .select('*')
  .eq('user_id', user.id)
  .order('created_at', { ascending: false })

3. 認証とユーザー管理

// メール/パスワード認証
const signUp = async (email: string, password: string) => {
  const { data, error } = await supabase.auth.signUp({
    email,
    password,
    options: {
      emailRedirectTo: `${window.location.origin}/auth/callback`
    }
  })
  return { data, error }
}

const signIn = async (email: string, password: string) => {
  const { data, error } = await supabase.auth.signInWithPassword({
    email,
    password
  })
  return { data, error }
}

// OAuth認証(Google例)
const signInWithGoogle = async () => {
  const { data, error } = await supabase.auth.signInWithOAuth({
    provider: 'google',
    options: {
      redirectTo: `${window.location.origin}/auth/callback`
    }
  })
  return { data, error }
}

// Magic Link認証
const sendMagicLink = async (email: string) => {
  const { data, error } = await supabase.auth.signInWithOtp({
    email,
    options: {
      emailRedirectTo: `${window.location.origin}/auth/callback`
    }
  })
  return { data, error }
}

// セッション管理
const getSession = async () => {
  const { data: { session } } = await supabase.auth.getSession()
  return session
}

const getUser = async () => {
  const { data: { user } } = await supabase.auth.getUser()
  return user
}

// 認証状態の監視
supabase.auth.onAuthStateChange((event, session) => {
  if (event === 'SIGNED_IN') {
    console.log('User signed in:', session?.user)
  } else if (event === 'SIGNED_OUT') {
    console.log('User signed out')
  }
})

4. リアルタイムサブスクリプション

// データベース変更の監視
const subscribeToProfile = (userId: string) => {
  return supabase
    .channel('profile-changes')
    .on(
      'postgres_changes',
      {
        event: '*',
        schema: 'public',
        table: 'profiles',
        filter: `id=eq.${userId}`
      },
      (payload) => {
        console.log('Profile changed:', payload)
      }
    )
    .subscribe()
}

// ブロードキャスト機能
const channel = supabase.channel('room-1')

// ブロードキャストの送信
const sendMessage = (message: string) => {
  channel.send({
    type: 'broadcast',
    event: 'message',
    payload: { message, user: user.id }
  })
}

// ブロードキャストの受信
channel
  .on('broadcast', { event: 'message' }, (payload) => {
    console.log('Received message:', payload)
  })
  .subscribe()

// プレゼンス機能(オンライン状態管理)
const trackPresence = async () => {
  const presenceTrackStatus = await channel.track({
    user: user.id,
    online_at: new Date().toISOString(),
  })
}

channel
  .on('presence', { event: 'sync' }, () => {
    const newState = channel.presenceState()
    console.log('Online users:', newState)
  })
  .on('presence', { event: 'join' }, ({ key, newPresences }) => {
    console.log('User joined:', key, newPresences)
  })
  .on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
    console.log('User left:', key, leftPresences)
  })
  .subscribe()

5. ファイルストレージとEdge Functions

// ファイルアップロード
const uploadFile = async (file: File, path: string) => {
  const { data, error } = await supabase.storage
    .from('avatars')
    .upload(path, file, {
      cacheControl: '3600',
      upsert: false
    })
  return { data, error }
}

// ファイルのダウンロード
const downloadFile = async (path: string) => {
  const { data, error } = await supabase.storage
    .from('avatars')
    .download(path)
  return { data, error }
}

// 公開URLの取得
const getPublicUrl = (path: string) => {
  const { data } = supabase.storage
    .from('avatars')
    .getPublicUrl(path)
  return data.publicUrl
}

// 署名付きURLの作成
const createSignedUrl = async (path: string, expiresIn: number = 3600) => {
  const { data, error } = await supabase.storage
    .from('private-files')
    .createSignedUrl(path, expiresIn)
  return { data, error }
}

// Edge Function の呼び出し
const callEdgeFunction = async (functionName: string, payload: any) => {
  const { data, error } = await supabase.functions.invoke(functionName, {
    body: payload,
    headers: {
      'Content-Type': 'application/json',
    }
  })
  return { data, error }
}

6. 本番デプロイとマネジメント

# データベーススキーマの適用
supabase db push

# Edgeファンクションのデプロイ
supabase functions deploy hello-world

# シークレット環境変数の設定
supabase secrets set API_KEY=your-secret-key

# データベースマイグレーション
supabase db diff --file migration_name
supabase db reset

# 型定義の生成
supabase gen types typescript --project-id your-project-id > types/database.types.ts
// 本番環境向け設定
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!

const supabase = createClient(supabaseUrl, supabaseAnonKey, {
  auth: {
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: true
  },
  realtime: {
    params: {
      eventsPerSecond: 10,
    },
  },
})

// パフォーマンス監視
const monitorQuery = async () => {
  const start = Date.now()
  const { data, error } = await supabase
    .from('posts')
    .select('*')
    .limit(100)
  
  const duration = Date.now() - start
  console.log(`Query executed in ${duration}ms`)
  
  if (error) {
    console.error('Query error:', error)
  }
  
  return { data, error, duration }
}

// エラーハンドリング
const handleSupabaseError = (error: any) => {
  switch (error.code) {
    case 'PGRST301':
      return 'Resource not found'
    case '23505':
      return 'Duplicate entry'
    case '42501':
      return 'Insufficient privileges'
    default:
      return error.message || 'An unknown error occurred'
  }
}