<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Supabase on AI2CORE - AI技術ブログ</title>
    <link>https://www.ai2core.com/tags/supabase/</link>
    <description>Recent content in Supabase on AI2CORE - AI技術ブログ</description>
    <generator>Hugo -- 0.146.4</generator>
    <language>ja</language>
    <lastBuildDate>Mon, 23 Feb 2026 18:00:00 +0900</lastBuildDate>
    <atom:link href="https://www.ai2core.com/tags/supabase/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Supabaseで構築するスケーラブルなデータベース基盤</title>
      <link>https://www.ai2core.com/posts/2026-02-23-supabase-db/</link>
      <pubDate>Mon, 23 Feb 2026 18:00:00 +0900</pubDate>
      <guid>https://www.ai2core.com/posts/2026-02-23-supabase-db/</guid>
      <description>Firebase代替としてのSupabaseの魅力と、Postgresの強力な機能。</description>
      <content:encoded><![CDATA[<h1 id="supabaseで構築するスケーラブルなデータベース基盤">Supabaseで構築するスケーラブルなデータベース基盤</h1>
<h2 id="はじめに">はじめに</h2>
<p>「バックエンドの開発速度を上げたい」「認証やリアルタイム機能を手軽に実装したい」——こうした要求に応えるBaaS (Backend as a Service) は、現代のアプリケーション開発において不可欠な存在です。その代表格であるFirebaseは、多くのプロジェクトで採用され、開発者に多大な恩恵をもたらしてきました。</p>
<p>しかし、プロジェクトが成長し、データ構造が複雑化するにつれて、このような課題に直面したことはないでしょうか？</p>
<ul>
<li>「Firebase (Firestore) のスキーマレスな性質が、逆にデータ整合性の維持を難しくしている…」</li>
<li>「複雑なデータ検索や集計を行いたいが、NoSQLのクエリでは表現力に限界がある…」</li>
<li>「ベンダーロックインが心配だ。将来的にインフラを移行する必要が出たときに、身動きが取れなくなるのではないか？」</li>
<li>「リレーショナルなデータを扱うには、Firestoreは最適とは言えないかもしれない…」</li>
</ul>
<p>もし、あなたがこれらの課題に少しでも心当たりがあるなら、この記事はあなたのためのものです。</p>
<p>本記事では、<strong>「オープンソースのFirebase代替」<strong>として注目を集める</strong>Supabase</strong>を取り上げます。Supabaseは、単なるFirebaseのクローンではありません。その核には、40年以上の歴史と絶大な信頼性を誇るリレーショナルデータベース<strong>PostgreSQL</strong>が据えられています。</p>
<p>この記事を読み終える頃には、あなたはSupabaseがなぜスケーラブルで堅牢なデータベース基盤を構築するための強力な選択肢となるのか、そしてPostgreSQLの力を最大限に活用して、高速な開発と長期的な運用性を両立させる方法を深く理解できるでしょう。</p>
<h2 id="なぜsupabaseが今注目されているのか---背景と課題">なぜSupabaseが今、注目されているのか？ - 背景と課題</h2>
<p>Supabaseの魅力を理解するためには、まずBaaS市場の変遷と、既存のサービスが抱える課題を理解する必要があります。</p>
<h3 id="baasの進化とfirebaseがもたらした革命">BaaSの進化とFirebaseがもたらした革命</h3>
<p>かつて、Webアプリケーションを開発するには、サーバーのプロビジョニング、データベースのセットアップ、APIサーバーの実装、認証システムの構築など、多くの定型的な作業が必要でした。</p>
<p>BaaSは、これらのバックエンド機能を汎用的なサービスとして提供することで、開発者がフロントエンドやアプリケーションのコアロジックに集中できるようにしました。中でもGoogleのFirebaseは、直感的なAPI、リアルタイムデータベース、強力な認証機能、ホスティングまでをワンストップで提供し、特にモバイルアプリやプロトタイピングの領域で圧倒的な支持を得ました。</p>
<h3 id="firebase-firestore-が抱えるスケーラビリティの課題">Firebase (Firestore) が抱えるスケーラビリティの課題</h3>
<p>Firebaseの成功は、その手軽さと開発速度にありました。しかし、プロジェクトが成長し、エンタープライズレベルの要件が求められるようになると、そのアーキテクチャに起因するいくつかの課題が顕在化します。</p>
<ol>
<li>
<p><strong>NoSQLデータベースの限界</strong>:
Firebaseの主要なデータベースであるFirestoreは、ドキュメント指向のNoSQLデータベースです。スキーマレスであるため初期開発は迅速ですが、データ間の複雑なリレーションを扱うのが苦手です。例えば、SNSアプリケーションで「ユーザー」と「投稿」と「コメント」と「いいね」が複雑に絡み合うようなデータモデルを考えたとき、正規化されたリレーショナルデータベースであればJOIN一発で取得できるデータも、Firestoreでは複数回のクエリやデータの非正規化といった工夫が必要になり、コードの複雑化やデータ冗長性を招きます。</p>
</li>
<li>
<p><strong>クエリの表現力不足</strong>:
SQLのように柔軟で強力なクエリ言語を持たないため、複雑な条件での絞り込み、集計、ソートといった操作に制限があります。<code>GROUP BY</code>や<code>HAVING</code>のような集計関数を使いたい場合、Cloud Functionsなどを駆使して自前で実装する必要があり、リアルタイム性やパフォーマンスが犠牲になることも少なくありません。</p>
</li>
<li>
<p><strong>ベンダーロックインへの懸念</strong>:
Firebaseは非常に優れたエコシステムですが、それはGoogle Cloud Platformに深く統合されています。一度Firebaseで大規模なシステムを構築すると、データベースの移行や、他のクラウドサービスとの連携が困難になる「ベンダーロックイン」のリスクが常に伴います。データのエクスポートは可能ですが、セキュリティルールやCloud Functionsで記述したビジネスロジックまで含めた完全な移行は、極めて困難です。</p>
</li>
</ol>
<p>これらの課題は、「開発の初期段階では最高のツールだが、長期的にスケールさせるには不安が残る」という評価につながっていました。</p>
<h3 id="rdbへの回帰とsupabaseの登場">RDBへの回帰とSupabaseの登場</h3>
<p>このような背景の中、開発者コミュニティでは、データの整合性、トランザクションの信頼性、そしてSQLという標準化された強力なクエリ言語を持つ<strong>リレーショナルデータベース (RDB) の価値</strong>が再評価されるようになります。</p>
<p>そこに登場したのがSupabaseです。Supabaseは、この流れを見事に捉えました。</p>
<p><strong>「世界で最も信頼されているオープンソースRDBであるPostgreSQLを使い、Firebaseのような開発者体験を提供する」</strong></p>
<p>このコンセプトが、多くの開発者の心を掴んだのです。Supabaseは、BaaSの手軽さと、RDBの堅牢性・柔軟性という、これまでトレードオフの関係にあると考えられていた2つの要素を、見事に両立させました。</p>
<h2 id="supabaseのアーキテクチャとpostgresqlの強力な機能">SupabaseのアーキテクチャとPostgreSQLの強力な機能</h2>
<p>Supabaseが単なるデータベースサービスではないことを理解するために、そのアーキテクチャを見ていきましょう。Supabaseは、既存の優れたオープンソースツール群をPostgreSQLを中心に統合した、いわば「バックエンドのオーケストラ」です。</p>
<pre tabindex="0"><code>                    +--------------------------------+
                    |       Your Application         |
                    | (Web, Mobile, etc.)            |
                    +--------------------------------+
                           |         |         |
                           | (SDK)   | (SDK)   |
  +------------------------+---------+---------+--------------------------+
  |                   Supabase Platform (Hosted or Self-hosted)             |
  |                                                                         |
  |  +-----------+   +-------------+   +-----------+   +---------+   +----------+
  |  |  Auth     |   | Realtime    |   |  Storage  |   | Edge    |   | REST API |
  |  | (GoTrue)  |   | (Realtime)  |   | (S3-comp) |   | Functions| |(PostgREST)|
  |  +-----------+   +-------------+   +-----------+   +---------+   +----------+
  |        |                 |               |               |           |
  |        +-----------------+---------------+---------------+-----------+
  |                                    |
  |                          +---------------------+
  |                          |     PostgreSQL      |  &lt;-- THE CORE
  |                          | (Database, RLS,     |
  |                          |  Functions, Exts)   |
  |                          +---------------------+
  +-------------------------------------------------------------------------+
</code></pre><ul>
<li><strong>PostgreSQL</strong>: すべての中心です。単なるデータストアではなく、認証情報、セキュリティポリシー、ビジネスロジック（関数）まで、すべてがここに集約されます。</li>
<li><strong>GoTrue</strong>: JWTベースの認証サーバー。ユーザー管理とアクセストークン発行を担当します。ユーザー情報はPostgresの<code>auth.users</code>テーブルに保存されます。</li>
<li><strong>PostgREST</strong>: データベーススキーマを読み取り、自動的にRESTful APIを生成します。テーブルやビューを作成するだけで、即座に対応するAPIエンドポイントが利用可能になります。</li>
<li><strong>Realtime</strong>: Postgresの論理レプリケーション機能を利用して、データベースの変更をリアルタイムにクライアントにWebSocket経由で配信します。</li>
<li><strong>Storage</strong>: S3互換のオブジェクトストレージ。Postgresを使って権限管理を行います。</li>
<li><strong>Edge Functions</strong>: Denoで書かれたサーバーレス関数。データベースに近い場所でカスタムロジックを実行できます。</li>
</ul>
<p>このアーキテクチャの最大のポイントは、<strong>すべてがPostgreSQLに根ざしている</strong>ことです。これにより、PostgreSQLが持つ強力な機能を最大限に活用できるのです。</p>
<h3 id="postgresqlがもたらすスケーラビリティと柔軟性">PostgreSQLがもたらすスケーラビリティと柔軟性</h3>
<p>SupabaseがFirebaseと一線を画すのは、このPostgreSQLの力です。具体的にどのようなメリットがあるのか見ていきましょう。</p>
<h4 id="1-厳密なスキーマとリレーション">1. 厳密なスキーマとリレーション</h4>
<p>NoSQLの柔軟性も魅力ですが、大規模なアプリケーションでは厳密なスキーマがデータの整合性を保証し、バグの温床を減らします。Supabaseでは、使い慣れたSQLでテーブルを定義できます。</p>
<p><strong>例: ユーザーと投稿テーブルの作成</strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#75715e">-- ユーザーテーブル
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TABLE</span> <span style="color:#66d9ef">public</span>.profiles (
</span></span><span style="display:flex;"><span>  id UUID <span style="color:#66d9ef">PRIMARY</span> <span style="color:#66d9ef">KEY</span> <span style="color:#66d9ef">REFERENCES</span> auth.users(id) <span style="color:#66d9ef">ON</span> <span style="color:#66d9ef">DELETE</span> <span style="color:#66d9ef">CASCADE</span>,
</span></span><span style="display:flex;"><span>  username TEXT <span style="color:#66d9ef">UNIQUE</span> <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>  updated_at TIMESTAMPTZ <span style="color:#66d9ef">DEFAULT</span> NOW()
</span></span><span style="display:flex;"><span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">-- 投稿テーブル
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TABLE</span> <span style="color:#66d9ef">public</span>.posts (
</span></span><span style="display:flex;"><span>  id BIGINT <span style="color:#66d9ef">GENERATED</span> <span style="color:#66d9ef">BY</span> <span style="color:#66d9ef">DEFAULT</span> <span style="color:#66d9ef">AS</span> <span style="color:#66d9ef">IDENTITY</span> <span style="color:#66d9ef">PRIMARY</span> <span style="color:#66d9ef">KEY</span>,
</span></span><span style="display:flex;"><span>  user_id UUID <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span> <span style="color:#66d9ef">REFERENCES</span> <span style="color:#66d9ef">public</span>.profiles(id),
</span></span><span style="display:flex;"><span>  content TEXT <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>  created_at TIMESTAMPTZ <span style="color:#66d9ef">DEFAULT</span> NOW()
</span></span><span style="display:flex;"><span>);
</span></span></code></pre></td></tr></table>
</div>
</div><p>このように、<code>FOREIGN KEY</code>制約を使えば、存在しないユーザーからの投稿を防ぐなど、データベースレベルでデータの整合性を担保できます。</p>
<p>そして、リレーショナルデータベースの真骨頂である<code>JOIN</code>が、その威力を発揮します。</p>
<p><strong>例: 投稿とその投稿者のユーザー名を取得する (JavaScript SDK)</strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">createClient</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;@supabase/supabase-js&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">supabase</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createClient</span>(<span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">env</span>.<span style="color:#a6e22e">SUPABASE_URL</span>, <span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">env</span>.<span style="color:#a6e22e">SUPABASE_ANON_KEY</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">fetchPostsWithUsernames</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">data</span>, <span style="color:#a6e22e">error</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">supabase</span>
</span></span><span style="display:flex;"><span>    .<span style="color:#a6e22e">from</span>(<span style="color:#e6db74">&#39;posts&#39;</span>)
</span></span><span style="display:flex;"><span>    .<span style="color:#a6e22e">select</span>(<span style="color:#e6db74">`
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      id,
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      content,
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      created_at,
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      profiles (
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        username
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      )
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    `</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">error</span>) <span style="color:#66d9ef">throw</span> <span style="color:#a6e22e">error</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#a6e22e">data</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// 出力例:
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#75715e">// [
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#75715e">//   {
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#75715e">//     id: 1,
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#75715e">//     content: &#39;Hello Supabase!&#39;,
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#75715e">//     created_at: &#39;...&#39;,
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#75715e">//     profiles: { username: &#39;user1&#39; }
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#75715e">//   }, ...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#75715e">// ]
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>Supabaseのクライアントライブラリは、リレーションを直感的に扱えるように設計されており、<code>JOIN</code>の強力な機能を簡単に利用できます。Firestoreでこれと同じことをしようとすると、クライアント側で複数回の読み取りが必要になるケースがほとんどです。</p>
<h4 id="2-強力なセキュリティ-row-level-security-rls">2. 強力なセキュリティ: Row Level Security (RLS)</h4>
<p>Supabaseのキラー機能の一つが、PostgreSQLの**Row Level Security (RLS)**です。これは、データベースの行（レコード）単位で、誰がどの操作（SELECT, INSERT, UPDATE, DELETE）を行えるかを定義できるセキュリティ機能です。</p>
<p>FirebaseのセキュリティルールがJSONライクな独自構文で記述するのに対し、RLSは<strong>SQL</strong>でポリシーを記述します。これにより、極めて柔軟かつ強力なアクセスコントロールが実現できます。</p>
<p><strong>例1: 自分のプロフィール情報しか更新できないようにするポリシー</strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#75715e">-- まずテーブルでRLSを有効化
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">ALTER</span> <span style="color:#66d9ef">TABLE</span> <span style="color:#66d9ef">public</span>.profiles ENABLE <span style="color:#66d9ef">ROW</span> <span style="color:#66d9ef">LEVEL</span> <span style="color:#66d9ef">SECURITY</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">-- UPDATEに対するポリシーを作成
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">CREATE</span> POLICY <span style="color:#e6db74">&#34;Users can update their own profile.&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">ON</span> <span style="color:#66d9ef">public</span>.profiles
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">FOR</span> <span style="color:#66d9ef">UPDATE</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">USING</span> ( auth.uid() <span style="color:#f92672">=</span> id ); <span style="color:#75715e">-- 現在認証中のユーザーのIDと、行のIDが一致する場合のみ許可
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>例2: ログインしているユーザーは全ての投稿を閲覧でき、自分の投稿のみ削除できるポリシー</strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">ALTER</span> <span style="color:#66d9ef">TABLE</span> <span style="color:#66d9ef">public</span>.posts ENABLE <span style="color:#66d9ef">ROW</span> <span style="color:#66d9ef">LEVEL</span> <span style="color:#66d9ef">SECURITY</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">-- ログインユーザーは全件閲覧可能
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">CREATE</span> POLICY <span style="color:#e6db74">&#34;Allow logged-in users to read all posts.&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">ON</span> <span style="color:#66d9ef">public</span>.posts
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">FOR</span> <span style="color:#66d9ef">SELECT</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">USING</span> ( auth.<span style="color:#66d9ef">role</span>() <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;authenticated&#39;</span> );
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">-- 自分の投稿のみ削除可能
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">CREATE</span> POLICY <span style="color:#e6db74">&#34;Allow users to delete their own posts.&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">ON</span> <span style="color:#66d9ef">public</span>.posts
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">FOR</span> <span style="color:#66d9ef">DELETE</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">USING</span> ( auth.uid() <span style="color:#f92672">=</span> user_id );
</span></span></code></pre></td></tr></table>
</div>
</div><p>これらのポリシーはデータベース層で強制されるため、クライアントからどのようなリクエストが来ても、不正な操作はブロックされます。PostgRESTが生成するAPIは、このRLSポリシーを自動的に尊重するため、APIサーバー側で複雑な権限チェックロジックを実装する必要がほとんどありません。</p>
<h4 id="3-トランザクションとビジネスロジック">3. トランザクションとビジネスロジック</h4>
<p>複数のデータ更新を伴う処理、例えば「銀行振込（A口座から減算し、B口座に加算する）」のような処理では、全ての操作が成功するか、全て失敗するかのどちらかでなければなりません。これを保証するのが<strong>トランザクション</strong>です。</p>
<p>PostgreSQLは、ACID特性に準拠した強力なトランザクション機能を備えています。Supabaseでは、PostgreSQLの関数を<code>rpc()</code>（Remote Procedure Call）として呼び出すことで、トランザクションを安全に実行できます。</p>
<p><strong>例: 投稿に「いいね」をする関数（重複いいねを防ぐ）</strong>
まず、PostgreSQLに関数を作成します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#75715e">-- いいねテーブル
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TABLE</span> <span style="color:#66d9ef">public</span>.likes (
</span></span><span style="display:flex;"><span>  post_id BIGINT <span style="color:#66d9ef">REFERENCES</span> <span style="color:#66d9ef">public</span>.posts(id) <span style="color:#66d9ef">ON</span> <span style="color:#66d9ef">DELETE</span> <span style="color:#66d9ef">CASCADE</span>,
</span></span><span style="display:flex;"><span>  user_id UUID <span style="color:#66d9ef">REFERENCES</span> <span style="color:#66d9ef">public</span>.profiles(id) <span style="color:#66d9ef">ON</span> <span style="color:#66d9ef">DELETE</span> <span style="color:#66d9ef">CASCADE</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">PRIMARY</span> <span style="color:#66d9ef">KEY</span> (post_id, user_id)
</span></span><span style="display:flex;"><span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">-- いいねを追加する関数
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">OR</span> <span style="color:#66d9ef">REPLACE</span> <span style="color:#66d9ef">FUNCTION</span> like_post (post_id_to_like BIGINT)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">RETURNS</span> void
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">LANGUAGE</span> plpgsql
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">AS</span> <span style="color:#960050;background-color:#1e0010">$$</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">BEGIN</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">-- すでにいいねしている場合は何もしない
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">IF</span> <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">EXISTS</span> (<span style="color:#66d9ef">SELECT</span> <span style="color:#ae81ff">1</span> <span style="color:#66d9ef">FROM</span> <span style="color:#66d9ef">public</span>.likes <span style="color:#66d9ef">WHERE</span> post_id <span style="color:#f92672">=</span> post_id_to_like <span style="color:#66d9ef">AND</span> user_id <span style="color:#f92672">=</span> auth.uid()) <span style="color:#66d9ef">THEN</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> <span style="color:#66d9ef">public</span>.likes (post_id, user_id)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">VALUES</span> (post_id_to_like, auth.uid());
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">END</span> <span style="color:#66d9ef">IF</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">END</span>;
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">$$</span>;
</span></span></code></pre></td></tr></table>
</div>
</div><p>この関数は、<code>likes</code>テーブルへの<code>INSERT</code>を試みますが、主キー制約により同じユーザーが同じ投稿に複数回いいねすることはできません。<code>IF</code>文で事前にチェックすることも可能です。</p>
<p>クライアントからは、この関数をシンプルに呼び出すだけです。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">likePost</span>(<span style="color:#a6e22e">postId</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">error</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">supabase</span>.<span style="color:#a6e22e">rpc</span>(<span style="color:#e6db74">&#39;like_post&#39;</span>, {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">post_id_to_like</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">postId</span>
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">error</span>(<span style="color:#e6db74">&#39;Error liking post:&#39;</span>, <span style="color:#a6e22e">error</span>);
</span></span><span style="display:flex;"><span>  } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#39;Successfully liked post!&#39;</span>);
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>このように、複雑なビジネスロジックやアトミックな操作が必要な処理をデータベース関数としてカプセル化することで、クライアント側のコードはシンプルになり、セキュリティとデータ整合性も向上します。</p>
<h4 id="4-無限の拡張性-postgresql-extensions">4. 無限の拡張性: PostgreSQL Extensions</h4>
<p>PostgreSQLのもう一つの強みは、その広大なエコシステムと拡張機能（Extensions）です。Supabaseでは、ダッシュボードからワンクリックで様々な拡張機能を有効にできます。</p>
<ul>
<li><strong>PostGIS</strong>: 地理空間データを扱うためのデファクトスタンダード。位置情報を使った検索や分析がSQLで可能になります。</li>
<li><strong>pg_cron</strong>: 定期的なジョブ実行をデータベース内でスケジューリングできます（例: 毎晩0時に古いログを削除する）。</li>
<li><strong>pgvector</strong>: ベクトルデータを効率的に保存・検索するための拡張機能。AI/MLアプリケーションにおける類似検索（画像検索、推薦システムなど）に不可欠です。</li>
<li><strong>TimescaleDB</strong>: 時系列データを高速に処理するための拡張機能。IoTデータや金融データの分析基盤として利用できます。</li>
</ul>
<p>これらはほんの一例です。FirebaseではCloud Functionsなどを駆使して外部サービスと連携する必要があるような機能も、SupabaseならPostgreSQLの拡張機能として、データベースと一体化した形で実現できる可能性があります。</p>
<h2 id="supabase-vs-firebase-メリットとデメリットの徹底比較">Supabase vs Firebase: メリットとデメリットの徹底比較</h2>
<p>ここで、両者を客観的に比較し、それぞれのツールの強みと弱みを整理してみましょう。</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">項目</th>
          <th style="text-align: left">Supabase</th>
          <th style="text-align: left">Firebase</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><strong>データベース</strong></td>
          <td style="text-align: left">PostgreSQL (リレーショナル)</td>
          <td style="text-align: left">Firestore (NoSQL), Realtime DB</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>スキーマ</strong></td>
          <td style="text-align: left">スキーマあり（厳密）</td>
          <td style="text-align: left">スキーマレス（柔軟）</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>クエリ</strong></td>
          <td style="text-align: left">SQL（JOIN, 集計など自由自在）</td>
          <td style="text-align: left">独自クエリ（制限あり、JOIN不可）</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>ベンダーロックイン</strong></td>
          <td style="text-align: left"><strong>低い</strong>（オープンソース, セルフホスト可）</td>
          <td style="text-align: left"><strong>高い</strong>（Google Cloudに依存）</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>セキュリティ</strong></td>
          <td style="text-align: left"><strong>RLS</strong> (SQLベース、行単位で強力)</td>
          <td style="text-align: left">Security Rules (JSONライク、パスベース)</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>ビジネスロジック</strong></td>
          <td style="text-align: left">DB関数 (RPC), Edge Functions</td>
          <td style="text-align: left">Cloud Functions</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>拡張性</strong></td>
          <td style="text-align: left"><strong>Postgres Extensions</strong></td>
          <td style="text-align: left">Google Cloudサービス連携</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>ローカル開発</strong></td>
          <td style="text-align: left">Dockerで完全な環境を再現可能</td>
          <td style="text-align: left">エミュレータスイート</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>学習コスト</strong></td>
          <td style="text-align: left">SQL/RDBの知識が必要</td>
          <td style="text-align: left">比較的容易に始められる</td>
      </tr>
  </tbody>
</table>
<h3 id="supabaseのメリット">Supabaseのメリット</h3>
<ul>
<li><strong>データ整合性と信頼性</strong>: RDBの特性により、データの整合性を担保しやすい。</li>
<li><strong>クエリの表現力</strong>: SQLが使えるため、複雑なデータ取得や分析が容易。</li>
<li><strong>脱ベンダーロックイン</strong>: オープンソースであり、<code>pg_dump</code>一発でデータをエクスポート可能。最悪の場合、自分でPostgreSQLをホストすることもできます。</li>
<li><strong>PostgreSQLエコシステムの活用</strong>: 長年培われてきたPostgreSQLのツール、知見、拡張機能という巨人の肩の上に立つことができます。</li>
<li><strong>コスト効率</strong>: 複雑なクエリをDB層で実行できるため、Cloud Functionsのように読み書きの回数で課金が増大するのを避けられる可能性があります。</li>
</ul>
<h3 id="supabaseのデメリット注意点">Supabaseのデメリット・注意点</h3>
<ul>
<li><strong>RDBの知識</strong>: スキーマ設計、正規化、インデックスの知識など、RDBに関する基本的な理解が求められます。</li>
<li><strong>スケーリングの考え方</strong>: Firestoreは水平スケーリングを前提に設計されていますが、PostgreSQLのスケールは基本的には垂直スケーリング（サーバーのスペックアップ）が中心となります。もちろん、リードレプリカなどでスケールアウトも可能ですが、Firestoreとは思想が異なります。ただし、ほとんどのアプリケーションにとってPostgreSQLのパフォーマンスは十分すぎるほど高性能です。</li>
<li><strong>エコシステムの成熟度</strong>: Firebaseに比べると、コミュニティの規模やサードパーティ製のライブラリ、学習資料などはまだ発展途上な面もあります（しかし、急速に成長しています）。</li>
</ul>
<h2 id="現場で使える実践的なtips">現場で使える実践的なTips</h2>
<p>Supabaseをプロダクションで活用するための、より実践的なヒントをいくつか紹介します。</p>
<h3 id="1-データベースマイグレーションはcliで管理する">1. データベースマイグレーションはCLIで管理する</h3>
<p>Supabaseのダッシュボード上でGUIでテーブルを操作するのは手軽ですが、プロダクション環境ではスキーマの変更履歴をバージョン管理することが不可欠です。Supabaseは強力なCLIツールを提供しています。</p>
<p><strong>基本的なマイグレーションフロー:</strong></p>
<ol>
<li>
<p><strong>ローカル環境と連携:</strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>supabase login
</span></span><span style="display:flex;"><span>supabase link --project-ref &lt;your-project-id&gt;
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p><strong>ローカルDBのスキーマ変更をダンプ:</strong>
リモート（本番）DBのスキーマ変更をローカルに反映します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>supabase db pull
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p><strong>ローカルでテーブル変更などを行い、差分からマイグレーションファイルを作成:</strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 例: postsテーブルに &#34;title&#34; カラムを追加したとする</span>
</span></span><span style="display:flex;"><span>supabase db diff -f add_title_to_posts
</span></span></code></pre></td></tr></table>
</div>
</div><p>これにより、<code>supabase/migrations</code>ディレクトリにSQLファイルが生成されます。</p>
</li>
<li>
<p><strong>マイグレーションをリモートDBに適用:</strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>supabase db push
</span></span></code></pre></td></tr></table>
</div>
</div></li>
</ol>
<p>このフローをCI/CDパイプラインに組み込むことで、データベーススキーマの変更を安全かつ自動的に管理できます。</p>
<h3 id="2-パフォーマンスチューニングの勘所-インデックスと実行計画">2. パフォーマンスチューニングの勘所: インデックスと実行計画</h3>
<p>アプリケーションのパフォーマンスが低下してきたら、まずクエリを疑います。</p>
<ul>
<li>
<p><strong>インデックスの作成</strong>: <code>WHERE</code>句で頻繁に検索するカラムや、<code>JOIN</code>の結合キーとなるカラムにはインデックスを作成しましょう。Supabaseのダッシュボードには、遅いクエリを検出し、インデックス作成を推奨してくれる機能もあります。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#75715e">-- postsテーブルのuser_idカラムにインデックスを作成
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">INDEX</span> idx_posts_on_user_id <span style="color:#66d9ef">ON</span> <span style="color:#66d9ef">public</span>.posts(user_id);
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p><strong>実行計画の確認</strong>: <code>EXPLAIN ANALYZE</code>を使うと、PostgreSQLがどのようにクエリを実行しているか（実行計画）を確認できます。<code>Seq Scan</code>（全件スキャン）が発生している箇所は、インデックスが効いていない可能性があり、チューニングの対象となります。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">EXPLAIN</span> <span style="color:#66d9ef">ANALYZE</span> <span style="color:#66d9ef">SELECT</span> <span style="color:#f92672">*</span> <span style="color:#66d9ef">FROM</span> <span style="color:#66d9ef">public</span>.posts <span style="color:#66d9ef">WHERE</span> user_id <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;...&#39;</span>;
</span></span></code></pre></td></tr></table>
</div>
</div></li>
</ul>
<h3 id="3-ビュー-view-を活用してapiをシンプルに保つ">3. ビュー (VIEW) を活用してAPIをシンプルに保つ</h3>
<p>複数のテーブルを<code>JOIN</code>した複雑なデータ構造を頻繁にクライアントから要求される場合、<strong>ビュー</strong>を作成するのが効果的です。ビューは、保存されたクエリ結果を仮想的なテーブルとして扱える機能です。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">VIEW</span> <span style="color:#66d9ef">public</span>.posts_with_username <span style="color:#66d9ef">AS</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span>
</span></span><span style="display:flex;"><span>  p.id,
</span></span><span style="display:flex;"><span>  p.content,
</span></span><span style="display:flex;"><span>  p.created_at,
</span></span><span style="display:flex;"><span>  u.username
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">public</span>.posts p
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">JOIN</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">public</span>.profiles u <span style="color:#66d9ef">ON</span> p.user_id <span style="color:#f92672">=</span> u.id;
</span></span></code></pre></td></tr></table>
</div>
</div><p>こうすることで、クライアントからはあたかも<code>posts_with_username</code>という単一のテーブルがあるかのように見え、シンプルなクエリでデータを取得できます。RLSポリシーもビューに対して設定可能です。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#75715e">// クライアント側のコードがシンプルになる
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">data</span>, <span style="color:#a6e22e">error</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">supabase</span>
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">from</span>(<span style="color:#e6db74">&#39;posts_with_username&#39;</span>)
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">select</span>(<span style="color:#e6db74">&#39;*&#39;</span>);
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="4-security-definer-関数で権限昇格を安全に行う">4. <code>security definer</code> 関数で権限昇格を安全に行う</h3>
<p>通常、<code>rpc</code>で呼び出される関数は、実行したユーザーの権限で動作します（<code>security invoker</code>）。しかし、時には関数の定義者（通常は管理者）の権限で特定の操作を行いたい場合があります。例えば、「ユーザー登録時に、プロフィールテーブルにもレコードを自動で作成する」といったケースです。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#75715e">-- `auth.users`テーブルに新しい行が挿入されるたびにトリガーされる関数
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">OR</span> <span style="color:#66d9ef">REPLACE</span> <span style="color:#66d9ef">FUNCTION</span> <span style="color:#66d9ef">public</span>.handle_new_user()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">RETURNS</span> <span style="color:#66d9ef">TRIGGER</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">LANGUAGE</span> plpgsql
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">SECURITY</span> <span style="color:#66d9ef">DEFINER</span> <span style="color:#75715e">-- この関数を定義者の権限で実行する
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">SET</span> search_path <span style="color:#f92672">=</span> <span style="color:#66d9ef">public</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">AS</span> <span style="color:#960050;background-color:#1e0010">$$</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">BEGIN</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> <span style="color:#66d9ef">public</span>.profiles (id, username)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">VALUES</span> (<span style="color:#66d9ef">new</span>.id, <span style="color:#66d9ef">new</span>.raw_user_meta_data<span style="color:#f92672">-&gt;&gt;</span><span style="color:#e6db74">&#39;username&#39;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">RETURN</span> <span style="color:#66d9ef">new</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">END</span>;
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">$$</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">-- `auth.users`テーブルへのINSERTをトリガーにする
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TRIGGER</span> on_auth_user_created
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">AFTER</span> <span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">ON</span> auth.users
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">FOR</span> <span style="color:#66d9ef">EACH</span> <span style="color:#66d9ef">ROW</span> <span style="color:#66d9ef">EXECUTE</span> <span style="color:#66d9ef">PROCEDURE</span> <span style="color:#66d9ef">public</span>.handle_new_user();
</span></span></code></pre></td></tr></table>
</div>
</div><p>この<code>SECURITY DEFINER</code>を使うと、ユーザーは<code>profiles</code>テーブルへの直接の<code>INSERT</code>権限を持っていなくても、サインアップするだけで自動的にプロフィールが作成される、という挙動を実現できます。ただし、強力な機能であるため、SQLインジェクションなどの脆弱性を作り込まないよう細心の注意が必要です。</p>
<h2 id="まとめ">まとめ</h2>
<p>本記事では、Supabaseが単なる「Firebaseの代替」ではなく、<strong>信頼と実績のあるPostgreSQLを核とした、スケーラブルで堅牢なデータベース基盤</strong>であることを、そのアーキテクチャと具体的な機能を通して解説しました。</p>
<ul>
<li><strong>BaaSの手軽さ</strong>: 認証、リアルタイム、ストレージといったバックエンド機能をすぐに利用開始できます。</li>
<li><strong>RDBの堅牢性</strong>: スキーマ、リレーション、トランザクションにより、データの整合性を高いレベルで保証します。</li>
<li><strong>SQLの表現力</strong>: 複雑なデータ取得や分析も、強力で標準化されたSQLで実現できます。</li>
<li><strong>強力なセキュリティ</strong>: Row Level Security (RLS)により、データベース層で行レベルのきめ細やかなアクセスコントロールが可能です。</li>
<li><strong>オープンソース</strong>: ベンダーロックインのリスクが低く、PostgreSQLの広大なエコシステムを最大限に活用できます。</li>
</ul>
<p><strong>どのようなプロジェクトにSupabaseは向いているでしょうか？</strong></p>
<ul>
<li><strong>データ整合性が最重要</strong>なアプリケーション（金融、業務システムなど）</li>
<li><strong>複雑なクエリやデータ分析</strong>が必要なサービス（SaaS、分析ツールなど）</li>
<li><strong>長期的な運用とスケーラビリティ</strong>を見据えたプロジェクト</li>
<li>開発チームが<strong>SQLとRDBに慣れ親しんでいる</strong>場合</li>
</ul>
<p>一方で、スキーマが固まらない超初期のプロトタイピングや、とにかく高速にシンプルなアプリを立ち上げたい場合には、依然としてFirebaseのスキーマレスなアプローチに分があるかもしれません。</p>
<p>しかし、今日のアプリケーション開発において、データの価値はますます高まっています。その大切なデータを、場当たり的な設計ではなく、堅牢な基盤の上で長期的に育てていきたいと考えるならば、Supabaseは検討すべき非常に有力な選択肢です。</p>
<p>Supabaseは、私たち開発者に「BaaSの利便性」と「本格的なデータベース管理」の二者択一を迫るのではなく、その両方を手に入れる道を示してくれました。ぜひ、次のプロジェクトでこのパワフルなデータベース基盤を体験してみてください。</p>
]]></content:encoded>
      <category>Backend</category>
      <category>Supabase</category>
      <category>PostgreSQL</category>
      <category>Database</category>
    </item>
  </channel>
</rss>
