- 技術 💻
Supabaseとは|使い方・料金・RLSとFirebaseとの違いを実例で
結論
Supabaseは、PostgreSQLにAPI・認証・ファイル保存を付けて提供するバックエンドのサービスです。テーブルを作ると、すぐREST APIで読み書きできます。
この記事でわかること
- 6つの部品の構成図と、Firebaseとの違い7項目
- RLSで行が見える・見えない様子の実物の出力
- supabase-jsが送るHTTPリクエスト6種の中身
アプリは 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ドルからです。 料金は変わることがあるため、契約前に公式ページを確かめます。
| 項目 | 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 版)の流れです。
プロジェクトを作る
supabase.com のダッシュボードで新しいプロジェクトを作ります。リージョンは東京(ap-northeast-1)を選べます。ソウル・シンガポールもあります。
SQL Editor でテーブルを作る
テーブル作成・権限(GRANT)・RLS の有効化・ポリシーを SQL で書きます。次のコード例がクイックスタートと同じ形です。
URL とキーを取り出す
ダッシュボードの「Connect」パネルに、Project URL と publishable キーが出ます。
supabase-js を入れて呼ぶ
npm install @supabase/supabase-js の後、createClient に URL とキーを渡し、from('テーブル名').select() で読みます。
クイックスタートの 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 が返りました。
enable row level security を実行し、必要なポリシーだけ足すpublishable キーだけで全行を読み書きできてしまう
secret キーは RLS を無視する
using (true) を authenticated に付けて「ログインしていれば全部見える」にする(select auth.uid()) = user_id のように持ち主で絞る誰でもサインアップできる設定なら、実質的に公開と同じ
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期連続の黒字決算。