Menu

Supabase Database Operations Guide

注目すべき点

  • 以下のドキュメントはSupabaseデータベース操作の完全なガイドではなく、Nexty.devで適用されているいくつかの操作方法であり、テンプレートが提供する機能をより迅速に理解するのに役立ちます。
  • AIはSupabaseに非常に精通しているため、自信を持ってAIを使用して様々なデータベース操作を完了することができます。

Nexty.devは完全なSupabaseデータベース操作アーキテクチャを採用し、型安全なデータベースアクセス、包括的な権限制御、統一された操作パターンを提供します。TypeScript型システム、Zodデータ検証、RLS(Row Level Security)ポリシーを組み合わせることで、データ操作のセキュリティと保守性を確保しています。

基本的なCRUD操作

クエリ操作(SELECT)

シンプルクエリ

const { data: users, error } = await supabase
  .from('users')
  .select('*') // または戻り値フィールドを指定 .select('email, role')
  .eq('role', 'admin');

リレーショナルクエリ

const { data: posts, error } = await supabase
  .from('posts')
  .select(`
    *,
    tags (*)
  `)
  .eq('status', 'published');

ページネーションクエリ

const { data, error, count } = await supabase
  .from('posts')
  .select('*', { count: 'exact' }) // { count: 'exact' } -> 総数統計付き
  .range(0, 9)
  .order('created_at', { ascending: false });

データの挿入

const { data: newPost, error } = await supabase
  .from('posts')
  .insert({
    title: 'New Post',
    slug: 'new-post',
    content: 'Post content...',
    language: 'en',
    status: 'draft',
    author_id: user.id
  })
  .select("id")
  .single();

データの更新

const { data: updatedPost, error } = await supabase
  .from('posts')
  .update({
    title: 'Updated Title',
    status: 'published'
  })
  .eq('id', postId)
  .select("id")
  .single();

データの削除

const { error } = await supabase
  .from('posts')
  .delete()
  .eq('id', postId);

高度なクエリテクニック

条件付きクエリ

let query = supabase
  .from('posts')
  .select('*');
 
if (filter) {
  // title、slug、descriptionでファジー検索を実行
  const filterValue = `%${filter}%`;
  query = query.or(
    `title.ilike.${filterValue},slug.ilike.${filterValue},description.ilike.${filterValue}`
  );
}
 
if (status) {
  query = query.eq('status', status);
}
 
const { data, error } = await query
  .order('is_pinned', { ascending: false })
  .order('created_at', { ascending: false })
  .range(from, to);

集計クエリ

// statusフィールドが'published'のレコード数をカウント
const { count, error } = await supabase
  .from('posts')
  .select('*', { count: 'exact', head: true })
  .eq('status', 'published');

トランザクション処理とRPC

データベースの原子操作と複雑な操作には、SupabaseのRPC(Remote Procedure Call)機能を活用できます。

RPCはデータベース内で直接定義されるPostgreSQL関数で、Supabaseクライアントを通じて直接呼び出すことができ、通常のSQLクエリよりも強力な機能を提供します。

const { error: usageError } = await supabaseAdmin.rpc('upsert_and_set_subscription_credits', {
  p_user_id: userId,
  p_credits_to_set: creditsToGrant
});

RPC関数の利点:

  • 原子操作:複雑な複数ステップ操作がすべて成功するかすべて失敗するかを保証
  • より良いパフォーマンス:データベースレベルで実行され、ネットワーク往復を削減
  • 権限制御SECURITY DEFINERを通じて細かい権限制御を実現
  • 複雑なビジネスロジック:条件判定、ループ、例外処理などをサポート
  • データ整合性:競合状態や並行処理の問題を回避

使用ケース:

  • クレジット控除システム
  • 注文処理ワークフロー
  • バッチデータ更新
  • 複雑な計算ロジック
  • トランザクション保証が必要な操作

Nexty.devのソースコードでは、.rpcをグローバル検索することで、ユーザー支払い後にクレジットを追加するメソッドで適用されているRPC関数を確認できます。それらの関数ロジックはdataフォルダ下の対応するデータテーブル定義ファイルにあります。

権限制御とセキュリティ

管理者権限チェック

機密操作では、まず管理者身元を確認し、その後Service Role Keyを使用してデータベースを操作

import { createClient as createAdminClient } from "@supabase/supabase-js";
import { isAdmin } from '@/lib/supabase/isAdmin';
 
// 管理者身元を確認
if (!(await isAdmin())) {
  return actionResponse.forbidden("管理者権限が必要です。");
}
 
// 管理者はService Role Keyを使用してデータベースを操作
const supabaseAdmin = createAdminClient<Database>(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY! // サーバーサイドのみ
);

RLSポリシー

RLS(Row Level Security)はPostgreSQLのセキュリティ機能で、データベーステーブルレベルで特定の行に対するユーザーアクセス権限を制御できます。簡単に言えば、ユーザーが自分に属するデータのみにアクセスできることを保証します。

マルチユーザーSaaSアプリケーションでは、異なるユーザー間のデータは厳格に分離される必要があります。RLSはデータベースレベルのセキュリティ保証を提供し、クライアントコードに脆弱性があっても他のユーザーのデータにアクセスできないことを保証します。

以下はRLSポリシーの基本的な使用方法です:

  1. RLSを有効化
-- テーブルで行レベルセキュリティを有効化
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
 
-- すべてのユーザーをRLS制限の対象にする(テーブル所有者を含む)
ALTER TABLE public.users FORCE ROW LEVEL SECURITY;
  1. ポリシーを作成
-- ユーザーは自分のプロフィールのみ表示可能
CREATE POLICY "Allow user read their own profile"
ON public.users 
FOR SELECT
USING (auth.uid() = id);
 
-- ユーザーは自分のプロフィールのみ更新可能
CREATE POLICY "Allow user update their own profile"
ON public.users 
FOR UPDATE
USING (auth.uid() = id);

テンプレートが提供するdataフォルダでは、すべてのテーブル設計でRLS作成文を確認できます。

注目すべき点

「ユーザーは自分のプロフィールのみ更新可能」などのRLSポリシーが作成されたテーブルに対して、管理者はlib/supabase/serverからエクスポートされたサーバーサイドクライアントを通じて直接ユーザープロフィールを更新することはできません。まずユーザーが管理者であることを確認し、その後@supabase/supabase-jsを使用してRLSポリシーをバイパスしてデータベースを修正できます。

データベースマイグレーションと更新

データベース型の更新

テンプレートはSupabase CLIを使用してTypeScript型定義を自動生成・更新します:

# 型ファイルを生成
supabase gen types typescript --project-id <your-project-id> --schema public > lib/supabase/types.ts

データベース定義を更新するたびに、このコマンドをローカルで実行して、ローカルの型定義が正しいことを確保する必要があります。

コマンド実行後、lib/supabase/types.tsファイル内のデータベース型定義が更新されます。

注目すべき点:

  • Supabase CLIのその他の使用方法については、Supabase Integrationをご確認ください
  • <your-project-id>の見つけ方は?Supabaseコンソールにログインし、アドレスバーの/project/後の文字列です。例:https://supabase.com/dashboard/project/<your-project-id>

スキーマ変更

-- 新しいフィールドを追加するマイグレーション例
ALTER TABLE posts ADD COLUMN view_count INTEGER DEFAULT 0;
 
-- インデックスを作成
CREATE INDEX IF NOT EXISTS idx_posts_status_language 
ON posts(status, language);
 
-- RLSポリシーを更新
CREATE POLICY "Users can view published posts" ON posts
FOR SELECT USING (status = 'published');

ベストプラクティス

選択的フィールドクエリ

.select('*')を使用する代わりに、selectで必要なフィールドのみを選択することを心がけ、クエリパフォーマンスを向上させます

const { data: users, error } = await supabase
  .from('users')
  .select('email, role')
  .eq('role', 'admin');

クエリのインデックス最適化

インデックスを作成してクエリパフォーマンスを最適化

-- 複合インデックスを作成
CREATE INDEX posts_language_status_idx ON posts (language, status);
CREATE INDEX posts_published_at_idx ON posts (published_at DESC);

統一戻り値形式

テンプレートは統一戻り値形式のメソッドを提供します:

  • Server Actionsには、lib/action-response.tsにカプセル化されたメソッドを呼び出し
  • APIには、lib/api-response.tsにカプセル化されたメソッドを呼び出し
lib/action-response.ts
export type ActionResult<T = any> =
  | { success: true; data?: T, customCode?: string }
  | { success: false; error: string, customCode?: string };
 
export const actionResponse = {
  success: <T>(data?: T, customCode?: string): ActionResult<T> => {
    return { success: true, data, customCode };
  },
  error: <T>(message: string, customCode?: string): ActionResult<T> => {
    return { success: false, error: message, customCode };
  },
  
  unauthorized: <T>(message = "Unauthorized", customCode?: string): ActionResult<T> => {
    return actionResponse.error(message, customCode);
  },
  // ...その他...
};

テンプレートは異なるエラー形式のフォールバック処理メソッドも提供します。try...catchコードブロックで、catchで捕捉されたエラーはlib/error-utils.tsgetErrorMessageメソッドを呼び出して処理でき、不規則なエラーメッセージによりフロントエンドのtoastが実際のエラーメッセージを表示できない状況を回避します。

Supabaseデータベース機能概要

SupabaseのDatabaseディレクトリでは、データベース定義に関連するすべての機能エントリを確認できます。これには以下が含まれます:Tables(データテーブル)、Functions(RPC関数)、Triggers(トリガー)、Indexes(インデックス)、Policies(ポリシー)。

supabase database overview