Supabase完全ガイド2026 — Firebase代替のオープンソースBaaS


Supabaseは「オープンソース版Firebase」として急速に普及しているBaaS(Backend as a Service)プラットフォームです。PostgreSQLベースのデータベース、認証、ストレージ、リアルタイム機能を提供し、2026年現在、多くの開発者に支持されています。この記事では、Supabaseの全機能を実践的に解説します。

Supabaseとは

Supabaseは、以下の特徴を持つオープンソースのBaaSです。

  • PostgreSQLベース: 本格的なRDBMSを使用
  • オープンソース: GitHubで公開、セルフホスティング可能
  • REST/GraphQL API自動生成: スキーマから自動でAPIを生成
  • リアルタイム機能: WebSocketによるデータ同期
  • 認証システム: Email、OAuth、マジックリンクなど
  • ストレージ: S3互換のファイルストレージ
  • Edge Functions: Deno製のサーバーレス関数

クイックスタート

プロジェクト作成

# Supabase CLIインストール
npm install -g supabase

# プロジェクト初期化
mkdir my-supabase-app
cd my-supabase-app
supabase init

# ローカル開発環境起動
supabase start

# Supabaseクライアントインストール
npm install @supabase/supabase-js

基本的な接続設定

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

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;

export const supabase = createClient(supabaseUrl, supabaseAnonKey);

環境変数(.env.local):

NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGc...

データベース操作

テーブル作成(SQL)

Supabase Studioまたはマイグレーションファイルでテーブルを作成します。

-- supabase/migrations/20260205000000_create_posts.sql
CREATE TABLE posts (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  title TEXT NOT NULL,
  content TEXT,
  author_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Row Level Security (RLS)を有効化
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- ポリシー: 誰でも読める
CREATE POLICY "Posts are viewable by everyone"
  ON posts FOR SELECT
  USING (true);

-- ポリシー: 自分の投稿だけ作成・更新・削除できる
CREATE POLICY "Users can insert their own posts"
  ON posts FOR INSERT
  WITH CHECK (auth.uid() = author_id);

CREATE POLICY "Users can update their own posts"
  ON posts FOR UPDATE
  USING (auth.uid() = author_id);

CREATE POLICY "Users can delete their own posts"
  ON posts FOR DELETE
  USING (auth.uid() = author_id);

データの取得(SELECT)

// 全件取得
const { data, error } = await supabase
  .from('posts')
  .select('*');

// 特定のカラムのみ取得
const { data, error } = await supabase
  .from('posts')
  .select('id, title, created_at');

// フィルタリング
const { data, error } = await supabase
  .from('posts')
  .select('*')
  .eq('author_id', userId)
  .order('created_at', { ascending: false })
  .limit(10);

// 複雑な条件
const { data, error } = await supabase
  .from('posts')
  .select('*')
  .or('title.ilike.%typescript%,content.ilike.%typescript%')
  .gte('created_at', '2026-01-01')
  .order('created_at', { ascending: false });

// ページネーション
const pageSize = 20;
const page = 1;
const { data, error, count } = await supabase
  .from('posts')
  .select('*', { count: 'exact' })
  .range(page * pageSize, (page + 1) * pageSize - 1);

JOINとリレーション

-- コメントテーブル
CREATE TABLE comments (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  post_id UUID REFERENCES posts(id) ON DELETE CASCADE,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  content TEXT NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
// 投稿とコメントを同時取得
const { data, error } = await supabase
  .from('posts')
  .select(`
    *,
    comments (
      id,
      content,
      created_at,
      user_id
    )
  `)
  .eq('id', postId)
  .single();

// ネストしたリレーション
const { data, error } = await supabase
  .from('posts')
  .select(`
    *,
    author:author_id (
      id,
      email,
      user_metadata
    ),
    comments (
      id,
      content,
      user:user_id (
        email
      )
    )
  `);

データの挿入(INSERT)

// 1件挿入
const { data, error } = await supabase
  .from('posts')
  .insert({
    title: 'My First Post',
    content: 'Hello Supabase!',
    author_id: user.id,
  })
  .select()
  .single();

// 複数件挿入
const { data, error } = await supabase
  .from('posts')
  .insert([
    { title: 'Post 1', content: 'Content 1', author_id: user.id },
    { title: 'Post 2', content: 'Content 2', author_id: user.id },
  ])
  .select();

// upsert(存在すれば更新、なければ挿入)
const { data, error } = await supabase
  .from('posts')
  .upsert({
    id: existingId,
    title: 'Updated Title',
    content: 'Updated Content',
    author_id: user.id,
  })
  .select();

データの更新(UPDATE)

// 条件に一致するレコードを更新
const { data, error } = await supabase
  .from('posts')
  .update({ title: 'New Title' })
  .eq('id', postId)
  .select();

// 複数フィールドを更新
const { data, error } = await supabase
  .from('posts')
  .update({
    title: 'Updated Title',
    content: 'Updated Content',
    updated_at: new Date().toISOString(),
  })
  .eq('id', postId)
  .select();

データの削除(DELETE)

// 削除
const { error } = await supabase
  .from('posts')
  .delete()
  .eq('id', postId);

// 複数削除
const { error } = await supabase
  .from('posts')
  .delete()
  .in('id', [id1, id2, id3]);

認証機能

Supabaseの認証システムは非常に強力で、多くの認証方法をサポートしています。

Email/Password認証

// サインアップ
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'secure-password',
  options: {
    data: {
      username: 'john_doe',
      full_name: 'John Doe',
    },
  },
});

// サインイン
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'secure-password',
});

// サインアウト
const { error } = await supabase.auth.signOut();

マジックリンク(パスワードレス認証)

// マジックリンクを送信
const { data, error } = await supabase.auth.signInWithOtp({
  email: 'user@example.com',
  options: {
    emailRedirectTo: 'https://example.com/auth/callback',
  },
});

OAuth認証

// Google認証
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: 'https://example.com/auth/callback',
  },
});

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

// サポートされるプロバイダー:
// - Google, GitHub, GitLab, Bitbucket
// - Facebook, Twitter, Discord, Slack
// - Apple, Microsoft, Notion, Spotify
// - Twitch, LinkedIn, WorkOS

セッション管理

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

// セッション取得
const { data: { session } } = await supabase.auth.getSession();

// 認証状態の監視
supabase.auth.onAuthStateChange((event, session) => {
  console.log('Auth event:', event);
  if (event === 'SIGNED_IN') {
    console.log('User signed in:', session?.user);
  } else if (event === 'SIGNED_OUT') {
    console.log('User signed out');
  }
});

パスワードリセット

// パスワードリセットメール送信
const { data, error } = await supabase.auth.resetPasswordForEmail(
  'user@example.com',
  {
    redirectTo: 'https://example.com/reset-password',
  }
);

// 新しいパスワードを設定
const { data, error } = await supabase.auth.updateUser({
  password: 'new-secure-password',
});

React/Next.jsでの認証フック

// hooks/useAuth.ts
import { useEffect, useState } from 'react';
import { supabase } from '@/lib/supabase';
import type { User } from '@supabase/supabase-js';

export function useAuth() {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // 初期セッション取得
    supabase.auth.getSession().then(({ data: { session } }) => {
      setUser(session?.user ?? null);
      setLoading(false);
    });

    // 認証状態の変更を監視
    const {
      data: { subscription },
    } = supabase.auth.onAuthStateChange((_event, session) => {
      setUser(session?.user ?? null);
    });

    return () => subscription.unsubscribe();
  }, []);

  return { user, loading };
}

使用例:

// app/dashboard/page.tsx
'use client';
import { useAuth } from '@/hooks/useAuth';
import { redirect } from 'next/navigation';

export default function DashboardPage() {
  const { user, loading } = useAuth();

  if (loading) return <div>Loading...</div>;
  if (!user) redirect('/login');

  return <div>Welcome, {user.email}!</div>;
}

ストレージ機能

Supabase Storageは、S3互換のファイルストレージを提供します。

バケット作成

// バケット作成(管理画面または)
const { data, error } = await supabase.storage.createBucket('avatars', {
  public: false, // true = 誰でもアクセス可能
  fileSizeLimit: 1024 * 1024 * 2, // 2MB
  allowedMimeTypes: ['image/png', 'image/jpeg'],
});

ファイルアップロード

// ファイルアップロード
async function uploadAvatar(file: File, userId: string) {
  const fileExt = file.name.split('.').pop();
  const fileName = `${userId}-${Date.now()}.${fileExt}`;
  const filePath = `avatars/${fileName}`;

  const { data, error } = await supabase.storage
    .from('avatars')
    .upload(filePath, file, {
      cacheControl: '3600',
      upsert: false,
    });

  if (error) throw error;

  // 公開URLを取得
  const { data: { publicUrl } } = supabase.storage
    .from('avatars')
    .getPublicUrl(filePath);

  return publicUrl;
}

ファイルダウンロード

// ファイルダウンロード
const { data, error } = await supabase.storage
  .from('avatars')
  .download('avatars/user-123.png');

// Blob URLを作成
if (data) {
  const url = URL.createObjectURL(data);
  // <img src={url} />
}

ファイル削除

// 1ファイル削除
const { data, error } = await supabase.storage
  .from('avatars')
  .remove(['avatars/user-123.png']);

// 複数ファイル削除
const { data, error } = await supabase.storage
  .from('avatars')
  .remove(['avatars/user-1.png', 'avatars/user-2.png']);

React での画像アップロード例

'use client';
import { useState } from 'react';
import { supabase } from '@/lib/supabase';

export function AvatarUpload({ userId }: { userId: string }) {
  const [uploading, setUploading] = useState(false);
  const [avatarUrl, setAvatarUrl] = useState<string | null>(null);

  const uploadAvatar = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;

    setUploading(true);

    const fileExt = file.name.split('.').pop();
    const fileName = `${userId}.${fileExt}`;
    const filePath = `${fileName}`;

    const { error } = await supabase.storage
      .from('avatars')
      .upload(filePath, file, { upsert: true });

    if (error) {
      alert('Upload failed: ' + error.message);
    } else {
      const { data } = supabase.storage.from('avatars').getPublicUrl(filePath);
      setAvatarUrl(data.publicUrl);
    }

    setUploading(false);
  };

  return (
    <div>
      {avatarUrl && <img src={avatarUrl} alt="Avatar" className="w-20 h-20" />}
      <input type="file" onChange={uploadAvatar} disabled={uploading} />
      {uploading && <p>Uploading...</p>}
    </div>
  );
}

リアルタイム機能

Supabaseのリアルタイム機能を使うと、データベースの変更をリアルタイムで購読できます。

Realtime購読の基本

// テーブルの変更を監視
const channel = supabase
  .channel('posts-channel')
  .on(
    'postgres_changes',
    {
      event: '*', // INSERT, UPDATE, DELETE, *
      schema: 'public',
      table: 'posts',
    },
    (payload) => {
      console.log('Change received!', payload);
    }
  )
  .subscribe();

// 購読解除
channel.unsubscribe();

INSERT/UPDATE/DELETE別の処理

const channel = supabase
  .channel('posts-changes')
  .on(
    'postgres_changes',
    { event: 'INSERT', schema: 'public', table: 'posts' },
    (payload) => {
      console.log('New post:', payload.new);
    }
  )
  .on(
    'postgres_changes',
    { event: 'UPDATE', schema: 'public', table: 'posts' },
    (payload) => {
      console.log('Updated post:', payload.new);
      console.log('Old post:', payload.old);
    }
  )
  .on(
    'postgres_changes',
    { event: 'DELETE', schema: 'public', table: 'posts' },
    (payload) => {
      console.log('Deleted post:', payload.old);
    }
  )
  .subscribe();

フィルタリング

// 特定の条件でフィルタ
const channel = supabase
  .channel('my-posts')
  .on(
    'postgres_changes',
    {
      event: '*',
      schema: 'public',
      table: 'posts',
      filter: `author_id=eq.${userId}`,
    },
    (payload) => {
      console.log('My post changed:', payload);
    }
  )
  .subscribe();

Reactでのリアルタイムデータ表示

'use client';
import { useEffect, useState } from 'react';
import { supabase } from '@/lib/supabase';

type Post = {
  id: string;
  title: string;
  content: string;
  created_at: string;
};

export function RealtimePosts() {
  const [posts, setPosts] = useState<Post[]>([]);

  useEffect(() => {
    // 初期データ取得
    const fetchPosts = async () => {
      const { data } = await supabase
        .from('posts')
        .select('*')
        .order('created_at', { ascending: false });
      setPosts(data || []);
    };
    fetchPosts();

    // リアルタイム購読
    const channel = supabase
      .channel('posts-realtime')
      .on(
        'postgres_changes',
        { event: 'INSERT', schema: 'public', table: 'posts' },
        (payload) => {
          setPosts((prev) => [payload.new as Post, ...prev]);
        }
      )
      .on(
        'postgres_changes',
        { event: 'UPDATE', schema: 'public', table: 'posts' },
        (payload) => {
          setPosts((prev) =>
            prev.map((post) =>
              post.id === payload.new.id ? (payload.new as Post) : post
            )
          );
        }
      )
      .on(
        'postgres_changes',
        { event: 'DELETE', schema: 'public', table: 'posts' },
        (payload) => {
          setPosts((prev) => prev.filter((post) => post.id !== payload.old.id));
        }
      )
      .subscribe();

    return () => {
      channel.unsubscribe();
    };
  }, []);

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

Edge Functions

Supabase Edge Functionsは、Deno製のサーバーレス関数です。

Edge Function作成

# Edge Function作成
supabase functions new hello-world
// supabase/functions/hello-world/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';

serve(async (req) => {
  const { name } = await req.json();
  const data = {
    message: `Hello ${name}!`,
  };

  return new Response(JSON.stringify(data), {
    headers: { 'Content-Type': 'application/json' },
  });
});

Edge Functionデプロイ

# デプロイ
supabase functions deploy hello-world

# ローカルで実行
supabase functions serve hello-world

Edge Functionを呼び出す

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

console.log(data); // { message: "Hello Supabase!" }

認証付きEdge Function

// supabase/functions/protected/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';

serve(async (req) => {
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_ANON_KEY')!,
    {
      global: {
        headers: { Authorization: req.headers.get('Authorization')! },
      },
    }
  );

  // ユーザー認証チェック
  const {
    data: { user },
  } = await supabase.auth.getUser();

  if (!user) {
    return new Response(JSON.stringify({ error: 'Unauthorized' }), {
      status: 401,
      headers: { 'Content-Type': 'application/json' },
    });
  }

  return new Response(JSON.stringify({ message: `Hello ${user.email}!` }), {
    headers: { 'Content-Type': 'application/json' },
  });
});

型安全性(TypeScript)

データベース型の自動生成

# 型定義を自動生成
supabase gen types typescript --project-id your-project-id > types/supabase.ts

生成された型を使う:

// lib/supabase.ts
import { createClient } from '@supabase/supabase-js';
import type { Database } from '@/types/supabase';

export const supabase = createClient<Database>(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

// 型安全なクエリ
const { data } = await supabase.from('posts').select('*');
// dataの型は Database['public']['Tables']['posts']['Row'][]

まとめ

Supabaseは、以下のような特徴を持つ強力なBaaSです。

主な機能:

  • PostgreSQLベースのデータベース
  • Row Level Securityによる柔軟な権限管理
  • Email/OAuth/マジックリンク認証
  • S3互換のストレージ
  • リアルタイムデータ購読
  • Deno製Edge Functions
  • TypeScript型生成

Supabaseが適しているケース:

  • 本格的なRDBMSが必要なアプリ
  • リアルタイム性が重要なアプリ
  • オープンソース/セルフホスティングを重視
  • PostgreSQLの強力な機能を使いたい

料金:

  • 無料プラン: 500MB DB、1GB ストレージ、50万 Edge Function実行
  • Proプラン: $25/月〜

Supabaseは、Firebaseの代替として、またはそれ以上の機能を持つBaaSとして、2026年現在、最も注目されているプラットフォームの一つです。