言語を切り替える
テーマを切り替える

Supabase 入門:PostgreSQL + Auth + Storage のオールインワンバックエンド

画面に表示された 12回目のデータベース接続エラーをじっと見つめながら、あることに気づきました。フロントエンド開発者がフルスタックのプロジェクトを作るのは、本当に大変だということです。

以前は、バックエンド機能が必要になるたびに Node.js や Express を学び、さらにデータベースの設定、ユーザー認証、ファイルストレージを片づけなければなりませんでした。どれも大きな落とし穴です。そんなとき出会ったのが Supabase でした。

これはざっくり言えばオープンソースの Firebase 代替ですが、頭を悩ませる NoSQL ではなく PostgreSQL を使っています。しかもデータベース、ユーザー認証、ファイルストレージの3つをまとめてくれる、オールインワンのバックエンドサービスなのです。

この記事では、ゼロから Supabase を使い始め、3つのコア機能である Database、Auth、Storage を中心に解説します。読み終わるころには、これを使って完全なバックエンドを素早く構築できるようになり、もう設定ファイルに苦しめられることはなくなるはずです。


Supabase とは何か

まずは Supabase が一体何なのかを話しましょう。

これは BaaS プラットフォーム、Backend as a Service——バックエンドをサービスとして提供するものです。どういう意味かというと、自分でサーバーを立てたり、データベースを設定したり、API を書いたりする必要がなく、それらをすべて代わりにやってくれるのです。

Firebase とは違い、Supabase は完全にオープンソースです。そして PostgreSQL を使っている点が重要です。PostgreSQL はリレーショナルデータベースで、複雑な SQL クエリができ、データの関係も明確です——Firestore のようなドキュメント型データベースよりずっと使いやすいのです。

Supabase には6つのコア機能があります:

  • Database: PostgreSQL データベース。SQL クエリに対応し、REST API も自動生成します
  • Auth: ユーザー認証システム。メールログイン、ソーシャルログイン(Google、GitHub など)に対応し、JWT Token も備えています
  • Storage: ファイルストレージ。AWS S3 に似ていますが、より簡単です
  • Realtime: リアルタイムのデータ同期。チャットアプリの構築に適しています
  • Edge Functions: エッジコンピューティング。AWS Lambda に似ています
  • Vector Database: ベクトルデータベース。AI アプリの構築に適しています

ただしこの記事では、最初の3つだけを扱います: Database、Auth、Storage——この3つが最もコアな部分で、他の機能はまた別の機会に説明します。

ここまで読むと、こう思うかもしれません。オープンソースで、PostgreSQL を使い、認証とストレージもある——では Firebase と比べて結局どちらが優れているのか? この点は後で詳しく話しますが、先に結論を言うと、SQL クエリが必要、データの関係が複雑、または自分でデータを管理したいなら Supabase を選びましょう。モバイルアプリを作る、リアルタイム同期機能が特に強力であってほしいなら Firebase を選びましょう。


クイックスタート

Supabase プロジェクトを作成する

最初のステップとして、supabase.com でアカウントを登録します。登録が終わったら、「New Project」をクリックして新しいプロジェクトを作成します。

プロジェクトを作成するときには、いくつかの項目を入力する必要があります:

  • プロジェクト名: 何でもよく、たとえば my-first-app
  • データベースパスワード: これは控えておきましょう。後で使います
  • リージョン: 近い場所を選びます。日本なら Singapore か Tokyo を選びましょう

だいたい3分ほどで作成できます。作成後、プロジェクトパネルが表示され、その中に Dashboard があります。左側には機能メニューがずらりと並んでいます: Table Editor、Authentication、Storage、Edge Functions……たくさんあるように見えますが、慌てないでください。この記事で一つずつ説明していきます。

インストールと初期化

次に、フロントエンドプロジェクトに Supabase をインストールします。まず依存パッケージをインストールします:

npm install @supabase/supabase-js

インストールが終わったら、Supabase Dashboard で2つの項目を探します: Project URL と Anon Public Key です。この2つは Settings > API の中で見つけられます。

そして Supabase Client を初期化します:

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

const supabaseUrl = 'https://your-project-id.supabase.co';
const supabaseAnonKey = 'your-anon-public-key';

export const supabase = createClient(supabaseUrl, supabaseAnonKey);

このコードが Supabase と通信するための入口です。この後のすべての操作——データベースクエリ、ユーザーログイン、ファイルアップロード——はすべてこの supabase オブジェクトを通して行います。

接続テスト

初期化が終わったら、まず接続できるかどうかをテストします。最も簡単な方法は、データベースにデータがあるかどうかを問い合わせることです:

const { data, error } = await supabase.from('users').select('*');

if (error) {
  console.error('接続失敗:', error.message);
} else {
  console.log('接続成功!', data);
}

もし users テーブルが見つからないというエラーが出ても、それは正常です——まだテーブルを作成していないからです。次の節でテーブルの作り方を説明します。

よくあるエラーにはいくつかの種類があります:

  • URL や Key の打ち間違い: 完全にコピーできているか確認しましょう
  • CORS エラー: フロントエンドプロジェクトで CORS の設定が必要な場合がありますが、Supabase はデフォルトで許可しています
  • ネットワークの問題: 時々発生します。数分待ってから再試行しましょう

すべてが正常なら、返ってきたデータが見られるはずです——空の配列 [] かもしれませんが、それはテーブルにまだデータがないことを意味します。


Database: データベース操作

ここが Supabase のコアです。以前 PostgreSQL を使ったことがあれば、かなり馴染みがあると感じるでしょう。フロントエンド開発者で SQL に慣れていない場合でも、Supabase は可視化された Table Editor を提供しているので、SQL を書かなくてもデータベースを操作できます。

データテーブルを作成する

テーブルを作成する方法は2つあります: Table Editor(可視化)を使うか、SQL を書くかです。

まず Table Editor について。Dashboard の左側で「Table Editor」をクリックし、「Create a new table」をクリックします。テーブル名、フィールド名、フィールドの型を入力する必要があります。

たとえば、users テーブルを作成してみましょう:

フィールド名制約
idint8Primary Key, Auto Increment
emailtextUnique, Not Null
nametext-
created_attimestamptzDefault: now()

入力が終わったら「Save」をクリックすると、テーブルが作成されます。

ただ、私は SQL を書く方が速いと思います。特に今後、複雑な操作をするようになるとなおさらです。Supabase は SQL Editor を提供しており、Dashboard の左側で見つけられます。

users テーブルと projects テーブルを作成する SQL:

-- 用户表
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  email TEXT UNIQUE NOT NULL,
  name TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- 项目表
CREATE TABLE projects (
  id SERIAL PRIMARY KEY,
  user_id INTEGER REFERENCES users(id),
  title TEXT NOT NULL,
  description TEXT,
  status TEXT DEFAULT 'active',
  created_at TIMESTAMPTZ DEFAULT NOW()
);

ここに細かいポイントがあります: projects テーブルの user_id は外部キーで、users テーブルの id に関連付けられています。こうすることで、各プロジェクトはあるユーザーに属します——これがリレーショナルデータベースの利点で、データの関係がとても明確です。

データ操作 CRUD

テーブルが作成できたので、追加・削除・更新・検索を試してみましょう。

データの挿入:

// 插入一个用户
const { data, error } = await supabase
  .from('users')
  .insert([
    { email: 'user@example.com', name: 'John Doe' }
  ]);

if (error) {
  console.error('插入失败:', error.message);
} else {
  console.log('插入成功:', data);
}

データの検索:

// 查询所有项目,顺便把用户信息也带上
const { data, error } = await supabase
  .from('projects')
  .select(`
    *,
    users (
      name,
      email
    )
  `)
  .eq('status', 'active')
  .order('created_at', { ascending: false });

console.log(data);

このクエリはなかなか面白いものです: projects テーブルを検索するだけでなく、外部キーを通じて関連するユーザー情報も取り出しています——1つのクエリで2つのテーブルのデータを取得できるのです。Firestore でこれを実現するには何度もクエリを書き、さらに手作業でデータを結合しなければなりません。

データの更新:

const { data, error } = await supabase
  .from('projects')
  .update({ status: 'completed' })
  .eq('id', 1);

データの削除:

const { data, error } = await supabase
  .from('projects')
  .delete()
  .eq('id', 1);

これらの操作はどれも直感的です。ただ、細かいポイントがあります: .eq() は条件フィルタで、「等しい」という意味です。Supabase は他にも多くのフィルタメソッドを提供しています。たとえば .gt()(より大きい)、.lt()(より小さい)、.like()(あいまい一致)——基本的に SQL のよく使うフィルタはすべて対応しています。

Row Level Security (RLS)

ここはかなり重要で、特にユーザー関連の機能を作るときに役立ちます。

RLS の正式名称は Row Level Security、行レベルセキュリティです。どういう意味かというと、ユーザーが自分のデータだけを見られて、他人のデータは見られないように制御できるということです。

たとえば: projects テーブルにはたくさんのプロジェクトがありますが、ユーザーがログイン後に自分の作成したプロジェクトだけを見られて、他人のものは見られないようにしたいとします。

まず RLS を有効にします:

ALTER TABLE projects ENABLE ROW LEVEL SECURITY;

そして Policy を作成します:

-- 用户只能看到自己的项目
CREATE POLICY "Users can view their own projects"
ON projects FOR SELECT
USING (user_id = auth.uid());

ここで auth.uid() は Supabase が提供する関数で、現在ログインしているユーザーの ID を返します。この Policy の意味は: projects テーブルを検索するとき、user_id が現在のユーザー ID と等しいレコードだけを返す、ということです。

同様に、挿入・更新・削除の Policy も作成できます:

-- 用户只能插入自己的项目
CREATE POLICY "Users can insert their own projects"
ON projects FOR INSERT
WITH CHECK (user_id = auth.uid());

-- 用户只能更新自己的项目
CREATE POLICY "Users can update their own projects"
ON projects FOR UPDATE
USING (user_id = auth.uid())
WITH CHECK (user_id = auth.uid());

-- 用户只能删除自己的项目
CREATE POLICY "Users can delete their own projects"
ON projects FOR DELETE
USING (user_id = auth.uid());

こうすることで、たとえ誰かが悪意を持って他人のデータを検索しようとしても、取得できません——データベース層で遮断されるのです。フロントエンドのコードで判定するよりずっと安全です。


Auth: ユーザー認証システム

認証の部分について、Supabase はかなり充実した作りになっています。多くのログイン方式に対応しています: メールアドレスとパスワードによるログイン、パスワード不要のログイン(Magic Link)、ワンタイムパスワード(OTP)、ソーシャルログイン(Google、GitHub、Apple など 20以上のプラットフォーム)、電話番号ログイン、さらに企業向けの SSO もあります。

認証機能の概要

まず Supabase Auth のコアな仕組みについて話しましょう。

これは JWT Token——JSON Web Token を使っています。ユーザーがログインに成功すると、Supabase は Token を生成し、フロントエンドはこの Token を持ってバックエンド API にリクエストします。バックエンドは Token を検証し、ユーザーの身元を確認します。

しかも Supabase Auth はユーザー情報を自動的に PostgreSQL の auth.users テーブルに保存します——このテーブルは自動的に作成されるので、自分で管理する必要はありません。ユーザー ID、メールアドレス、作成時刻、最終ログイン時刻……こうした情報がすべてこの中に入っています。

さらに Session Persistence、セッションの永続化という機能もあります。どういう意味かというと、ユーザーがログインした後、ブラウザがこのログイン状態を記憶し、次にページを開くときに再ログインが不要になるということです。Supabase のフロントエンド SDK が自動的にこれを処理してくれるので、追加のコードを書く必要はありません。

Email/Password 認証

これは最も一般的なログイン方式です。まず登録から説明します:

// 用户注册
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'securepassword123',
});

if (error) {
  console.error('注册失败:', error.message);
} else {
  console.log('注册成功:', data);
}

登録に成功すると、Supabase はユーザーのメールアドレスに確認メールを送ります。ユーザーがメール内のリンクをクリックすると、アカウントが有効化されます。これはデフォルトの動作で、Dashboard でオフにすることもできますが、おすすめしません——メールアドレスの確認は悪意のある登録を防ぐのに役立ちます。

次にログインです:

// 用户登录
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'securepassword123',
});

if (error) {
  console.error('登录失败:', error.message);
} else {
  console.log('登录成功:', data);
}

ログインに成功すると、ユーザー情報を取得できます:

// 获取当前登录用户
const { data: { user } } = await supabase.auth.getUser();

console.log('当前用户:', user);
// 输出类似:{ id: 'abc123', email: 'user@example.com', ... }

最後にログアウトです:

await supabase.auth.signOut();

ログアウトすると、Session がクリアされ、ユーザーは再ログインする必要があります。

さらによくあるニーズとして、パスワードのリセットがあります。Supabase は既製の機能を提供しています:

// 发送密码重置邮件
const { data, error } = await supabase.auth.resetPasswordForEmail(
  'user@example.com'
);

ユーザーはメールを受け取り、リンクをクリックすればパスワードをリセットできます。この一連の流れに追加のロジックを書く必要はありません。

Social Auth ソーシャルログイン

この機能はかなり人気があります——ユーザーはフォームに入力する必要がなく、Google や GitHub のアカウントで直接ログインでき、体験がずっと良くなります。

まずソーシャルログインを設定します。Dashboard で Authentication > Providers をクリックし、使いたいプラットフォーム、たとえば Google や GitHub を有効にします。

各プラットフォームの設定はそれぞれ異なりますが、大まかな手順は似ています:

  1. Google/GitHub で OAuth App を作成する
  2. Client ID と Client Secret をコピーして Supabase に貼り付ける
  3. コールバック URL(Redirect URL)を設定する

設定が終わると、フロントエンドのコードはとてもシンプルです:

// Google 登录
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
});

// GitHub 登录
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'github',
});

呼び出すと、ページが Google/GitHub の認可ページに遷移し、ユーザーが「同意」をクリックすると、あなたのアプリに戻ってきます。このときユーザーはログインに成功しています。

コールバック URL や追加のパラメータをカスタマイズすることもできます:

const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: 'https://your-app.com/dashboard',
    queryParams: {
      access_type: 'offline',
      prompt: 'consent',
    }
  }
});

redirectTo はログイン成功後に遷移するページです。queryParams は追加の OAuth パラメータで、たとえばオフラインアクセスを要求したり、毎回認可を確認したりするものです。

RLS と Auth の組み合わせ

先ほど RLS の話をしたときに auth.uid() に触れましたが、ここでようやくつなげられます。

ユーザーがログインすると、auth.uid() はユーザーの ID を返します。データベースの Policy でこの ID を使えば、「ユーザーは自分のデータにのみアクセスできる」を実現できます。

たとえば: ユーザーがログイン後にプロジェクトを作成するとき、プロジェクトを自動的に現在のユーザーに関連付けたいとします。

// 创建项目,自动关联当前用户
const { data: { user } } = await supabase.auth.getUser();

const { data, error } = await supabase
  .from('projects')
  .insert([
    {
      title: 'New Project',
      user_id: user.id  // 从 Auth 获取用户 ID
    }
  ]);

そして RLS Policy が以下を保証します:

  • 検索のとき、user_id = auth.uid() のプロジェクトだけが見られる
  • 挿入のとき、user_id は必ず auth.uid() と等しくなければならない
  • 更新と削除も同様

こうすることで、認証と権限制御が完成します——ユーザーのログイン、データの関連付け、権限の制限が一本の線でつながるのです。


Storage: ファイルストレージ

Supabase Storage はオブジェクトストレージサービスで、AWS S3 に似ていますが、使うのはずっと簡単です。ユーザーのアバター、画像、ドキュメントといったファイルを保存するのに適しています。

Storage 機能の概要

コアとなる概念は2つあります: Bucket と Object です。

  • Bucket: ストレージバケットで、フォルダのような概念です。複数の Bucket を作成でき、たとえば avatars でアバターを、documents でドキュメントを保存できます
  • Object: 具体的なファイルで、たとえば avatar.jpgreport.pdf などです

Bucket には2つの権限モードがあります:

  • Public: 公開バケット。誰でもファイルにアクセスできる(公開画像に適している)
  • Private: 非公開バケット。認可されたユーザーだけがアクセスできる(機密文書に適している)

Bucket を作成する

Dashboard で Storage をクリックし、「New Bucket」をクリックしてバケットを作成します。

たとえば、ユーザーのアバターを保存する avatars バケットを作成してみましょう:

  • 名前: avatars
  • Public bucket: チェックを入れる(アバターは一般的に公開)
  • File size limit: 最大ファイルサイズを設定、たとえば 2MB

作成が終わると、このバケットにファイルをアップロードできるようになります。

ファイルのアップロードとダウンロード

ファイルのアップロード:

// 上传用户头像
const file = document.getElementById('avatar-input').files[0];

const { data, error } = await supabase.storage
  .from('avatars')
  .upload('user-id/avatar.jpg', file, {
    cacheControl: '3600',
    upsert: false
  });

if (error) {
  console.error('上传失败:', error.message);
} else {
  console.log('上传成功:', data);
}

ここにいくつかの細かいポイントがあります:

  • upload() の第1引数はファイルパス: 'user-id/avatar.jpg'
  • cacheControl: キャッシュ時間、3600秒
  • upsert: ファイルがすでに存在する場合に上書きするかどうか。false は上書きせずエラーになる、true は上書きする、という意味です

ファイルのダウンロード:

// 下载文件
const { data, error } = await supabase.storage
  .from('avatars')
  .download('user-id/avatar.jpg');

if (error) {
  console.error('下载失败:', error.message);
} else {
  // data 是 Blob 对象,可以转成 URL 显示
  const url = URL.createObjectURL(data);
  document.getElementById('avatar-img').src = url;
}

バケットが公開なら、公開 URL を直接取得することもできます:

// 获取公开 URL
const { data } = supabase.storage
  .from('avatars')
  .getPublicUrl('user-id/avatar.jpg');

console.log('公开 URL:', data.publicUrl);
// 输出类似:https://your-project.supabase.co/storage/v1/object/public/avatars/user-id/avatar.jpg

非公開バケットのファイルは一時的なアクセス URL を生成する必要があります:

// 生成临时访问 URL(有效期 1 小时)
const { data, error } = await supabase.storage
  .from('documents')
  .createSignedUrl('private-file.pdf', 3600);

console.log('临时 URL:', data.signedUrl);

ファイルの削除:

const { data, error } = await supabase.storage
  .from('avatars')
  .remove(['user-id/avatar.jpg']);

アクセス制御

Storage も RLS Policy に対応しており、ユーザーがどのファイルにアクセスできるかを制御できます。

たとえば: ユーザーは自分のフォルダ内のファイルだけをアップロードしたりアクセスしたりできる、とします。

-- 用户只能上传到自己的文件夹
CREATE POLICY 'Users can upload to their own folder'
ON storage.objects FOR INSERT
WITH CHECK (
  bucket_id = 'avatars' AND
  (storage.foldername(name))[1] = auth.uid()::text
);

-- 用户只能访问自己文件夹里的文件
CREATE POLICY 'Users can access their own files'
ON storage.objects FOR SELECT
USING (
  bucket_id = 'avatars' AND
  (storage.foldername(name))[1] = auth.uid()::text
);

ここで storage.foldername(name) はファイルパスのフォルダ部分を抽出するものです。[1] は最初のフォルダ名、つまりユーザー ID を取り出します。

こうすることで、ユーザーがファイルをアップロードするとき、パスは必ず user-id/filename でなければなりません——RLS が user-id が現在のユーザー ID と等しいかどうかを検証します。等しくなければアップロードを拒否します。


実践ケース: タスク管理アプリ

ここまで色々と説明してきたので、完全なケースで全体をつなげてみましょう。

シンプルなタスク管理アプリを作るとして、機能には以下が含まれるとします:

  • ユーザーの登録とログイン
  • タスクの作成、タスク一覧の表示
  • タスク添付ファイルのアップロード

データベース設計

まずテーブル構造を設計します:

-- 用户表(Auth 自动创建,不用手动建)
-- tasks 表
CREATE TABLE tasks (
  id SERIAL PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id) NOT NULL,
  title TEXT NOT NULL,
  description TEXT,
  status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'completed')),
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- 启用 RLS
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;

-- Policy:用户只能访问自己的任务
CREATE POLICY 'Users can manage their own tasks'
ON tasks FOR ALL
USING (user_id = auth.uid())
WITH CHECK (user_id = auth.uid());

-- attachments 表(关联任务)
CREATE TABLE task_attachments (
  id SERIAL PRIMARY KEY,
  task_id INTEGER REFERENCES tasks(id) ON DELETE CASCADE,
  file_name TEXT NOT NULL,
  file_path TEXT NOT NULL,
  file_size INTEGER,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- 启用 RLS
ALTER TABLE task_attachments ENABLE ROW LEVEL SECURITY;

-- Policy:通过任务关联判断权限
CREATE POLICY 'Users can manage attachments of their tasks'
ON task_attachments FOR ALL
USING (
  EXISTS (
    SELECT 1 FROM tasks
    WHERE tasks.id = task_attachments.task_id
    AND tasks.user_id = auth.uid()
  )
);

ここに細かいポイントがあります: task_attachments テーブルの Policy は直接 user_id を判定するのではなく、task_id を通じて tasks テーブルに関連付け、さらに tasks.user_id を判定しています。こうすることで、タスクの持ち主だけが添付ファイルを管理できるようになります。

Storage Bucket を作成する

task-files バケットを作成し、非公開に設定します:

-- 在 Dashboard 创建,或者用 SQL
INSERT INTO storage.buckets (name, public)
VALUES ('task-files', false);

-- Policy:用户只能上传到自己的文件夹
CREATE POLICY 'Users can upload task files'
ON storage.objects FOR INSERT
WITH CHECK (
  bucket_id = 'task-files' AND
  (storage.foldername(name))[1] = auth.uid()::text
);

CREATE POLICY 'Users can access task files'
ON storage.objects FOR SELECT
USING (
  bucket_id = 'task-files' AND
  (storage.foldername(name))[1] = auth.uid()::text
);

コア機能の実装

すべての機能をつなげるコード:

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

const supabase = createClient(
  'https://your-project.supabase.co',
  'your-anon-key'
);

// 1. 用户注册
async function register(email: string, password: string) {
  const { data, error } = await supabase.auth.signUp({
    email,
    password,
  });

  if (error) throw error;
  return data;
}

// 2. 用户登录
async function login(email: string, password: string) {
  const { data, error } = await supabase.auth.signInWithPassword({
    email,
    password,
  });

  if (error) throw error;
  return data;
}

// 3. 创建任务
async function createTask(title: string, description?: string) {
  const { data: { user } } = await supabase.auth.getUser();

  if (!user) throw new Error('未登录');

  const { data, error } = await supabase
    .from('tasks')
    .insert([
      {
        title,
        description,
        user_id: user.id,
      }
    ])
    .select();

  if (error) throw error;
  return data[0];
}

// 4. 获取任务列表
async function getTasks() {
  const { data, error } = await supabase
    .from('tasks')
    .select('*')
    .order('created_at', { ascending: false });

  if (error) throw error;
  return data;
}

// 5. 上传附件
async function uploadAttachment(taskId: number, file: File) {
  const { data: { user } } = await supabase.auth.getUser();

  if (!user) throw new Error('未登录');

  const filePath = user.id + '/' + taskId + '/' + file.name;

  const { data, error } = await supabase.storage
    .from('task-files')
    .upload(filePath, file);

  if (error) throw error;

  // 记录到 task_attachments 表
  const { data: attachment, error: dbError } = await supabase
    .from('task_attachments')
    .insert([
      {
        task_id: taskId,
        file_name: file.name,
        file_path: filePath,
        file_size: file.size,
      }
    ])
    .select();

  if (dbError) throw dbError;

  return attachment[0];
}

// 6. 获取任务附件
async function getTaskAttachments(taskId: number) {
  const { data, error } = await supabase
    .from('task_attachments')
    .select('*')
    .eq('task_id', taskId);

  if (error) throw error;

  // 生成临时访问 URL
  const attachmentsWithURLs = data.map(async (attachment) => {
    const { data: urlData } = await supabase.storage
      .from('task-files')
      .createSignedUrl(attachment.file_path, 3600);

    return {
      ...attachment,
      url: urlData.signedUrl,
    };
  });

  return Promise.all(attachmentsWithURLs);
}

// 7. 完成任务
async function completeTask(taskId: number) {
  const { data, error } = await supabase
    .from('tasks')
    .update({ status: 'completed', updated_at: new Date() })
    .eq('id', taskId)
    .select();

  if (error) throw error;
  return data[0];
}

// 8. 登出
async function logout() {
  await supabase.auth.signOut();
}

このコードはユーザーの登録・ログイン、タスクの CRUD、ファイルのアップロードをカバーしています——基本的に完全なアプリの骨組みが揃っています。これをベースに、タスクの分類、タグ、コメント、通知……といった機能をさらに追加できます。


Supabase vs Firebase の比較

ここまで色々と説明してきましたが、こう思うかもしれません: Supabase と Firebase は結局どちらを選ぶべきか? この点を詳しく比較してみましょう。

コアな違い

比較項目SupabaseFirebase
データベースの種類PostgreSQL(リレーショナル)Firestore(ドキュメント型 NoSQL)
オープンソース性完全オープンソースGoogle のクローズドソース製品
料金モデル固定 $25/月(Pro Plan)従量課金(読み取り/書き込み/ストレージを別々に計算)
データの所有権100% 自分で保有、いつでもエクスポート可能データは Google にあり、エクスポートが面倒
クエリ能力SQL の強力なクエリ、複雑な関連付けクエリに制限あり、複雑な関連付けは複数回のクエリが必要
リアルタイム機能WebSocket、手動での購読が必要Firestore はネイティブにリアルタイム、自動同期
オフライン対応キャッシュを自分で実装する必要ありネイティブのオフライン対応、自動同期
移行の難易度標準的な PostgreSQL、移行が容易独自フォーマット、移行コストが高い

適用シーンの推奨

Supabase を選ぶシーン:

  • データの関係が複雑で、SQL クエリや関連付けが必要
  • 長期プロジェクトで、データを完全に管理したい
  • チームが SQL に慣れていて、PostgreSQL を使いたい
  • コストに敏感で、予算が限られている(固定費用は従量課金より安定)
  • オープンソース優先で、技術スタックを自分で管理したい

Firebase を選ぶシーン:

  • 素早いプロトタイプ開発で、時間が切迫している
  • モバイルアプリ優先で、オフライン機能が重要
  • リアルタイム同期がコア機能(たとえばチャットアプリ)
  • すでに Google Cloud のエコシステムを使っている
  • データ構造がシンプルで、クエリが複雑でない

正直に言うと、私は以前 Firebase でいくつかのプロジェクトを作りましたが、後で請求額に驚きました——1か月 $800以上だったのです。その後 Supabase に移行したところ、同じ機能で、コストは $25前後に安定しました。この差はかなり大きいものです。

しかも Firebase のデータエクスポートは厄介です——Firestore のデータフォーマットは独自のもので、エクスポートした後にさらに変換しないと使えません。Supabase なら簡単で、SQL ファイルを直接エクスポートしたり、PostgreSQL のツールで CSV をエクスポートしたりでき、いつでも他のデータベースに移行できます。

ただし Firebase はリアルタイム機能とモバイルアプリの分野では確かに少し強いです——Firestore のリアルタイム同期やオフライン対応はよくできています。チャットアプリやコラボレーションドキュメントといった強いリアルタイム要件がある場合は、Firebase の方が使いやすいかもしれません。


まとめと提案

ここまで色々と説明してきましたが、締めくくりに入りましょう。

Supabase のコアな価値は次のいくつかです:

  • PostgreSQL データベース——強力なクエリ、明確な関係
  • 企業レベルの認証——多様なログイン方式、JWT Token、RLS 権限
  • オブジェクトストレージ——シンプルで使いやすく、アクセス制御に対応
  • リアルタイム同期、エッジコンピューティング——今後さらに探求できる

しかもオープンソースで、データを完全に管理でき、コストも安定している——これらは長期プロジェクトにとってかなり重要です。

もしあなたがフロントエンド開発者で、フルスタックアプリを素早く構築したいけれど、バックエンドの設定に振り回されたくないなら、Supabase は良い選択肢です。基本的に数時間でデータベース、認証、ストレージの3機能を片づけられます——従来のバックエンド開発よりずっと速いのです。

学習の提案:

  • まず Database、Auth、Storage の3つをしっかり理解しましょう。これがコアです
  • 次に RLS Policy を試してみましょう。この部分は認証と権限制御がどうつながるかを理解するのに役立ちます
  • 実践プロジェクトが最良の学習方法です——何かニーズを見つけて、ゼロからアプリを構築してみましょう
  • その後、Realtime、Edge Functions、Vector Database といった高度な機能を探求しましょう

ここまでで、だいたい話し終えました。Supabase の公式ドキュメントはかなり明確に書かれているので、分からないことがあれば見てみるとよいでしょう: supabase.com/docs。さらにコミュニティには多くのチュートリアルやケースがあり、たとえば GitHub には awesome-supabase というリストがあって、たくさんのリソースが整理されています。

一言でまとめると: PostgreSQL でバックエンドを作りたいけれど、設定をいじり回したくないなら、Supabase が道を整えてくれます——そのまま進めばいいのです。


Supabase の3つのコア機能を素早く使い始める

ゼロから Supabase プロジェクトを構築し、Database、Auth、Storage の3つのコア機能を習得する

⏱️ 目安時間: 2 時間

  1. 1

    ステップ1: Supabase プロジェクトを作成する

    supabase.com にアクセスしてアカウント登録し、新しいプロジェクトを作成します:

    • プロジェクト名: my-first-app
    • データベースパスワード: 必ず控えておく
    • リージョン: 最も近い場所を選択(日本なら Singapore か Tokyo)

    作成後、Settings > API で Project URL と Anon Public Key を取得します
  2. 2

    ステップ2: クライアントのインストールと初期化

    フロントエンドプロジェクトに依存パッケージをインストールします:

    npm install @supabase/supabase-js

    Supabase Client を初期化します:

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

    const supabase = createClient(
    'https://your-project.supabase.co',
    'your-anon-key'
    );
  3. 3

    ステップ3: データテーブルの作成と RLS の設定

    SQL Editor を使ってテーブルを作成します:

    CREATE TABLE tasks (
    id SERIAL PRIMARY KEY,
    user_id UUID REFERENCES auth.users(id),
    title TEXT NOT NULL,
    status TEXT DEFAULT 'pending'
    );

    ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;

    CREATE POLICY 'Users can manage their own tasks'
    ON tasks FOR ALL
    USING (user_id = auth.uid());

    RLS Policy によってユーザーは自分のデータにのみアクセスできるようになります
  4. 4

    ステップ4: ユーザー認証を実装する

    Email/Password 認証のコード例:

    // 登録
    await supabase.auth.signUp({
    email: 'user@example.com',
    password: 'password123'
    });

    // ログイン
    await supabase.auth.signInWithPassword({
    email: 'user@example.com',
    password: 'password123'
    });

    // 現在のユーザーを取得
    const { data: { user } } = await supabase.auth.getUser();

    Google、GitHub など 20以上のプラットフォームのソーシャルログインに対応
  5. 5

    ステップ5: ファイルストレージを設定する

    Storage Bucket(公開または非公開)を作成します:

    // ファイルをアップロード
    await supabase.storage
    .from('avatars')
    .upload('user-id/avatar.jpg', file);

    // 公開 URL を取得
    const { data } = supabase.storage
    .from('avatars')
    .getPublicUrl('user-id/avatar.jpg');

    非公開ファイルは createSignedUrl で一時的なアクセスリンクを生成します

FAQ

Supabase と Firebase の本質的な違いは何ですか?
Supabase は PostgreSQL リレーショナルデータベースを使い、複雑な SQL クエリやデータの関連付けに対応します。Firebase は Firestore というドキュメント型 NoSQL を使い、クエリ能力に制限があります。Supabase は完全オープンソースで、固定料金 $25/月、データを 100% 自分で管理できます。Firebase はクローズドソースで、従量課金(月 $800以上になることもある)、データのエクスポートが面倒です。
Supabase はどんなプロジェクトに向いていますか?
以下のような場面に向いています:

• データの関係が複雑で、SQL クエリや関連付けが必要
• 長期プロジェクトで、データを完全に管理したい
• コストに敏感で、予算が限られている(固定費用の方が安定)
• フロントエンド開発者がフルスタックアプリを素早く構築したい
• オープンソース優先で、技術スタックを自分で管理したい
Row Level Security (RLS) とは何ですか? 何の役に立ちますか?
RLS は PostgreSQL の行レベルセキュリティ機能で、データベース層でユーザーが自分のデータにのみアクセスできるよう制御できます。Supabase Auth の auth.uid() 関数と組み合わせることで、ログイン後のユーザーが自分の作成したレコードのみ閲覧・編集できるようになり、フロントエンドのコードで判定するより安全です。
Supabase Auth はどんなログイン方式に対応していますか?
複数のログイン方式に対応しています:

• Email/Password(メールアドレスとパスワード)
• Magic Link(パスワード不要のログイン)
• OTP(ワンタイムパスワード)
• Social Auth(Google、GitHub、Apple など 20以上のプラットフォーム)
• Phone Auth(Twilio、MessageBird)
• SSO(企業向けシングルサインオン)
Supabase Storage の公開バケットと非公開バケットの違いは何ですか?
公開バケット(Public Bucket)のファイルは誰でもアクセスでき、公開画像やアバターなどに適しています。非公開バケット(Private Bucket)のファイルはアクセスに認可が必要で、createSignedUrl で一時的なアクセスリンク(有効期限を設定可能)を生成します。機密文書やユーザーのアップロードファイルなどに適しています。
Firebase から Supabase への移行にはどのくらい時間がかかりますか?
シンプルなアプリなら 2〜3日で移行できます。Firestore のデータを PostgreSQL のテーブル構造に変換し、Firebase Auth のユーザーを Supabase Auth にエクスポートし、Cloud Storage のファイルを Supabase Storage に移行します。複雑なアプリでは 1〜2週間かかることもあり、主な作業量はデータモデルの変換とクエリロジックの書き換えにあります。
Supabase の無料プランには何が含まれますか?
無料プランには以下が含まれます: 無制限の API リクエスト、月間アクティブユーザー 50,000人、データベースストレージ 500MB、ファイルストレージ 1GB、帯域幅 5GB。個人の MVP 開発に適しており、制限を超える場合は Pro Plan($25/月)へのアップグレードが必要です。

7分で読めます · 公開日: 2026年4月3日 · 更新日: 2026年6月8日

関連記事

コメント

GitHubアカウントでログインしてコメントできます