Supabase入門|FirebaseのオープンソースオルタナティブでBaaS開発

kento_morota 24分で読めます

「Firebaseは便利だけど、ベンダーロックインが心配」「SQLデータベースを使いたいのにFirestoreのNoSQL設計に苦労している」「バックエンドをなるべく書かずにアプリを開発したい」――こうした悩みを抱える開発者にとって、Supabaseは最適な選択肢です。

Supabaseは「Firebaseのオープンソースオルタナティブ」を掲げるBaaS(Backend as a Service)プラットフォームで、PostgreSQLを基盤としています。本記事では、Supabaseのセットアップから認証、データベース操作、リアルタイム機能まで、実際のコードを交えて初心者向けに解説します。

Supabaseとは?Firebaseとの比較

Supabaseは、PostgreSQL、認証、リアルタイムサブスクリプション、ストレージ、Edge Functionsなどの機能を統合したBaaSプラットフォームです。

Supabaseの構成要素

  • PostgreSQL:フル機能のリレーショナルデータベース
  • GoTrue:認証サーバー(メール/パスワード、OAuth、マジックリンク)
  • PostgREST:PostgreSQLからRESTful APIを自動生成
  • Realtime:WebSocketベースのリアルタイム変更通知
  • Storage:S3互換のファイルストレージ
  • Edge Functions:Deno ベースのサーバーレス関数
  • pg_graphql:PostgreSQLからGraphQL APIを自動生成

Firebaseとの主な違い

データベース:FirebaseのFirestoreはNoSQL(ドキュメント型)ですが、SupabaseはPostgreSQL(リレーショナル)です。JOINや複雑なクエリが使え、SQLの知識がそのまま活かせます。

オープンソース:Supabaseは全コンポーネントがオープンソースで、セルフホスティングが可能です。ベンダーロックインのリスクがありません。

Row Level Security:SupabaseはPostgreSQLのRLS(行レベルセキュリティ)を使って、データベースレベルでアクセス制御を行います。

移行の容易さ:標準的なPostgreSQLなので、他のホスティングサービスへの移行が容易です。

プロジェクトのセットアップ

Supabaseプロジェクトの作成

supabase.comでアカウントを作成し、新しいプロジェクトを作成します。プロジェクト作成時にデータベースのパスワードを設定します(後から必要になるので控えておきましょう)。

Next.jsプロジェクトとの連携

# Next.jsプロジェクトの作成
npx create-next-app@latest my-supabase-app --typescript --tailwind --app
cd my-supabase-app

# Supabaseクライアントのインストール
npm install @supabase/supabase-js @supabase/ssr
// .env.local
NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIs...
// lib/supabase/client.ts(ブラウザ用)
import { createBrowserClient } from '@supabase/ssr';

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );
}
// lib/supabase/server.ts(サーバー用)
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';

export async function createClient() {
  const cookieStore = await cookies();

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll();
        },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value, options }) => {
            cookieStore.set(name, value, options);
          });
        },
      },
    }
  );
}

TypeScript型定義の自動生成

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

# ログイン
npx supabase login

# 型定義の生成
npx supabase gen types typescript --project-id your-project-id > lib/database.types.ts
// 型安全なクライアントの作成
import { createBrowserClient } from '@supabase/ssr';
import type { Database } from '@/lib/database.types';

export function createClient() {
  return createBrowserClient<Database>(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );
}

認証(Authentication)の実装

SupabaseはGoTrueベースの認証機能を提供し、メール/パスワード、OAuth(Google、GitHub等)、マジックリンクなど多様な認証方式に対応しています。

メール/パスワード認証

// サインアップ
async function signUp(email: string, password: string) {
  const supabase = createClient();
  const { data, error } = await supabase.auth.signUp({
    email,
    password,
    options: {
      data: {
        display_name: '田中太郎',  // メタデータ
      },
    },
  });

  if (error) {
    console.error('サインアップエラー:', error.message);
    return null;
  }

  return data.user;
}

// ログイン
async function signIn(email: string, password: string) {
  const supabase = createClient();
  const { data, error } = await supabase.auth.signInWithPassword({
    email,
    password,
  });

  if (error) {
    console.error('ログインエラー:', error.message);
    return null;
  }

  return data.session;
}

// ログアウト
async function signOut() {
  const supabase = createClient();
  await supabase.auth.signOut();
}

// セッション情報の取得
async function getSession() {
  const supabase = createClient();
  const { data: { session } } = await supabase.auth.getSession();
  return session;
}

OAuth認証(Google)

// Google OAuthログイン
async function signInWithGoogle() {
  const supabase = createClient();
  const { data, error } = await supabase.auth.signInWithOAuth({
    provider: 'google',
    options: {
      redirectTo: `${window.location.origin}/auth/callback`,
      queryParams: {
        access_type: 'offline',
        prompt: 'consent',
      },
    },
  });
}
// app/auth/callback/route.ts(OAuth コールバック)
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const code = searchParams.get('code');

  if (code) {
    const supabase = await createClient();
    await supabase.auth.exchangeCodeForSession(code);
  }

  return NextResponse.redirect(new URL('/dashboard', request.url));
}

認証状態に基づくミドルウェア

// middleware.ts
import { createServerClient } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server';

export async function middleware(request: NextRequest) {
  let supabaseResponse = NextResponse.next({ request });

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return request.cookies.getAll();
        },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value, options }) => {
            request.cookies.set(name, value);
            supabaseResponse.cookies.set(name, value, options);
          });
        },
      },
    }
  );

  const { data: { user } } = await supabase.auth.getUser();

  // 未認証ユーザーをログインページにリダイレクト
  if (!user && request.nextUrl.pathname.startsWith('/dashboard')) {
    const url = request.nextUrl.clone();
    url.pathname = '/login';
    return NextResponse.redirect(url);
  }

  return supabaseResponse;
}

export const config = {
  matcher: ['/dashboard/:path*', '/settings/:path*'],
};

データベース操作(CRUD)

SupabaseのJavaScriptクライアントは、PostgRESTを通じてPostgreSQLに対して直感的なCRUD操作を提供します。

テーブルの作成(SQL Editor)

-- Supabaseダッシュボードの SQL Editor で実行
CREATE TABLE posts (
  id          UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  title       VARCHAR(255) NOT NULL,
  content     TEXT,
  slug        VARCHAR(255) UNIQUE NOT NULL,
  published   BOOLEAN DEFAULT false,
  author_id   UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
  created_at  TIMESTAMPTZ DEFAULT NOW(),
  updated_at  TIMESTAMPTZ DEFAULT NOW()
);

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

-- ポリシー:公開済み記事は誰でも読める
CREATE POLICY "Published posts are visible to everyone"
  ON posts FOR SELECT
  USING (published = true);

-- ポリシー:自分の記事のみ作成可能
CREATE POLICY "Users can create 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);

クライアントからのCRUD操作

import { createClient } from '@/lib/supabase/client';

const supabase = createClient();

// CREATE:記事の作成
async function createPost(title: string, content: string, slug: string) {
  const { data, error } = await supabase
    .from('posts')
    .insert({
      title,
      content,
      slug,
      author_id: (await supabase.auth.getUser()).data.user?.id,
    })
    .select()
    .single();

  return { data, error };
}

// READ:記事一覧の取得
async function getPosts() {
  const { data, error } = await supabase
    .from('posts')
    .select('id, title, slug, created_at')
    .eq('published', true)
    .order('created_at', { ascending: false })
    .limit(10);

  return { data, error };
}

// READ:リレーション付きで取得
async function getPostWithAuthor(slug: string) {
  const { data, error } = await supabase
    .from('posts')
    .select(`
      *,
      author:profiles!author_id (
        display_name,
        avatar_url
      )
    `)
    .eq('slug', slug)
    .single();

  return { data, error };
}

// UPDATE:記事の更新
async function updatePost(id: string, updates: { title?: string; content?: string }) {
  const { data, error } = await supabase
    .from('posts')
    .update({
      ...updates,
      updated_at: new Date().toISOString(),
    })
    .eq('id', id)
    .select()
    .single();

  return { data, error };
}

// DELETE:記事の削除
async function deletePost(id: string) {
  const { error } = await supabase
    .from('posts')
    .delete()
    .eq('id', id);

  return { error };
}

リアルタイム機能の実装

Supabaseのリアルタイム機能を使えば、データベースの変更をリアルタイムでクライアントに通知できます。

リアルタイムサブスクリプション

// Reactコンポーネントでのリアルタイムサブスクリプション
'use client';

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

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

  useEffect(() => {
    // 初期データの取得
    async function fetchPosts() {
      const { data } = await supabase
        .from('posts')
        .select('*')
        .eq('published', true)
        .order('created_at', { ascending: false });

      if (data) setPosts(data);
    }
    fetchPosts();

    // リアルタイムサブスクリプションの設定
    const channel = supabase
      .channel('posts-changes')
      .on(
        'postgres_changes',
        {
          event: '*',        // INSERT, UPDATE, DELETE すべて
          schema: 'public',
          table: 'posts',
          filter: 'published=eq.true',
        },
        (payload) => {
          console.log('Change received:', payload);

          if (payload.eventType === 'INSERT') {
            setPosts((prev) => [payload.new, ...prev]);
          } else if (payload.eventType === 'UPDATE') {
            setPosts((prev) =>
              prev.map((post) =>
                post.id === payload.new.id ? payload.new : post
              )
            );
          } else if (payload.eventType === 'DELETE') {
            setPosts((prev) =>
              prev.filter((post) => post.id !== payload.old.id)
            );
          }
        }
      )
      .subscribe();

    // クリーンアップ
    return () => {
      supabase.removeChannel(channel);
    };
  }, []);

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

リアルタイムを有効にする設定

-- Supabase ダッシュボードの SQL Editor で実行
-- テーブルのリアルタイムを有効化
ALTER PUBLICATION supabase_realtime ADD TABLE posts;

ストレージ(ファイルアップロード)

バケットの作成とアップロード

-- ストレージバケットの作成(SQL Editor)
INSERT INTO storage.buckets (id, name, public)
VALUES ('avatars', 'avatars', true);

-- アップロードのポリシー
CREATE POLICY "Users can upload their own avatar"
  ON storage.objects FOR INSERT
  WITH CHECK (
    bucket_id = 'avatars'
    AND auth.uid()::text = (storage.foldername(name))[1]
  );

-- 公開アクセスのポリシー
CREATE POLICY "Anyone can view avatars"
  ON storage.objects FOR SELECT
  USING (bucket_id = 'avatars');
// ファイルのアップロード
async function uploadAvatar(userId: string, file: File) {
  const supabase = createClient();
  const fileExt = file.name.split('.').pop();
  const filePath = `${userId}/avatar.${fileExt}`;

  const { data, error } = await supabase.storage
    .from('avatars')
    .upload(filePath, file, {
      cacheControl: '3600',
      upsert: true,  // 既存ファイルを上書き
    });

  if (error) {
    console.error('アップロードエラー:', error.message);
    return null;
  }

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

  return publicUrl;
}

Edge Functionsの活用

Edge FunctionsはDeno ベースのサーバーレス関数で、WebhookやAPIエンドポイントとして利用できます。

# Edge Functionの作成
npx supabase functions new send-welcome-email
// supabase/functions/send-welcome-email/index.ts
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';

serve(async (req) => {
  const { record } = await req.json();

  // Supabase管理者クライアント
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
  );

  // ウェルカムメールの送信(外部メールサービスを利用)
  const response = await fetch('https://api.resend.com/emails', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${Deno.env.get('RESEND_API_KEY')}`,
    },
    body: JSON.stringify({
      from: 'noreply@example.com',
      to: record.email,
      subject: 'ようこそ!アカウント登録が完了しました',
      html: `

${record.raw_user_meta_data?.display_name}さん、登録ありがとうございます。

`, }), }); return new Response(JSON.stringify({ success: true }), { headers: { 'Content-Type': 'application/json' }, }); });
# デプロイ
npx supabase functions deploy send-welcome-email

# ローカルテスト
npx supabase functions serve send-welcome-email

まとめ

本記事では、Supabaseの基本から実践的な活用方法まで解説しました。

  • データベース:PostgreSQLベースで、SQLの知識がそのまま活かせる
  • 認証:メール/パスワード、OAuth、マジックリンクに対応
  • RLS:行レベルセキュリティでデータベースレベルのアクセス制御
  • リアルタイム:データベースの変更をWebSocketでクライアントに通知
  • ストレージ:S3互換のファイルストレージ
  • Edge Functions:Denoベースのサーバーレス関数
  • 型安全:CLIで型定義を自動生成し、TypeScriptと組み合わせて安全に開発

Supabaseは、PostgreSQLの強力な機能とBaaSの手軽さを両立したプラットフォームです。Firebaseのようなスピード感で開発しつつ、SQLの柔軟性とオープンソースの安心感を手に入れることができます。無料プランで十分に試せるので、まずは小さなプロジェクトから始めてみてください。

#Supabase#BaaS#PostgreSQL
共有:
無料メルマガ

週1回、最新の技術記事をお届け

AI・クラウド・開発の最新記事を毎週月曜にメールでお届けします。登録は無料、いつでも解除できます。

プライバシーポリシーに基づき管理します

起業準備に役立つ情報、もっとありますよ。

まずは話だけ聞いてもらう