api-baas-supabase について
Supabase でバックエンドを構築する開発者向けに、Postgres、認証、リアルタイム購読、ファイルストレージ、Edge Functions の実装パターンをまとめたガイドです。型安全なアクセス、RLS ポリシー、エラー処理を重視します。
主な機能
- 01Database ジェネリクスを使う型付きクライアント
- 02Postgres クエリとエラー処理
- 03認証フローとセッション管理
- 04RLS ポリシーとデータアクセス制御
- 05リアルタイム購読、ストレージ、Edge Functions
活用シーン
- 01Supabase クライアントでの Postgres クエリ
- 02OAuth、マジックリンク、セッション管理の実装
- 03複数ユーザーのデータに対する RLS ポリシー設定
- 04データベース変更の購読やファイルのアップロード
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ agents-inc/ skills --skill api-baas-supabaseインストール後、Claude Code で/api-baas-supabaseを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.yamlreference.mdexamples/auth.mdcore.mddatabase.mdedge-functions.mdstorage.md詳細
Supabase のパターン
クイックガイド: Supabase を、Postgres データベース、認証、リアルタイムサブスクリプション、ファイルストレージ、エッジ関数を提供するバックエンド・アズ・ア・サービスとして使用します。必ず
Databaseジェネリックを指定した型付きクライアントを使用し、すべてのテーブルで RLS を有効にしてください。また、シークレットキーはサーバー上でのみ使用してください。
<critical_requirements>
重要:このスキルを使用する前に
(公開スキーマ内のすべてのテーブルで、例外なく Row Level Security(RLS)を有効にする必要があります)
(型安全なクエリを実行するため、createClient<Database>() で Database ジェネリック型を使用する必要があります)
(シークレットキーをクライアント側のコードに絶対に公開してはいけません。ブラウザーではパブリッシャブルキーを使用し、シークレットキーはサーバー上でのみ使用してください)
(パフォーマンス向上のため、RLS ポリシー内では (select auth.uid()) をサブクエリでラップして使用する必要があります)
(Supabase のすべてのレスポンスを { data, error } の分割代入で処理する必要があります。成功したと決めつけてはいけません)
</critical_requirements>
自動検出: Supabase、createClient、@supabase/supabase-js、@supabase/ssr、supabase-js、auth.uid()、RLS、行レベルセキュリティ、リアルタイム、postgres_changes、supabase.auth、supabase.from、supabase.storage、supabase.functions、supabase.channel、エッジ関数、Deno.serve
使用する場面:
- TypeScript の型安全性を備えた Supabase クライアントをセットアップする
- 認証(メールアドレス/パスワード、OAuth、マジックリンク、セッション管理)を実装する
- Supabase クライアント経由で Postgres にクエリを実行する(select、insert、update、delete、RPC)
- データアクセスを制御する Row Level Security ポリシーを作成する
- データベースの変更をリアルタイムで購読する
- Supabase Storage からファイルをアップロードして配信する
- Supabase Edge Functions(Deno)でサーバーレス関数を構築する
対象となる主なパターン:
Databaseジェネリック型と環境変数を使用した型付きクライアントのセットアップ- 認証フロー:サインアップ、サインイン、OAuth、マジックリンク、セッション更新、
onAuthStateChange - フィルター、結合、RPC 呼び出し、エラー処理を含むデータベースクエリ
- RLS ポリシー:
USINGとWITH CHECKの違い、auth.uid()、ロールベースのアクセス channel().on('postgres_changes')によるリアルタイムサブスクリプション- ストレージ:アップロード、署名付き URL、公開 URL、バケットポリシー
- エッジ関数:
Deno.serve、CORS ヘッダー、シークレット、関数内の Supabase クライアント
使用しない場面:
- Postgres への直接接続(代わりにデータベースドライバーのスキルを使用してください)
- 複雑なサーバーサイド ORM パターン(専用の ORM スキルを使用してください)
- Supabase 以外の認証プロバイダー(専用の認証スキルを使用してください)
詳細なリソース:
- 判断フレームワークとアンチパターンについては、reference.md を参照してください
クライアントとクエリ:
- examples/core.md — クライアントのセットアップ、型付きクエリ、エラー処理のパターン
認証:
- examples/auth.md — 認証フロー全般、OAuth、マジックリンク、セッション更新、ミドルウェアによる保護
データベース:
- examples/database.md — 複雑なクエリ、結合、RPC、マイグレーション、型生成
ストレージ:
- examples/storage.md — ファイルのアップロード、署名付き URL、バケットポリシー、画像変換
エッジ関数:
- examples/edge-functions.md — Deno エッジ関数、
Deno.serve()、CORS、シークレット
## 考え方
Supabase は、Postgres を基盤に構築されたオープンソースの Firebase 代替サービスです。Postgres 拡張機能、自動生成される REST/GraphQL API、認証、リアルタイムサブスクリプション、ファイルストレージ、エッジ関数を組み合わせ、バックエンド一式を提供します。
基本原則:
- 中核にあるのは Postgres — すべての機能は Postgres 上に構築されています。RLS ポリシー、認証、リアルタイム機能はすべて Postgres のプリミティブを活用します。Postgres を理解することは、Supabase を理解することです。
- エンドツーエンドの型安全性 —
supabase gen typesを使って、データベーススキーマから TypeScript の型を生成します。createClientにDatabaseジェネリックを渡すことで、クエリに完全な型付けが適用されます。 - デフォルトでセキュア — すべてのテーブルで RLS を有効にする必要があります。公開可能キーはブラウザーで安全に使用できます(アクセスは RLS によって制御されます)。シークレットキーは RLS をバイパスするため、サーバーの外に出してはいけません。
- エラーを値として扱う — Supabase のすべてのメソッドは
{ data, error }を返します。成功を前提にしてはいけません。dataを使う前に、必ずerrorを確認してください。 - リアルタイム機能を内蔵 — Postgres の変更は、チャネルを通じて WebSocket 上にストリーミングされます。別途 pub/sub インフラストラクチャを用意する必要はありません。
- エッジファーストの関数 — Edge Functions はユーザーの近くにあるエッジで Deno を実行します。短時間で完了し、冪等性のある操作を設計してください。
Supabase を使う場面:
- Postgres、認証、ストレージをすぐに使える状態で、バックエンドを迅速に開発する場合
- リアルタイム機能(チャット、通知、ライブダッシュボード)が必要なプロジェクト
- 認証、データベース、ストレージの各サービスを個別に管理したくないチーム
- マルチテナントデータの分離に行レベルセキュリティを活用できるアプリケーション
使わないほうがよい場面:
- 完全なアプリケーションサーバーが必要な、複雑なサーバーサイドのビジネスロジックがある場合(シンプルなケースには Edge Functions を使い、複雑なケースには専用の API を使います)
- 高度なクエリ構築が可能な ORM が必要なアプリケーション(Supabase のクエリビルダーは強力ですが、完全な ORM ではありません)
- 複雑な同期プロトコルを必要とするオフラインファーストのアプリケーション
基本パターン
パターン 1:型付きクライアントのセットアップ
テーブル名、カラム名、戻り値の型を完全に補完できるよう、必ず createClient に Database ジェネリックを渡してください。URL とキーには環境変数を使用します。
export const supabase = createClient<Database>(
SUPABASE_URL,
SUPABASE_PUBLISHABLE_KEY,
);
ジェネリックを指定しないと、テーブル名やカラム名の誤りはコンパイル時に検出されません。ブラウザー、サーバー、管理者用クライアントのセットアップパターンについては、examples/core.md を参照してください。
パターン 2:{ data, error } によるエラー処理
Supabase のすべてのメソッドは { data, error } を返します。必ず分割代入し、data を使う前に error を確認してください。data に対する非 null アサーションは使わないでください。
const { data, error } = await supabase
.from("profiles")
.select("id, username")
.eq("id", userId)
.single();
if (error) throw new Error(`Failed to fetch profile: ${error.message}`);
再利用可能なエラーハンドラーパターンとよくある間違いについては、examples/core.md を参照してください。
パターン 3:認証フロー
Supabase Auth は、メールアドレス/パスワード(signInWithPassword)、OAuth(signInWithOAuth)、マジックリンク(signInWithOtp)、電話番号による OTP をサポートしています。アプリケーションのライフサイクルの早い段階で onAuthStateChange を登録し、必ず subscription.unsubscribe() でクリーンアップしてください。
重要な注意点:onAuthStateChange の中から Supabase のメソッドを直接呼び出してはいけません。setTimeout(..., 0) を使って呼び出しを遅らせてください。
サインアップ、サインイン、OAuth、マジックリンク、セッション管理、ミドルウェアによる保護、パスワードリセットのパターンについては、examples/auth.md を参照してください。
パターン 4:データベースクエリ
フィルター、結合、並べ替え、ページネーションを使った型安全な CRUD には、クエリビルダーを使用します。影響を受けた行を返すには、.insert() または .update() の後に必ず .select() を追加してください。
const { data, error } = await supabase
.from("posts")
.select("id, title, author:profiles(username)")
.eq("published", true)
.order("created_at", { ascending: false })
.range(0, PAGE_SIZE - 1);
複雑なクエリ、upsert、RPC 呼び出し、条件付きフィルター、件数取得、マイグレーションについては、examples/database.md を参照してください。
### パターン 5:行レベルセキュリティ(RLS)ポリシー
RLS は主要なセキュリティメカニズムです。すべてのテーブルで有効にし、操作ごとに個別のポリシーを作成し(FOR ALL は使用しないでください)、パフォーマンス向上のため auth.uid() をサブクエリで囲んでください。
alter table public.posts enable row level security;
create policy "posts_select" on public.posts for select to authenticated
using ( published = true or (select auth.uid()) = author_id );
アクセス制御に JWT の user_metadata を決して信頼しないでください。ユーザーが変更できるためです。CRUD ポリシー、チームベースのアクセス、アンチパターンの全例については、examples/database.md を参照してください。
パターン 6:リアルタイムサブスクリプション
channel().on('postgres_changes', ...) を使ってデータベースの変更をサブスクライブします。クリーンアップ時には必ずサブスクライブを解除してください。DELETE イベントはフィルタリングできず、すべての削除が受信されます。UPDATE/DELETE のペイロードで以前のレコードデータを取得するには、replica identity full が必要です。
const channel = supabase
.channel("room-messages")
.on(
"postgres_changes",
{
event: "INSERT",
schema: "public",
table: "messages",
filter: `room_id=eq.${roomId}`,
},
(payload) => {
/* handle */
},
)
.subscribe();
チャット、ライブダッシュボード、通知に使用します。高頻度のデータ(1 秒あたり 100 回を超える更新)には使用しないでください。
パターン 7:ストレージ操作
supabase.storage.from(bucket).upload() を使ってファイルをアップロードします。パブリックバケットには getPublicUrl() を、時間制限付きアクセスを設定したプライベートバケットには createSignedUrl() を使用します。ストレージのアクセス制御には storage.objects の RLS を使用します。
アップロード、署名付き URL、パブリック URL、画像変換、バケットポリシー、署名付きアップロード URL については、examples/storage.md を参照してください。
パターン 8:エッジ関数
非推奨の serve インポートではなく、Deno.serve() を使用します。npm: プレフィックスを付けて supabase-js をインポートします:import { createClient } from "npm:@supabase/supabase-js@2"。すべてのレスポンスで CORS を処理してください。シークレットには Deno.env.get() を使用します。RLS を適用するため、ユーザー JWT を転送してください。
基本的な関数、認証済みアクセス、共有ユーティリティ、Webhook、複数ルートの「ファット関数」、EdgeRuntime.waitUntil() を使ったバックグラウンド処理については、examples/edge-functions.md を参照してください。
<decision_framework>
判断フレームワーク
使用する Supabase キーの選択
Where is the code running?
├─ Browser / Client-side → publishable key (RLS enforced)
├─ Server / API route → publishable key + user JWT (RLS enforced per user)
└─ Admin / Migration script → secret key (bypasses RLS)
└─ NEVER expose the secret key in client bundles
認証方法の選択
What auth flow does the user need?
├─ Email + Password → signInWithPassword
├─ Social login (GitHub, Google, etc.) → signInWithOAuth
├─ Passwordless email → signInWithOtp (magic link)
├─ Phone + SMS → signInWithOtp (phone)
└─ SSO / SAML → signInWithSSO (enterprise)
リアルタイムとポーリングの比較
How fresh must the data be?
├─ Instant (< 1 second) → Realtime subscription (postgres_changes)
├─ Near-instant (1-5 seconds) → Realtime subscription
├─ Periodic (> 5 seconds ok) → Polling with setInterval
└─ On-demand (user refresh) → Re-fetch on action
└─ High-frequency updates (> 100/sec)?
├─ YES → Polling or batch (Realtime has per-subscriber checks)
└─ NO → Realtime is fine
ストレージ:パブリックバケットとプライベートバケット
Who should access the files?
├─ Anyone (public assets, avatars) → Public bucket + getPublicUrl()
├─ Authenticated users only → Private bucket + createSignedUrl()
├─ Specific users (own files) → Private bucket + RLS on storage.objects
└─ Server-only processing → secret key for upload/download
エッジ関数とクライアントクエリの比較
Does the operation need server-side logic?
├─ Simple CRUD → Client query with RLS (no edge function needed)
├─ Multi-step / transactional → Edge function or Postgres function (RPC)
├─ Third-party API call → Edge function
├─ Webhook receiver → Edge function
└─ Heavy computation → Edge function with EdgeRuntime.waitUntil() for background work
</decision_framework>
<red_flags>
重大な警告
優先度の高い問題:
- テーブルに RLS が設定されていない — 公開スキーマ内で RLS が設定されていないテーブルは、誰でも完全にアクセスできます。2025 年 1 月には、RLS の設定漏れにより、170 件を超えるアプリでデータベースが公開状態になっていることが判明しました(CVE-2025-48757)。
- クライアントコードにシークレットキーが含まれている — シークレットキー(以前の
service_roleキー)は、すべての RLS をバイパスします。ブラウザーのバンドルに含めると、すべてのユーザーにデータベースへの完全な管理者アクセス権が与えられます。 { data, error }の戻り値を無視する —errorを確認せずにdataにアクセスすると、操作が失敗した際に実行時エラーが発生します。- RLS ポリシーで
auth.jwt() ->> 'user_metadata'を使用する —user_metadataは、認証済みユーザーがupdateUser()で変更できます。アクセス制御の判断には決して使用しないでください。
優先度が中程度の問題:
- RLS ポリシーで
FOR ALLを使用する — 明確さと監査のしやすさのため、SELECT、INSERT、UPDATE、DELETEの各ポリシーに分けてください。 - サブクエリを使わず、ポリシー内で
auth.uid()を直接使用する — Supabase のベンチマークによると、(select auth.uid())で囲むとパフォーマンスが最大 94~99%向上します。 - ポリシーで
to authenticatedまたはto anonを指定しない — ロールを指定しない場合、ポリシーはすべてのロールに適用され、意図せずデータが公開される可能性があります。 - あらゆる場所で
select("*")を使用する — 機密データを含むすべての列が取得されます。必要な列だけを選択してください。 - Edge Functions で非推奨の
serveインポートを使用する —import { serve } from "https://deno.land/std/http/server.ts"は非推奨です。Deno.serve()を使用してください。
よくある間違い:
.insert()または.update()の後に.select()を追加しない —.select()がない場合、これらのメソッドはデータを返さず(nullのみを返し)ます。- Edge Functions に CORS ヘッダーがない — 適切な CORS ヘッダーと OPTIONS の処理がないと、ブラウザーからのリクエストは失敗します。
- Realtime チャンネルの購読を解除しない — WebSocket 接続がリークし、メモリの問題を引き起こす可能性があります。
- Edge Functions でベア指定子を使用する —
import { createClient } from "@supabase/supabase-js"は Deno では失敗します。npm:@supabase/supabase-js@2を使用してください。 - 認証の確認に
getSession()を使用する —getSession()はローカルストレージから読み取るため、改ざんされる可能性があります。安全なサーバー側での確認にはgetUser()を使用してください。
注意点とエッジケース:
- Realtime の DELETE イベントはフィルタリングできない — 購読しているテーブルの削除は、フィルターに関係なくすべて受信されます。
- 古いレコードのデータを取得するには、Realtime で
replica identity fullが必要 — デフォルトでは、UPDATE および DELETE のペイロードには新しいレコードのみが含まれます。payload.oldにアクセスするには、alter table X replica identity fullを設定してください。 - RLS ポリシーは Realtime の DELETE イベントには適用されない — DELETE イベントで公開される情報には注意してください。
- タブにフォーカスすると
onAuthStateChangeが発火する — ブラウザーのタブが再びフォーカスされたときにもSIGNED_INイベントが発火します。実際にサインインしたときだけではありません。 onAuthStateChangeコールバック内で Supabase のメソッドを呼び出さないでください — デッドロックが発生する可能性があります。setTimeout(..., 0)を使用して処理を遅延させてください。- 署名付き URL には有効期限がある —
createSignedUrl()の URL は、指定した期間が経過すると期限切れになります。署名付きアップロード URL の有効期限は 2 時間です。 - 公開バケットの URL は RLS をバイパスする — 公開バケット内のファイルは、ポリシーに関係なく、URL を知っている人なら誰でもアクセスできます。
- Edge Functions のコールドスタート — アイドル期間後の最初の呼び出しでは、レイテンシが増加します。コールドスタートを最小限に抑えるため、「ファット関数」(数を減らして規模を大きくした関数)を設計してください。
- Edge Functions:ファイルを書き込めるのは
/tmpのみ — エッジ関数で書き込み可能なパスは/tmpディレクトリだけです。
</red_flags>
<critical_reminders>
重大な注意事項
(公開スキーマ内のすべてのテーブルで、例外なく Row Level Security(RLS)を有効にする必要があります)
(型安全なクエリを実行するには、createClient<Database>() で Database ジェネリック型を使用する必要があります)
(クライアント側のコードでシークレットキーを公開してはなりません。ブラウザーではパブリッシャブルキーを使用し、シークレットキーはサーバーでのみ使用してください)
(パフォーマンス向上のため、RLS ポリシー内では (select auth.uid()) をサブクエリで囲んで使用する必要があります)
(Supabase のすべてのレスポンスは { data, error } で分割代入して処理してください。成功を前提としてはなりません)
これらのルールに従わないと、セキュリティ上の脆弱性、型安全でないクエリ、実行時の予期しないエラーが発生します。
</critical_reminders>