結論

Supabaseは、PostgreSQLにAPI・認証・ファイル保存を付けて提供するバックエンドのサービスです。テーブルを作ると、すぐREST APIで読み書きできます。

この記事でわかること

  • 6つの部品の構成図と、Firebaseとの違い7項目
  • RLSで行が見える・見えない様子の実物の出力
  • supabase-jsが送るHTTPリクエスト6種の中身

2026年10月18日更新|PostgREST 16.4・Supabase Auth v2.197.0で再現

アプリは supabase-js から HTTP で呼び、裏側の部品がすべて同じ PostgreSQL を使います。

flowchart TD
    A[アプリ<br>supabase-js] --> G[API ゲートウェイ]
    G --> R[REST<br>PostgREST]
    G --> U[Auth<br>JWT]
    G --> S[Storage<br>Realtime<br>Functions]
    R --> P[(PostgreSQL)]
    U --> P
    S --> P

よく調べられることを先にまとめます。

知りたいこと 答え 詳しい節
Supabaseとは PostgreSQL を中心にしたバックエンドのサービス Supabaseとは
無料で使えるか Free プランあり(2プロジェクトまで) 料金
有料の値段 Pro は月25ドルから 料金
Firebaseとの違い DB がリレーショナル(SQL)かドキュメントか Firebaseとの違い
データを守る仕組み RLS(行レベルセキュリティ)のポリシー RLS
ログイン機能 Supabase Auth(メール・OAuth・SSO など) Supabase Auth
JS からの呼び方 createClient(URL, キー) → from('表').select() supabase-js
日本のリージョン 東京(ap-northeast-1)がある 使い方

確認環境:Ubuntu 24.04・Node.js 22.22.2。Supabase と同じ部品の PostgreSQL 16.15・PostgREST 16.4(GitHub リリースのバイナリ)・Supabase Auth v2.197.0(GoTrue、GitHub リリースのバイナリ)を手元で起動し、/rest/v1 と /auth/v1 を振り分ける小さな Python のサーバーを前に置いて、Supabase と同じ URL の形を再現しました。ロール(anon・authenticated・service_role)は Supabase のドキュメントと同じ名前で作り、@supabase/supabase-js 2.118.0 をその URL に向けて、RLS の動き・エラー・送られる HTTP リクエストを確かめています。料金・API キー・ダッシュボードの手順・Realtime・Storage・Edge Functions・CLI は supabase.com の公式ドキュメント、Firebase の無料枠は firebase.google.com で確かめました(確認日 2026年10月18日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Supabaseとは|使い方・料金・RLSとFirebaseとの違いを実例で」

Supabaseとは

Supabase(スーパベース)は、アプリのバックエンドに要る機能をまとめて提供するサービスです。 中心にあるのは本物の PostgreSQL で、その上に次の機能が付いています。

  • テーブルから自動で作られる REST API(と GraphQL API)
  • ユーザー登録・ログイン(Supabase Auth)
  • ファイルの保存(Storage)
  • データの変更をすぐ届ける仕組み(Realtime)
  • サーバー側の小さな処理(Edge Functions)

このような「バックエンドをサービスとして借りる」形を BaaS(Backend as a Service)と呼びます。サーバーの API を一から書かなくても、ブラウザやスマホのアプリから直接データを読み書きできるのが特徴です。

公式の比較ページでは、Supabase を「Postgres の上で動くオープンソースのバックエンド基盤」と説明しています。主要な部品(PostgreSQL・GoTrue・PostgREST など)は寛容なライセンスで公開されていて、Docker で自分のサーバーに立てる(セルフホスト)こともできます。

Supabaseが向いている場面

場面 Supabase で楽になること
フロントエンド中心で、API を書く人手が少ない テーブルを作るだけで REST API ができる
ログイン付きのアプリを早く作りたい Auth と RLS で「自分のデータだけ見える」を作れる
データに関係(結合)がある SQL の JOIN・外部キー・トランザクションがそのまま使える
後で自前のサーバーに移すかもしれない 中身が PostgreSQL なので、pg_dump などで持ち出せる
チャット・共同編集など、変更をすぐ届けたい Realtime でテーブルの変更を受け取れる

Supabaseの構成(PostgreSQL+PostgREST+Auth+Storage+Realtime+Edge Functions)

公式の「Architecture」のページに載っている部品です。すべての部品が、プロジェクトごとに1つの PostgreSQL を共有しています。

部品 中身(ソフトウェア) 何をするか
Database PostgreSQL すべての中心。データ・ユーザー・ファイルの情報を持つ
REST API PostgREST DB のテーブルをそのまま REST API にする
GraphQL API pg_graphql(PostgreSQL の拡張) GraphQL で読み書きする
Auth GoTrue(Supabase Auth) ユーザー管理と JWT(アクセストークン)の発行
Storage Storage API S3 互換のファイル保存。ファイルの情報は PostgreSQL に入る
Realtime Realtime WebSocket で変更・メッセージ・在席状況を届ける
Edge Functions Deno TypeScript の関数をサーバー側で動かす
Studio Studio 管理画面(ダッシュボード)
接続プール Supavisor PostgreSQL への接続をまとめる
API ゲートウェイ Envoy 各部品の前に立って振り分ける

URL は部品ごとにパスが分かれています。

部品 URL の形
REST API https://<プロジェクトID>.supabase.co/rest/v1/<テーブル名>
Auth https://<プロジェクトID>.supabase.co/auth/v1/...
Edge Functions https://<プロジェクトID>.supabase.co/functions/v1/<関数名>

PostgRESTが「テーブルをAPIにする」仕組み

PostgREST は、DB のスキーマ(テーブルや列の定義)を読み、テーブルごとの URL を自動で用意します。手元で todos テーブルを1つ作って PostgREST 16.4 を起動すると、ログに次のように出ました。

Starting PostgREST 16.4...
Successfully connected to PostgreSQL 16.15 (Ubuntu 16.15-0ubuntu0.24.04.1) on x86_64-pc-linux-gnu, ...
Connection Pool initialized with a maximum size of 10 connections
API server listening on 127.0.0.1:27301
Schema cache loaded 1 Relations, 1 Relationships, 0 RPCs, 0 Domain Representations, 4 Media Type Handlers

この時点で、API のコードを1行も書かずに /todos が使えます。URL のクエリで絞り込み・並び替え・件数指定ができ、Prefer: count=exact を付けると全件数がヘッダーに返ります。

curl -i "http://127.0.0.1:27300/rest/v1/todos?select=id,title,is_done&is_done=eq.false&order=id.desc&limit=2" \
  -H "apikey: $KEY" -H "Authorization: Bearer $KEY" -H "Prefer: count=exact"
HTTP/1.0 206 Partial Content
Content-Range: 0-1/4
Content-Type: application/json; charset=utf-8
Preference-Applied: count=exact

[{"id":6,"title":"資料をまとめる","is_done":false}, 
 {"id":4,"title":"歯医者の予約","is_done":false}]

Content-Range: 0-1/4 は「全4件のうち0〜1番目を返した」という意味です。eq.false のような演算子(eq・neq・gt・lt・like・in など)が、supabase-js の .eq()・.gt() などにそのまま対応しています。

関連記事「GraphQLとRESTの違い|同じ画面を両方で作ってリクエスト数を実測」で扱っています。

SupabaseとFirebaseの違い

いちばん大きな違いはデータベースの種類です。 Supabase は PostgreSQL(表と SQL)、Firebase の Cloud Firestore はドキュメント型(JSON に近い入れ子のデータ)です。両社の公式ページに書かれている事実を並べます。

観点 Supabase Firebase
データベース PostgreSQL(リレーショナル) Cloud Firestore(ドキュメント型)
問い合わせ SQL。JOIN・副問い合わせ・全文検索 コレクション単位のクエリ
ソースコード 主要な部品がオープンソース Google が運用するサービス。クライアント SDK は公開
セルフホスト Docker などで可能 Google Cloud 上で動く
料金の数え方 プラン+使った容量・転送量など。API リクエスト数では数えない 従量課金。Firestore はドキュメントの読み・書き・削除の回数で数える
無料枠の例 DB 500MB・MAU 5万(Free) Firestore 1GiB・読み取り 5万回/日・MAU 5万(Spark)
サーバー側の関数 Edge Functions(Deno・TypeScript) Cloud Functions(Blaze プランで利用)

どちらを選ぶか(判断表)

状況 選びやすいもの 理由
データ同士の関係が多い(注文と商品など) Supabase 外部キーと JOIN で1回で取れる
SQL を書ける人がチームにいる Supabase DB の知識がそのまま使える
自前のサーバーへ移す可能性がある Supabase PostgreSQL ごと持ち出せる
Google Cloud・Android の機能と深く連携したい Firebase 同じ Google のサービスでそろう
入れ子の JSON をそのまま保存したい Firebase(Supabase も jsonb 型で可) ドキュメント型が素直に合う

Supabaseの料金(2026年10月18日時点)

公式の料金ページ(supabase.com/pricing)の内容です。Free プランは無料で、Pro は月25ドルからです。 料金は変わることがあるため、契約前に公式ページを確かめます。

0ドルFree プラン動いているプロジェクトは2つまで
25ドル〜Pro プラン(月額)月10ドル分のコンピュート代を含む
1週間Free が一時停止されるまで使われない状態が続いた場合
項目 Free Pro Team
月額 0ドル 25ドルから 599ドルから
データベース容量 1プロジェクト 500MB 8GB(超過 0.125ドル/GB) 8GB(超過 0.125ドル/GB)
月間アクティブユーザー(MAU) 5万 10万(超過 0.00325ドル/人) 10万(超過 0.00325ドル/人)
転送量(Egress) 5GB 250GB(超過 0.09ドル/GB) 250GB(超過 0.09ドル/GB)
ファイル保存 1GB 100GB(超過 0.0213ドル/GB) 100GB(超過 0.0213ドル/GB)
Edge Functions の実行回数 50万回 200万回(超過 100万回ごと2ドル) 200万回(超過 100万回ごと2ドル)
Realtime の同時接続 200 500(超過 1,000ごと10ドル) 500(超過 1,000ごと10ドル)
Realtime のメッセージ 200万/月 500万/月(超過 100万ごと2.5ドル) 500万/月(超過 100万ごと2.5ドル)
使われないときの一時停止 1週間で一時停止 しない しない

料金ページにある、ほかの決まりです。

  • Pro は「使用量の上限(Spend cap)」が最初から有効です。 有効の間は、枠を超えた分は課金されません。外すと、超えた分が従量で請求されます。
  • コンピュート(DB のサーバー)は時間単位の課金です。有料プランに付く月10ドル分で、Micro のインスタンス1台分がまかなえます。Free は共有 CPU・メモリ 500MB です。
  • Pro・Team では、2つ目以降のプロジェクトは月10ドルからの追加です。
  • Enterprise は個別見積もりです。

Supabaseの使い方(プロジェクト作成から最初のクエリまで)

公式のクイックスタート(React 版)の流れです。

1

プロジェクトを作る

supabase.com のダッシュボードで新しいプロジェクトを作ります。リージョンは東京(ap-northeast-1)を選べます。ソウル・シンガポールもあります。

DB のパスワードはここで決める。後から DB に直接つなぐときに使う

2

SQL Editor でテーブルを作る

テーブル作成・権限(GRANT)・RLS の有効化・ポリシーを SQL で書きます。次のコード例がクイックスタートと同じ形です。

ダッシュボードの Table Editor でも作れる

3

URL とキーを取り出す

ダッシュボードの「Connect」パネルに、Project URL と publishable キーが出ます。

secret キーはブラウザ側のコードに入れない

4

supabase-js を入れて呼ぶ

npm install @supabase/supabase-js の後、createClient に URL とキーを渡し、from('テーブル名').select() で読みます。

yarn add・pnpm add・CDN の script タグでも入れられる

クイックスタートの SQL は、次の4つを順に行っています(テーブル名は instruments)。

create table instruments (
  id bigint primary key generated always as identity,
  name text not null
);
insert into instruments (name) values ('violin'), ('viola'), ('cello');

grant select on public.instruments to anon;
alter table instruments enable row level security;
create policy "public can read instruments"
  on public.instruments for select to anon
  using (true);

grant select ... to anon を明示している点に注意します。 RLS のドキュメントによると、既存のプロジェクトでは public に作ったテーブルに anon・authenticated・service_role の3つのロールへ全部の権限が最初から付きますが、すべてのプロジェクトで自動で付くわけではありません。権限(GRANT)が無いと、RLS のポリシーより前の段階で拒否されます。

関連記事「PostgreSQLのロールと権限|GRANT設計テンプレートとエラー対処」で扱っています。

.env.local に入れる名前は、Vite の場合 VITE_SUPABASE_URL と VITE_SUPABASE_PUBLISHABLE_KEY です。

VITE_SUPABASE_URL=https://<プロジェクトID>.supabase.co
VITE_SUPABASE_PUBLISHABLE_KEY=sb_publishable_xxxxxxxx

関連記事「.envの使い方と注意点|APIキーを安全に管理する方法」で扱っています。

supabase-jsの基本(select・insert・update・delete)

supabase-js は、Supabase の JavaScript クライアントです。2.118.0 を入れると、中に次のパッケージが入っていました。部品ごとにクライアントが分かれています。

`-- @supabase/supabase-js@2.118.0
  +-- @supabase/auth-js@2.118.0
  +-- @supabase/functions-js@2.118.0
  +-- @supabase/postgrest-js@2.118.0
  +-- @supabase/realtime-js@2.118.0
  `-- @supabase/storage-js@2.118.0

createClientと読み書きのコード

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.SUPABASE_URL,             // https://<プロジェクトID>.supabase.co
  process.env.SUPABASE_PUBLISHABLE_KEY  // sb_publishable_...
)

// 読む(列を選び、絞り込み・並び替え)
const { data, error } = await supabase
  .from('todos')
  .select('id, title, is_done')
  .eq('is_done', false)
  .order('id', { ascending: false })
  .limit(10)

// 追加して、追加した行を受け取る
await supabase.from('todos').insert({ title: '資料をまとめる' }).select()

// 更新(必ず条件を付ける)
await supabase.from('todos').update({ is_done: true }).eq('id', 6).select()

// 削除
await supabase.from('todos').delete().eq('id', 6)

どのメソッドもエラーを投げず、{ data, error } を返します。 error が null かどうかで成否を判断します。2.118.0 で select の結果を JSON.stringify すると、次の形でした。

{"success":true,"error":null,"data":[{"id":1,"title":"牛乳を買う"},{"id":2,"title":"請求書を送る"}],"count":null,"status":200,"statusText":"OK"}

createClient の第3引数には、使うスキーマ(db.schema)、トークンの自動更新(auth.autoRefreshToken)、セッションの保存(auth.persistSession)、全リクエストに付けるヘッダー(global.headers)などを渡せます。

supabase-jsが実際に送るHTTPリクエスト

supabase-js 2.118.0 を手元の URL に向け、前に置いたサーバーで届いたリクエストを記録しました(キーとトークンは途中を省略しています)。JS のメソッドが、そのまま PostgREST の URL とヘッダーに変わっているのが分かります。

supabase-js のコード 送られたリクエスト
.from('todos').select('id, title') GET /rest/v1/todos?select=id%2Ctitle
.select('id, title').order('id') GET /rest/v1/todos?select=id%2Ctitle&order=id.asc
.insert({ title: '…' }).select() POST /rest/v1/todos?select=*(本文は JSON)
.update({ is_done: true }).eq('title', '…').select() PATCH /rest/v1/todos?title=eq.…&select=*
auth.signInWithPassword({ email, password }) POST /auth/v1/token?grant_type=password
functions.invoke('hello', { body }) POST /functions/v1/hello

ログイン後に insert したときのヘッダーです。

POST /rest/v1/todos?select=*
  apikey: eyJhbGciOiJIUzI1NiIs…VAxR_k        ← プロジェクトのキー(毎回同じ)
  Authorization: Bearer eyJhbGciOiJIU…mqWmS8   ← ログインした人のアクセストークン
  content-profile: public                      ← 書き込むスキーマ
  content-type: application/json
  prefer: return=representation                ← .select() を付けたので、追加した行を返させる
  x-client-info: supabase-js/2.118.0; runtime=node; runtime-version=22.22.2
  body: {"title":"資料をまとめる"}

ログイン前は、Authorization にもプロジェクトのキーと同じ値が入っていました。ログインすると Authorization だけがユーザーのトークンに変わります。 この違いで、DB 側のロールが anon か authenticated かが決まります(次の節)。

prefer: return=representation は、PostgreSQL の RETURNING で追加した行を返す指定です。関連記事「PostgreSQL RETURNINGの使い方|更新結果とIDを取得」で扱っています。

Supabase Authの使い方(サインアップ・ログイン・JWT)

Supabase Auth は、ユーザー登録・ログイン・トークンの発行を受け持つ部品です。公式ドキュメントによると、次の方法に対応しています。

ログイン方法 内容
メールとパスワード signUp・signInWithPassword
マジックリンク・OTP メールや SMS に届くリンク・ワンタイムパスワード
ソーシャルログイン Google・GitHub・Apple などの OAuth
電話番号 MessageBird・Twilio・Vonage を通した SMS
SSO(SAML 2.0) 企業向けのシングルサインオン
匿名サインイン・MFA ログインせずに使い始める・多要素認証

ユーザーの情報は、同じ PostgreSQL の中の専用スキーマ(auth)に入ります。手元で Supabase Auth v2.197.0 を起動すると、75個のマイグレーションが当たり、auth.users などのテーブルと auth.uid() などの関数ができました。

{"count":75,"level":"info","msg":"GoTrue migrations applied successfully"}

サインアップとログインのコード

// 登録
const { data, error } = await supabase.auth.signUp({
  email: 'alice@example.com',
  password: 'pass-alice-2026',
})

// ログイン
const { data: login, error: e } = await supabase.auth.signInWithPassword({
  email: 'alice@example.com',
  password: 'pass-alice-2026',
})

// ログアウト
await supabase.auth.signOut()

メール確認を自動で済ませる設定で2人を登録すると、次の結果でした。role は authenticated です。

alice@example.com { id: '8da8f8aa-d7cc-4ffc-b4ff-1d098faa7edf', role: 'authenticated', hasSession: true }
bob@example.com { id: '5ca43f4b-0485-46aa-9de3-98bd4fea8136', role: 'authenticated', hasSession: true }

ログインで受け取るセッションとJWTの中身

ログインすると、data.session に次の項目が入ります。access_token が JWT(署名付きのトークン)で、有効期限は3600秒(1時間)でした。期限が近づくと、supabase-js が refresh_token で自動で取り直します。

access_token, token_type, expires_in, expires_at, refresh_token, user, weak_password
token_type: bearer | expires_in: 3600

access_token の中身(ペイロード)を取り出したものです。

{
  "aud": "authenticated",
  "role": "authenticated",
  "sub": "8da8f8aa-d7cc-4ffc-b4ff-1d098faa7edf",
  "email": "alice@example.com",
  "aal": "aal1",
  "is_anonymous": false,
  "keys": ["sub", "aud", "exp", "iat", "email", "phone", "app_metadata",
           "user_metadata", "role", "aal", "amr", "session_id", "is_anonymous"]
}

sub がユーザーの ID、role が DB で使うロールです。 RLS のポリシーで使う auth.uid() は、この sub を返す関数です。

関連記事「JWTとは|仕組みと、安全に使うための注意点」で扱っています。

RLS(行レベルセキュリティ)の考え方

RLS(Row Level Security)は、「この人はどの行を読める・書けるか」を DB のテーブルに書いておく PostgreSQL の機能です。 Supabase ではブラウザから直接 DB の API を呼ぶため、データを守る中心が RLS になります。

リクエストが届いてから行が返るまでの流れです。

flowchart TD
    A[リクエスト<br>Authorization の JWT] --> B[PostgREST が<br>JWT の署名を確かめる]
    B --> C[JWT の role で<br>DB のロールを切り替える]
    C --> D[ポリシーの条件で<br>行を絞る]
    D --> E[条件に合う行だけ返る]

anon・authenticated・service_role の3つのロール

ロール どんなリクエストか RLS
anon ログインしていない(publishable キーだけ) ポリシーに従う
authenticated ログインしている(ユーザーの JWT 付き) ポリシーに従う
service_role secret キーを使った管理用のリクエスト 無視する(BYPASSRLS)

手元で作ったロールの属性です。service_role は rolbypassrls が t なので、どのポリシーも効きません。

    rolname    | rolbypassrls | rolcanlogin 
---------------+--------------+-------------
 anon          | f            | f
 authenticated | f            | f
 authenticator | f            | t
 postgres      | t            | t
 service_role  | t            | f

authenticator は PostgREST が DB にログインするためのロールです。リクエストのたびに、JWT の role に合わせて anon・authenticated・service_role へ切り替わります。

ポリシーの書き方(USINGとWITH CHECK)

公式ドキュメントの例と同じ形で、「自分の todo だけ扱える」4つのポリシーを書きました。

alter table public.todos enable row level security;

create policy "自分のtodoだけ見られる" on public.todos
  for select to authenticated
  using ( (select auth.uid()) = user_id );

create policy "自分のtodoだけ追加できる" on public.todos
  for insert to authenticated
  with check ( (select auth.uid()) = user_id );

create policy "自分のtodoだけ更新できる" on public.todos
  for update to authenticated
  using ( (select auth.uid()) = user_id )
  with check ( (select auth.uid()) = user_id );

create policy "自分のtodoだけ削除できる" on public.todos
  for delete to authenticated
  using ( (select auth.uid()) = user_id );

create index todos_user_id_idx on public.todos (user_id);
句 何を決めるか 使う操作
using (…) 既にある行のうち、どれを対象にできるか SELECT・UPDATE・DELETE
with check (…) これから書き込む行が条件を満たすか INSERT・UPDATE
to authenticated どのロールに効くポリシーか すべて

公式ドキュメントは、速さのために次の2点を勧めています。

  • auth.uid() を (select auth.uid()) のように select で包む。PostgreSQL が1回だけ計算して使い回す(initPlan)ようになる。
  • ポリシーで絞り込む列(ここでは user_id)にインデックスを作る。

user_id の列には default auth.uid() を付けておくと、insert で user_id を送らなくてもログインした人の ID が入ります。

create table public.todos (
  id bigint generated always as identity primary key,
  user_id uuid not null default auth.uid() references auth.users(id) on delete cascade,
  title text not null,
  is_done boolean not null default false,
  created_at timestamptz not null default now()
);

RLSが効く様子を手元で再現する

alice の todo を2件、bob の todo を1件入れた todos テーブルで、RLS の状態を3段階に変えて確かめました。

1. RLSが無効のとき:誰でも全部読めて消せる

anon(ログインなし・キーだけ)で読むと、全員の行が返りました。

$ curl "http://127.0.0.1:27300/rest/v1/todos?select=id,title" -H "apikey: $ANON" -H "Authorization: Bearer $ANON"
[{"id":1,"title":"牛乳を買う"}, 
 {"id":2,"title":"請求書を送る"}, 
 {"id":3,"title":"歯医者の予約"}]

同じキーで DELETE を送ると、bob の行が消えました。

$ curl -X DELETE "http://127.0.0.1:27300/rest/v1/todos?id=eq.3" -H "apikey: $ANON" -H "Authorization: Bearer $ANON" -H "Prefer: return=representation"
[{"id":3,"user_id":"5ca43f4b-0485-46aa-9de3-98bd4fea8136","title":"歯医者の予約","is_done":false,...}]

publishable キー(旧 anon キー)はブラウザのコードに入る、誰でも見られる値です。 RLS が無いテーブルは、キーを知った人に読み書きされます。公式ドキュメントも「公開しているスキーマのテーブルは、すべて RLS を有効にする」と書いています。

2. RLSを有効にして、ポリシーが無いとき:何も見えない

alter table public.todos enable row level security; だけを実行した状態です。

anon で SELECT          → []
anon で INSERT          → HTTP 401
{"code":"42501","details":null,"hint":null,"message":"new row violates row-level security policy for table \"todos\""}
service_role で SELECT  → [{"id":1,...},{"id":2,...},{"id":4,...}]

読み取りはエラーにならず、空の配列 [] が返ります。 「データが入っているのに 0 件になる」ときは、まずポリシーが無いことを疑います。service_role(secret キー)だけは RLS を無視して全件読めました。

3. ポリシーを足したとき:自分の行だけ見える

上の4つのポリシーを足し、supabase-js でログインして試しました。

anon      : {"success":true,"error":null,"data":[],"count":null,"status":200,"statusText":"OK"}
signIn    : ok
alice     : {"success":true,"error":null,"data":[{"id":1,"title":"牛乳を買う"},{"id":2,"title":"請求書を送る"}],...}
insert    : {"success":true,"error":null,"data":[{"id":6,"user_id":"8da8f8aa-...","title":"資料をまとめる",...}],"status":201,"statusText":"Created"}
spoof     : {"success":false,"error":{"code":"42501",...,"message":"new row violates row-level security policy for table \"todos\""},"data":null,"status":403,"statusText":"Forbidden"}
update bob: {"success":true,"error":null,"data":[],"count":null,"status":200,"statusText":"OK"}
bob       : {"success":true,"error":null,"data":[{"id":4,"title":"歯医者の予約","is_done":false}],...}
試したこと 結果 理由
ログインなしで読む [] anon 用のポリシーが無い
alice が読む alice の2件だけ using で自分の行に絞られた
alice が user_id なしで追加 201 Created default auth.uid() で alice の ID が入った
alice が bob の ID で追加 403・42501 with check に合わない
alice が bob の行を更新 200・[](何も変わらない) using で対象の行が0件になった
bob が読む bob の1件・is_done は false のまま alice の更新は届いていない

他人の行の更新・削除は、エラーではなく「0件を更新した」になります。 画面で「保存したのに変わらない」ときは、.select() を付けて返った行が空かどうかで確かめます。なお、追加した行の id が 5 を飛ばして 6 になっているのは、手順2で失敗した INSERT でも identity の番号が進んだためです。

同じ 42501 でも、ログインしていない(anon)ときは 401、ログインしている(authenticated)ときは 403 が返りました。

起きやすい形テーブルを作ったが、RLS を有効にしていない
→
直し方enable row level security を実行し、必要なポリシーだけ足す

publishable キーだけで全行を読み書きできてしまう

起きやすい形ブラウザのコードに secret キー(旧 service_role)を入れる
→
直し方ブラウザは publishable キー、secret キーはサーバーと Edge Functions だけで使う

secret キーは RLS を無視する

起きやすい形using (true) を authenticated に付けて「ログインしていれば全部見える」にする
→
直し方(select auth.uid()) = user_id のように持ち主で絞る

誰でもサインアップできる設定なら、実質的に公開と同じ

起きやすい形UPDATE に using だけ書き、with check を書かない
→
直し方両方に同じ条件を書く

自分の行の user_id を他人の ID に書き換えられないようにする

SQLでポリシーを確かめる方法

ダッシュボードの SQL Editor や psql では、ロールと JWT の中身を一時的に設定して、ポリシーの効き方を確かめられます。begin 〜 rollback の間だけ効くので、設定は残りません。

begin;
set local role authenticated;
set local request.jwt.claims = '{"sub":"5ca43f4b-0485-46aa-9de3-98bd4fea8136","role":"authenticated"}';
select auth.uid();
select id, title from todos;
rollback;
                 uid                  
--------------------------------------
 5ca43f4b-0485-46aa-9de3-98bd4fea8136
(1 row)

 id |    title     
----+--------------
  4 | 歯医者の予約
(1 row)

set local role anon; に変えると select count(*) from todos; は 0 でした。作ったポリシーの一覧は pg_policies で見られます。

        policyname        |  cmd   |      roles      |                  qual                   |               with_check                
--------------------------+--------+-----------------+-----------------------------------------+-----------------------------------------
 自分のtodoだけ見られる   | SELECT | {authenticated} | (( SELECT auth.uid() AS uid) = user_id) | 
 自分のtodoだけ追加できる | INSERT | {authenticated} |                                         | (( SELECT auth.uid() AS uid) = user_id)
 自分のtodoだけ更新できる | UPDATE | {authenticated} | (( SELECT auth.uid() AS uid) = user_id) | (( SELECT auth.uid() AS uid) = user_id)
 自分のtodoだけ削除できる | DELETE | {authenticated} | (( SELECT auth.uid() AS uid) = user_id) | 

SupabaseのAPIキーの種類(publishable・secret・anon・service_role)

公式の「API keys」のページの内容です。今の推奨は publishable キーと secret キーで、JWT 形式の anon・service_role キーは旧形式です。

キー 形 使う場所 DB のロール
publishable キー sb_publishable_... ブラウザ・スマホアプリ・公開するコード anon(ログイン後は authenticated)
secret キー sb_secret_... サーバー・バッチなど、外に出ないコードだけ service_role(RLS を無視)
anon キー(旧) JWT publishable キーの旧版 anon
service_role キー(旧) JWT secret キーの旧版 service_role
  • publishable キーと secret キーは apikey ヘッダーで送ります。ドキュメントには「Authorization: Bearer ではなく apikey ヘッダーで送る」とあります。
  • ドキュメントによると、anon・service_role キーは 2026年末までに廃止される予定です。 新しいキーを作っても旧キーは自動では無効にならず、ダッシュボードの Settings > API Keys で無効にする操作が別に要ります。

この記事の手元の再現では、PostgREST が検証できるように、role を anon・service_role にした JWT をキーとして使っています(旧形式の anon・service_role キーと同じ考え方です)。

Realtime・Storage・Edge Functionsの使い方

Realtime:テーブルの変更を受け取る

Realtime には3つの機能があります。

機能 何をするか
Broadcast クライアント同士で、遅延の少ないメッセージを送り合う
Presence 誰がオンラインかなど、ユーザーの状態をそろえる
Postgres Changes テーブルの INSERT・UPDATE・DELETE をすぐ受け取る

Postgres Changes を使うには、先にテーブルを supabase_realtime という publication(変更を配る対象)に入れます。

alter publication supabase_realtime add table todos;
const changes = supabase
  .channel('table-db-changes')
  .on(
    'postgres_changes',
    { event: '*', schema: 'public', table: 'todos' },
    (payload) => console.log(payload)
  )
  .subscribe()

ドキュメントによると、Postgres Changes は受け取る人ごとに RLS で確かめてから届けます。そのため、ポリシーは軽く、インデックスが効く形にしておきます。ただし DELETE の通知には RLS が効きません(消えた行を Postgres が確かめられないため)。

Storage:ファイルを保存する

Storage は S3 互換のファイル置き場で、ファイルを「バケット」という入れ物に入れます。

バケットの種類 ファイルの取得 アップロード・削除など
Public URL を知っていれば誰でも取得できる RLS で制御する
Private RLS のポリシーで制御する RLS で制御する

ファイルの権限は、storage.objects テーブルに RLS のポリシーを書いて決めます。DB と同じ考え方で、ファイルも守れます。

Edge Functions:サーバー側の処理を書く

Edge Functions は、Deno で動く TypeScript の関数です。決済サービスの呼び出しなど、secret キーや外部の API キーを使う処理を、ブラウザの外で動かすときに使います。CLI での流れは次のとおりです。

supabase functions new hello-world      # 関数のひな形を作る
supabase functions serve hello-world    # 手元で動かす
supabase functions deploy hello-world   # Supabase に公開する

呼び出しの URL は https://<プロジェクトID>.supabase.co/functions/v1/hello-world(手元では http://localhost:54321/functions/v1/hello-world)です。supabase-js からは invoke で呼びます。

const { data, error } = await supabase.functions.invoke('hello-world', {
  body: { name: 'JavaScript' },
})

手元で invoke('hello', { body: { name: 'RJC' } }) を呼ぶと、POST /functions/v1/hello に Content-Type: application/json・apikey・Authorization を付け、本文 {"name":"RJC"} で送っていました。関数が無いときの error は FunctionsHttpError でした。

Supabaseでよくあるエラーと直し方

手元で出した supabase-js 2.118.0・PostgREST 16.4・Supabase Auth v2.197.0 のエラーの原文です。error.code と status で原因を分けられます。

エラー(原文) status・code 原因と直し方
data が [](エラーなし) 200 RLS が有効でポリシーが無い・合わない。ポリシーを足す
new row violates row-level security policy for table "todos" 401/403・42501 with check に合わない。user_id などの値を確かめる
更新・削除したのに変わらない(data が []) 200 using で対象が0件。自分の行か、条件が合うか確かめる
Invalid login credentials 400・invalid_credentials メールかパスワードが違う
User already registered 422・user_already_exists 同じメールで登録済み。ログインに切り替える
Password should be at least 6 characters. 422・weak_password パスワードが短い
Could not find the table 'public.todo' in the schema cache 404・PGRST205 テーブル名の間違い。hint に候補が出る
column todos.name does not exist 400・42703 列名の間違い
invalid input syntax for type bigint: "abc" 400・22P02 列の型に合わない値で絞り込んだ
Cannot coerce the result to a single JSON object 406・PGRST116 .single() で0件か2件以上だった。.maybeSingle() にする
JWT issued at future PGRST303 トークンの発行時刻(iat)が未来。サーバーの時計を確かめる

テーブル名を間違えたときは、hint に正しい名前の候補が入っていました。

{"code":"PGRST205","message":"Could not find the table 'public.todo' in the schema cache","hint":"Perhaps you meant the table 'public.todos'"}

関連記事「PostgreSQL invalid input syntaxの直し方」で扱っています。

新しく作ったテーブルや列がAPIに出てこない

PostgREST はスキーマの情報をキャッシュ(schema cache)しています。PGRST205 が出るのにテーブルがあるときは、作ったスキーマが API に公開されているか(ダッシュボードの Data API の設定)と、ロールに GRANT があるかを確かめます。

Free プランのプロジェクトが止まっている

料金ページのとおり、Free プランのプロジェクトは1週間使われないと一時停止されます。ダッシュボードから再開できます。止めたくない場合は Pro 以上のプランにします。

Supabaseをローカルで動かす(Supabase CLI)

Supabase CLI を使うと、同じ部品一式を手元の Docker で起動できます。公式の「Getting started」の手順です。

やること コマンド
CLI を入れる(プロジェクトの開発用依存) npm install supabase --save-dev
使い方を見る npx supabase --help
ローカルのプロジェクトを作る npx supabase init
起動する(Docker などが要る) npx supabase start
止める(DB の中身は残る) npx supabase stop

Homebrew・Scoop・Linux のパッケージでも入れられ、その場合は supabase コマンドとして使えます。コンテナを動かす環境は、Docker Desktop のほか OrbStack・Rancher Desktop・Podman・colima が挙げられています。

supabase start の後に使う URL です。

用途 URL
Project URL(supabase-js に渡す) http://127.0.0.1:54321
データベース postgresql://postgres:postgres@127.0.0.1:54322/postgres
Studio(管理画面) http://127.0.0.1:54323

よくある質問

Q. supabase とは何ですか? A. PostgreSQL を中心に、REST API・認証(Auth)・ファイル保存・Realtime・Edge Functions をまとめて提供するバックエンドのサービスです。「Supabaseの構成」の表に部品をまとめています。

Q. supabase auth とは? A. Supabase のユーザー登録・ログインの部品です。メールとパスワード・マジックリンク・OAuth・SSO などに対応し、ログインすると JWT が発行されます。その JWT の sub を、RLS のポリシーで auth.uid() として使います。

Q. Supabase は無料で使えますか? A. Free プランがあり、動いているプロジェクト2つまで、DB 500MB・MAU 5万などの枠で使えます(2026年10月18日時点)。1週間使われないと一時停止されます。

Q. Supabase と Firebase の違いは? A. Supabase は PostgreSQL(表と SQL)、Firebase の Firestore はドキュメント型のデータベースです。Supabase は主要な部品がオープンソースで、セルフホストもできます。

Q. Supabase の RLS とは? A. 行ごとに「誰が読める・書けるか」をテーブルに書く PostgreSQL の機能です。RLS を有効にしてポリシーが無いと、publishable キーからは何も見えなくなります。

Q. publishable キーは公開しても問題ありませんか? A. 公式ドキュメントは、publishable キーをブラウザやアプリのコードに入れてよいキーとしています。その代わり、データは RLS で守ります。secret キーは外に出さないキーです。

Q. Supabase に東京リージョンはありますか? A. あります。Northeast Asia (Tokyo)(ap-northeast-1)を選べます。

まとめ

  • Supabase は、PostgreSQL に PostgREST(REST API)・Auth・Storage・Realtime・Edge Functions を付けたバックエンドのサービス。
  • supabase-js のメソッドは、そのまま /rest/v1/テーブル名?select=…&列=eq.値 の HTTP リクエストになる。
  • ログインすると Authorization がユーザーの JWT に変わり、DB のロールが anon から authenticated になる。
  • RLS が無いテーブルは publishable キーだけで読み書きできる。RLS を有効にし、(select auth.uid()) = user_id のポリシーで持ち主に絞る。
  • RLS で弾かれた読み取り・更新は、エラーではなく空の結果になる。追加だけが 42501 のエラーになる。
  • Free は無料(2プロジェクト・500MB)、Pro は月25ドルから(2026年10月18日時点)。

参考・出典

確認日はいずれも 2026年10月18日です。

  • Supabase「Pricing」:https://supabase.com/pricing
  • Supabase Docs「Architecture」:https://supabase.com/docs/guides/getting-started/architecture
  • Supabase Docs「Row Level Security」:https://supabase.com/docs/guides/database/postgres/row-level-security
  • Supabase Docs「Understanding API keys」:https://supabase.com/docs/guides/api/api-keys
  • Supabase Docs「REST API」:https://supabase.com/docs/guides/api
  • Supabase Docs「Auth」:https://supabase.com/docs/guides/auth
  • Supabase Docs「Use Supabase with React」:https://supabase.com/docs/guides/getting-started/quickstarts/reactjs
  • Supabase Docs「JavaScript: Initializing」:https://supabase.com/docs/reference/javascript/initializing
  • Supabase Docs「JavaScript: Installing」:https://supabase.com/docs/reference/javascript/installing
  • Supabase Docs「Realtime」:https://supabase.com/docs/guides/realtime
  • Supabase Docs「Postgres Changes」:https://supabase.com/docs/guides/realtime/postgres-changes
  • Supabase Docs「Storage」:https://supabase.com/docs/guides/storage
  • Supabase Docs「Storage Buckets」:https://supabase.com/docs/guides/storage/buckets/fundamentals
  • Supabase Docs「Edge Functions」:https://supabase.com/docs/guides/functions
  • Supabase Docs「Edge Functions Quickstart」:https://supabase.com/docs/guides/functions/quickstart
  • Supabase Docs「Supabase CLI: Getting started」:https://supabase.com/docs/guides/local-development/cli/getting-started
  • Supabase Docs「Available regions」:https://supabase.com/docs/guides/platform/regions
  • Supabase「Supabase vs Firebase」:https://supabase.com/alternatives/supabase-vs-firebase
  • Firebase「Pricing」:https://firebase.google.com/pricing
  • PostgREST(GitHub):https://github.com/PostgREST/postgrest
  • Supabase Auth(GitHub):https://github.com/supabase/auth

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Supabaseとは|使い方・料金・RLSとFirebaseとの違いを実例で」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?