すべてのプロダクト
Search
ドキュメントセンター

PolarDB:PolarDB Supabase のベストプラクティス — Web アプリケーション

最終更新日:Mar 20, 2026

PolarDB Supabase は、PolarDB for PostgreSQL が提供するフルマネージドな Supabase サービスです。このサービスは、PolarDB for PostgreSQL を活用し、リアルタイムデータベース、RESTful API、GoTrue 身分認証、ファイルストレージ、およびログ収集などの主要機能を統合しています。また、Supabase における複雑なパラメーター管理およびアプリケーションの運用および保守 (O&M) を不要にすることを目的として最適化・強化されており、柔軟でパフォーマンス専有型のバックエンドソリューションを提供します。PolarDB Supabase を使用することで、Web アプリケーション、SaaS プラットフォーム、AI 統合アプリケーションなどのモダンなアプリケーションを迅速に構築できます。

本セクションでは、PolarDB Supabase を使用して会議メモシステムを迅速に構築する方法について説明します。

主な特徴

特徴

説明

完全なデータベース機能

SupabasePolarDB for PostgreSQL 上に構築されており、以下の完全なデータベース機能を提供します:

  • リレーショナルデータベース:複雑なテーブル関係、外部キー制約、トランザクション処理をサポートします。

  • JSONB サポート:メモの内容やアクティビティデータなどの半構造化データを格納できます。

  • 全文検索(Full-Text Search):組み込みの全文検索機能を備えています。

  • 拡張機能のサポート:UUID 生成やタイムスタンプ処理などの PostgreSQL 拡張機能をサポートします。

-- 例:JSONB を使用した複雑なデータの格納
CREATE TABLE notes (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  content JSONB DEFAULT '{}',  -- リッチテキストコンテンツを格納
  activity_data JSONB DEFAULT '{}'  -- アクティビティメタデータを格納
);

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

PostgreSQL の論理レプリケーションに基づくリアルタイムデータ同期を実現します:

  • データベース変更リスナー:INSERT、UPDATE、DELETE イベントを監視します。

  • チャネル管理:複数の独立したチャネルをサポートします。必要に応じてサブスクライブできます。

  • イベントフィルタリング:条件付きフィルターにより、不要なイベントを削減します。

  • 自動再接続:ネットワーク切断時に自動的に再接続します。

// リアルタイムサブスクリプションの例
const channel = supabase
  .channel('meeting-updates')
  .on('postgres_changes', {
    event: 'UPDATE',
    schema: 'public',
    table: 'notes',
    filter: 'meeting_id=eq.123'
  }, (payload) => {
    console.log('メモが更新されました:', payload.new)
  })
  .subscribe()

身分認証(Auth)

組み込みの包括的な身分認証および権限付与システムです:

  • 複数のログイン方式:メールアドレスとパスワード、ソーシャルログイン、マジックリンク。

  • セッション管理:トークンのリフレッシュおよびセッション維持を自動的に処理します。

  • ユーザー管理:ユーザー登録、パスワード再設定、メール検証。

  • 匿名ユーザー:一時的なユーザーアクセスをサポートします。

// 認証の例
const { data: { user }, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'password'
})

行レベルセキュリティ(RLS)

PostgreSQL に基づく行レベルセキュリティポリシーです:

  • 細かい粒度でのアクセス制御:ユーザー、ロール、データ条件に基づいてアクセスを制御します。

  • ポリシー定義:SQL を使用してアクセスルールを定義します。

  • 自動適用:すべてのクエリに対してセキュリティポリシーが自動的に適用されます。

  • パフォーマンス最適化:データフィルタリングをデータベースレベルで実行します。

-- RLS ポリシーの例
CREATE POLICY "ユーザーは自身の会議のみ参照可能" ON meetings
  FOR SELECT USING (auth.uid() = created_by);

CREATE POLICY "ユーザーは自身の会議のみ更新可能" ON meetings
  FOR UPDATE USING (auth.uid() = created_by);

ストレージサービス

PolarDB ファイルシステムに基づくファイルストレージおよび管理機能です:

  • ファイルアップロード:大容量ファイルのアップロードおよび再開可能なアップロードをサポートします。

  • アクセス制御:RLS に基づくファイルアクセス権限を提供します。

  • ファイル管理:フォルダによる整理およびメタデータ管理をサポートします。

// ファイルアップロードの例
const { data, error } = await supabase.storage
  .from('meeting-files')
  .upload('document.pdf', file, {
    cacheControl: '3600',
    upsert: false
  })

Edge Functions

Deno を基盤とするサーバーサイド関数です:

  • TypeScript サポート:TypeScript をネイティブでサポートします。

  • データベースアクセス:Supabase データベースに直接アクセスできます。

  • サードパーティ連携:外部 API の呼び出しおよび Webhook の処理が可能です。

// Edge Function の例
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'

serve(async (req) => {
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL') ?? '',
    Deno.env.get('SUPABASE_ANON_KEY') ?? ''
  )
  
  const { data } = await supabase.from('meetings').select('*')
  return new Response(JSON.stringify(data), {
    headers: { 'Content-Type': 'application/json' }
  })
})

API(REST & GraphQL)

自動生成される API オペレーションです:

  • REST API:CRUD オペレーションを自動生成します。

  • GraphQL:GraphQL クエリをサポートします(Enterprise Edition)。

  • リアルタイム API:リアルタイムクエリおよびサブスクリプションをサポートします。

  • 型安全性:TypeScript 型を提供します。

// REST API の例
const { data, error } = await supabase
  .from('meetings')
  .select('*, notes(*)')
  .eq('id', meetingId)
  .single()

会議メモシステムの構築

会議メモシステムでは、主に以下の機能を使用します:

特徴

説明

PolarDB for PostgreSQL クラスター

データストレージ。主に会議、メモ、タスク、ユーザー状態などのテーブル情報を記録します。

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

コラボレーション機能。メモの共同編集およびユーザーのオンライン状態をリアルタイムで同期します。

行レベルセキュリティ(RLS)

データセキュリティ。ユーザーのアクセス権限を制御します。

ストレージサービス

ファイル管理。会議資料のアップロードおよびダウンロードを行います。

身分認証(Auth)

ユーザー管理。ユーザーのログインおよびセッション管理を行います。

アプリケーション技術スタック

  • フロントエンド:Next.js 15 + React 18 + TypeScript。

  • バックエンドPolarDB SupabasePostgreSQL + 認証 + リアルタイムサブスクリプション + ストレージ)。

  • UI:Tailwind CSS + Radix UI。

  • 状態管理:React Hooks + ローカル状態。

データベース設計

会議メモシステムの主な機能(会議テーブル、メモテーブル、ユーザーのオンライン状態テーブル、タグテーブル、タスクテーブルなど)に対応するシステムテーブルを設計できます。データ整合性を保証するために外部キー制約を使用します。

SQL の例

-- 会議テーブルの作成
CREATE TABLE IF NOT EXISTS meetings (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  title VARCHAR(255) NOT NULL,
  description TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- メモテーブルの作成
CREATE TABLE IF NOT EXISTS notes (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  meeting_id UUID REFERENCES meetings(id) ON DELETE CASCADE,
  content JSONB DEFAULT '{}', -- リッチテキストコンテンツを格納
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- ユーザーのオンライン状態テーブルの作成
CREATE TABLE IF NOT EXISTS user_presence (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  meeting_id UUID REFERENCES meetings(id) ON DELETE CASCADE,
  user_name VARCHAR(100) NOT NULL,
  user_color VARCHAR(7) DEFAULT '#1890ff',
  is_typing BOOLEAN DEFAULT FALSE,
  last_seen TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  UNIQUE(meeting_id, user_name)
);

-- タグテーブルの作成
CREATE TABLE IF NOT EXISTS tags (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  meeting_id UUID REFERENCES meetings(id) ON DELETE CASCADE,
  name VARCHAR(50) NOT NULL,
  color VARCHAR(7) DEFAULT '#1890ff',
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- タスクテーブルの作成
CREATE TABLE IF NOT EXISTS tasks (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  meeting_id UUID REFERENCES meetings(id) ON DELETE CASCADE,
  title VARCHAR(255) NOT NULL,
  description TEXT,
  assignee VARCHAR(100),
  status VARCHAR(20) DEFAULT 'pending',
  due_date TIMESTAMP WITH TIME ZONE,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 会議アクティビティログテーブルの作成
CREATE TABLE IF NOT EXISTS meeting_activities (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  meeting_id UUID REFERENCES meetings(id) ON DELETE CASCADE,
  user_name VARCHAR(100) NOT NULL,
  activity_type VARCHAR(50) NOT NULL, -- 'join', 'leave', 'edit', 'add_tag', 'add_task' など
  activity_data JSONB DEFAULT '{}',
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 会議資料テーブル:meeting_files
CREATE TABLE IF NOT EXISTS meeting_files (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  meeting_id UUID REFERENCES meetings(id) ON DELETE CASCADE,
  file_name VARCHAR(255) NOT NULL,
  file_url TEXT NOT NULL,
  uploader VARCHAR(100),
  uploaded_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  file_size BIGINT,
  mime_type VARCHAR(100)
);

-- 会議 ID 用のインデックスを追加
CREATE INDEX IF NOT EXISTS idx_meeting_files_meeting_id ON meeting_files(meeting_id);

リアルタイムサブスクリプションの構成

リアルタイム機能の有効化

-- リアルタイムサブスクリプション機能を有効化
ALTER PUBLICATION supabase_realtime ADD TABLE meetings;
ALTER PUBLICATION supabase_realtime ADD TABLE notes;
ALTER PUBLICATION supabase_realtime ADD TABLE user_presence;
ALTER PUBLICATION supabase_realtime ADD TABLE tags;
ALTER PUBLICATION supabase_realtime ADD TABLE tasks;
ALTER PUBLICATION supabase_realtime ADD TABLE meeting_activities;
ALTER PUBLICATION supabase_realtime ADD TABLE meeting_files; 

クライアント側のリアルタイムサブスクリプション

// リアルタイムサブスクリプションを管理するカスタム Hook の作成
export function useRealtime(meetingId: string, callbacks: RealtimeCallbacks) {

  const channelsRef = useRef<any[ ]>([ ])


  const cleanup = useCallback(() => {
    channelsRef.current.forEach((channel) => {
      supabase.removeChannel(channel)
    })
    channelsRef.current = []
  }, [])


  useEffect(() => {
    if (!meetingId) return

    // 以前の接続をクリーンアップ
    cleanup()

    // 複数の専用チャネルを作成
    const presenceChannel = supabase
      .channel(`presence:${meetingId}`)
      .on(
        "postgres_changes",
        {
          event: "*",
          schema: "public",
          table: "user_presence",
          filter: `meeting_id=eq.${meetingId}`,
        },
        (payload) => {
          console.log("ユーザーのオンライン状態の変更:", payload)
          if (callbacks.onUserPresenceChange) {
            loadOnlineUsers()
          }
        },
      )
      .subscribe()

    // クリーンアップ用にチャネル参照を保存
    channelsRef.current = [presenceChannel, /* その他のチャネル */]
  }, [meetingId, callbacks])

  return { cleanup }
}

リアルタイムサブスクリプションの設計ガイド

  • 機能の分離:オンライン状態、文書共同編集、タスク通知など、ビジネスシナリオごとに独立したチャネルを分割し、論理的な結合を回避します。

  • イベントフィルタリングfilter パラメーター(例:会議 ID の指定)を活用してイベント範囲を限定し、冗長なデータ送信を削減します。

  • リソースの解放:コンポーネントのアンインストールまたはページ遷移時に、removeChannel を明示的に呼び出してすべてのチャネル接続をクリーンアップし、リソースを解放します。

  • エラー処理:サブスクリプションチャネルのステータスを監視し、切断、タイムアウト、その他の例外発生時に再試行または段階的な機能縮小(degrade gracefully)を実施します。

セキュリティ

行レベルセキュリティ(RLS)

-- RLS の有効化
ALTER TABLE meetings ENABLE ROW LEVEL SECURITY;

-- セキュリティポリシーの作成
CREATE POLICY "ユーザーはすべての会議を参照可能" ON meetings
  FOR SELECT USING (true);

CREATE POLICY "ユーザーは会議を作成可能" ON meetings
  FOR INSERT WITH CHECK (true);

CREATE POLICY "ユーザーは自身の会議のみ更新可能" ON meetings
  FOR UPDATE USING (auth.uid() = created_by);

ユーザー認証

  const login = useCallback(async (email: string, password: string) => {
    try {
      const { data, error } = await supabase.auth.signInWithPassword({ email, password })
      if (error) throw error
      if (data.user) {
        const userData = transformSupabaseUser(data.user)
        setUser(userData)
        localStorage.setItem("meeting_user", JSON.stringify(userData))
        return { success: true, user: userData }
      }
      return { success: false, error: 'ログインに失敗しました' }
    } catch (error: any) {
      return { success: false, error: error.message || 'ログインに失敗しました' }
    }
  }, [])

  const transformSupabaseUser = (supabaseUser: SupabaseUser): User => ({
    id: supabaseUser.id,
    email: supabaseUser.email || null,
    name: supabaseUser.user_metadata?.name || supabaseUser.email?.split('@')[0] || 'ユーザー',
    avatar_url: supabaseUser.user_metadata?.avatar_url || null,
    created_at: supabaseUser.created_at,
    is_anonymous: supabaseUser.user_metadata?.is_anonymous || false,
  })

クライアント統合

クライアント構成

// lib/supabase.ts
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!

export const supabase = createClient(supabaseUrl, supabaseAnonKey)

// 型定義
export interface Meeting {
  id: string
  created_at: string
  title: string
  description: string | null
}

環境変数の管理

# 本番環境の環境変数
NEXT_PUBLIC_SUPABASE_URL=<YOUR_SUPABASE_PUBLIC_URL>
NEXT_PUBLIC_SUPABASE_ANON_KEY=<YOUR_SUPABASE_ANON_KEY>

クイックセットアップ

  1. サンプルプロジェクトコードのダウンロードPolarDB-Supabase-App-Demo

  2. 実行環境の準備: サンプルプロジェクトコードを実行するには、ご利用のローカル環境に Node.js および pnpm がインストールされている必要があります。

    説明
    • Node.js: 公式 Node.js ウェブサイトからダウンロードしてインストールできます。

    • pnpmNode.js をインストールした後、npm を使用して pnpm をグローバルにインストールできます。コマンドは npm install -g pnpm です。

  3. 環境構成: プロジェクトのルートディレクトリに .env.local ファイルを作成し、以下の値をご利用の PolarDB Supabase 構成情報に置き換えてください。

    説明

    クラスターの AI Capabilities > AI Applications リストページで、ご利用の Application ID をクリックすると、アプリケーション詳細ページに移動します。Topology タブおよび Configuration タブで関連する構成情報を確認できます。

    • <YOUR_SUPABASE_PUBLIC_URL> はアプリケーションの パブリックネットワークアドレス です。

    • <YOUR_SUPABASE_ANON_KEY> はアプリケーションパラメーター secret.jwt.anonKey の値です。

    # Production environment variables
    NEXT_PUBLIC_SUPABASE_URL=<YOUR_SUPABASE_PUBLIC_URL>
    NEXT_PUBLIC_SUPABASE_ANON_KEY=<YOUR_SUPABASE_ANON_KEY>
  4. データベースの初期化: プロジェクトのルートディレクトリにある scripts フォルダ内に 01-create-tables.sql ファイルがあります。この SQL を Supabase Dashboard の右側ナビゲーションウィンドウにある SQL Editor で実行してください。image

  5. プロジェクトの実行: プロジェクトのルートディレクトリで以下のコマンドを実行して依存パッケージをインストールし、プロジェクトを起動できます。起動後、デフォルトのローカルアクセスアドレスは http://localhost:3000 です。ご利用のブラウザから直接システムにアクセスできます。

    pnpm install
    pnpm dev

概要

会議メモシステムプロジェクトから得られた実践的な経験に基づき、PolarDB Supabase のベストプラクティスには以下が含まれます。

これらのベストプラクティスに従うことで、安定した、安全な、高性能なPolarDB Supabase アプリケーションの構築に役立ちます。