Use Cases

Claude CodeでSupabase統合開発を加速する

Claude Codeを使ったSupabaseの統合開発ガイド。認証、データベース、リアルタイム、ストレージの実装パターンを具体的なコード例付きで解説。

Supabase とは

Supabaseはオープンソースのバックエンドプラットフォームです。PostgreSQLデータベース、認証、リアルタイムサブスクリプション、ストレージを統合的に提供します。Claude Codeを使えば、Supabaseの各機能を効率的に実装できます。

セットアップ

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

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

export const supabase = createClient<Database>(
  supabaseUrl,
  supabaseAnonKey
);

// サーバーサイド用(Service Role Key)
export function createServerClient() {
  return createClient<Database>(
    supabaseUrl,
    process.env.SUPABASE_SERVICE_ROLE_KEY!,
    { auth: { persistSession: false } }
  );
}

認証

// メール/パスワード認証
async function signUp(email: string, password: string) {
  const { data, error } = await supabase.auth.signUp({
    email,
    password,
    options: {
      data: { display_name: email.split("@")[0] },
    },
  });

  if (error) throw error;
  return data;
}

async function signIn(email: string, password: string) {
  const { data, error } = await supabase.auth.signInWithPassword({
    email,
    password,
  });

  if (error) throw error;
  return data;
}

// OAuth認証
async function signInWithGitHub() {
  const { data, error } = await supabase.auth.signInWithOAuth({
    provider: "github",
    options: {
      redirectTo: `${window.location.origin}/auth/callback`,
    },
  });

  if (error) throw error;
  return data;
}

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

データベースCRUD

// 型安全なクエリ
async function getPosts(params: {
  page?: number;
  category?: string;
}) {
  const { page = 1, category } = params;
  const perPage = 20;

  let query = supabase
    .from("posts")
    .select(`
      id,
      title,
      content,
      published_at,
      author:users(id, name, avatar),
      categories(id, name, slug)
    `, { count: "exact" })
    .eq("published", true)
    .order("published_at", { ascending: false })
    .range((page - 1) * perPage, page * perPage - 1);

  if (category) {
    query = query.contains("categories", [{ slug: category }]);
  }

  const { data, error, count } = await query;

  if (error) throw error;
  return { posts: data, total: count };
}

// 挿入
async function createPost(post: {
  title: string;
  content: string;
}) {
  const { data: { user } } = await supabase.auth.getUser();
  if (!user) throw new Error("Not authenticated");

  const { data, error } = await supabase
    .from("posts")
    .insert({
      title: post.title,
      content: post.content,
      author_id: user.id,
    })
    .select()
    .single();

  if (error) throw error;
  return data;
}

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

import { useEffect, useState } from "react";

function useRealtimeComments(postId: string) {
  const [comments, setComments] = useState<Comment[]>([]);

  useEffect(() => {
    // 初期データ取得
    supabase
      .from("comments")
      .select("*, author:users(name, avatar)")
      .eq("post_id", postId)
      .order("created_at")
      .then(({ data }) => setComments(data || []));

    // リアルタイム購読
    const channel = supabase
      .channel(`comments:${postId}`)
      .on(
        "postgres_changes",
        {
          event: "INSERT",
          schema: "public",
          table: "comments",
          filter: `post_id=eq.${postId}`,
        },
        async (payload) => {
          // 作者情報を取得
          const { data } = await supabase
            .from("users")
            .select("name, avatar")
            .eq("id", payload.new.author_id)
            .single();

          setComments((prev) => [
            ...prev,
            { ...payload.new, author: data },
          ]);
        }
      )
      .subscribe();

    return () => {
      supabase.removeChannel(channel);
    };
  }, [postId]);

  return comments;
}

ストレージ

async function uploadAvatar(file: File, userId: string) {
  const ext = file.name.split(".").pop();
  const path = `avatars/${userId}.${ext}`;

  const { error: uploadError } = await supabase.storage
    .from("profiles")
    .upload(path, file, {
      upsert: true,
      contentType: file.type,
    });

  if (uploadError) throw uploadError;

  const { data } = supabase.storage
    .from("profiles")
    .getPublicUrl(path);

  // プロフィールを更新
  await supabase
    .from("users")
    .update({ avatar: data.publicUrl })
    .eq("id", userId);

  return data.publicUrl;
}

Row Level Security

-- RLSポリシーの設定
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- 公開済み記事は誰でも閲覧可
CREATE POLICY "Public posts are viewable by everyone"
ON posts FOR SELECT
USING (published = true);

-- 自分の記事のみ編集可
CREATE POLICY "Users can update own posts"
ON posts FOR UPDATE
USING (auth.uid() = author_id);

-- 認証済みユーザーのみ作成可
CREATE POLICY "Authenticated users can create posts"
ON posts FOR INSERT
WITH CHECK (auth.uid() = author_id);

Claude Codeでの活用

Supabase開発をClaude Codeに依頼する例です。認証についてはOAuth認証の実装、データベース設計はPrisma ORM完全ガイドも参照してください。

Supabaseでブログアプリのバックエンドを構築して。
- 認証: メール/パスワード + GitHub OAuth
- テーブル: users, posts, comments, categories
- RLSポリシーの設定
- リアルタイムコメント機能
- 画像アップロード機能

Supabaseの詳細はSupabase公式ドキュメントを参照してください。Claude Codeの使い方は公式ドキュメントで確認できます。

まとめ

Supabaseは認証からデータベース、リアルタイムまでを統合的に提供するプラットフォームです。Claude Codeを使えば、各機能の実装を効率的に進め、フルスタックアプリケーションを素早く構築できます。

#Claude Code #Supabase #BaaS #PostgreSQL #リアルタイム