取り組みの背景 — Supabase × Antigravity という選択肢
フルスタックアプリ開発において、バックエンドの構築は大きなハードルの一つです。データベース設計、認証システム、リアルタイム通信、ストレージ管理——これらを一から実装するには膨大な時間がかかります。
Supabase は「Firebase のオープンソース代替」として急速に支持を広げている BaaS(Backend as a Service)です。PostgreSQL ベースのデータベース、組み込みの認証機能、リアルタイムサブスクリプション、エッジファンクションなど、モダンなアプリ開発に必要な機能を一通り提供しています。
ここに Antigravity IDE のエージェント機能を掛け合わせると、スキーマ設計からフロントエンド実装までを AI が支援してくれるため、開発効率が飛躍的に向上します。ここではAntigravity と Supabase を使ってリアルタイム対応のタスク管理アプリを構築する流れを、ステップバイステップで解説します。
Supabase の基本構成を理解する
Supabase は以下の主要コンポーネントで構成されています。
- PostgreSQL データベース: フルマネージドの PostgreSQL インスタンス。Row Level Security(RLS)によるきめ細かいアクセス制御が可能
- Auth: メール・パスワード、OAuth(Google, GitHub 等)、マジックリンクに対応した認証基盤
- Realtime: PostgreSQL の変更をリアルタイムで WebSocket 経由で配信するエンジン
- Storage: S3 互換のファイルストレージ。画像やドキュメントのアップロードに対応
- Edge Functions: Deno ベースのサーバーレス関数。Stripe Webhook などの処理に利用可能
Firebase との最大の違いは、標準 SQL が使える点です。NoSQL に不慣れな開発者でも直感的にデータモデリングができます。また、ベンダーロックインが少なく、将来的にセルフホスト(自前サーバー運用)への移行も容易です。
Antigravity プロジェクトのセットアップ
まず Antigravity で新しい Next.js プロジェクトを作成し、Supabase クライアントをインストールします。Antigravity のエージェントにプロンプトで依頼すると、プロジェクト構成の提案から実行まで自動で進めてくれます。
# Antigravity のエージェントに以下のプロンプトを送信
# 「Next.js 15 + TypeScript + Supabase のプロジェクトを作成して」
# エージェントが実行するコマンド例:
npx create-next-app@latest task-app --typescript --tailwind --app
cd task-app
# Supabase クライアントのインストール
npm install @supabase/supabase-js @supabase/ssr次に、Supabase ダッシュボードでプロジェクトを作成し、API キーを .env.local に設定します。
# .env.local
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
SUPABASE_SERVICE_ROLE_KEY=YOUR_SERVICE_ROLE_KEYSupabase クライアントの初期化は、サーバーコンポーネントとクライアントコンポーネントで異なるインスタンスを使い分けるのがベストプラクティスです。
// src/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!
);
}// src/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)
);
},
},
}
);
}Antigravity のエージェントに「Supabase の SSR クライアントを設定して」と伝えれば、この構成を自動で生成してくれます。Auth.js v5 を使った認証システムの構築ガイドも参考になりますが、Supabase Auth を使う場合は Supabase 組み込みの認証を活用する方がシンプルです。
データベース設計と Row Level Security
Supabase ダッシュボードの SQL エディタ、または Antigravity のエージェント経由でテーブルを作成します。今回はタスク管理アプリのスキーマを設計しましょう。
-- Supabase SQL Editor または Antigravity エージェント経由で実行
-- タスクテーブル
create table public.tasks (
id uuid default gen_random_uuid() primary key,
user_id uuid references auth.users(id) on delete cascade not null,
title text not null,
description text,
status text default 'todo' check (status in ('todo', 'in_progress', 'done')),
priority integer default 0 check (priority between 0 and 3),
due_date timestamptz,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
-- RLS(Row Level Security)を有効化
alter table public.tasks enable row level security;
-- ポリシー: ユーザーは自分のタスクのみ操作可能
create policy "Users can view own tasks"
on public.tasks for select
using (auth.uid() = user_id);
create policy "Users can insert own tasks"
on public.tasks for insert
with check (auth.uid() = user_id);
create policy "Users can update own tasks"
on public.tasks for update
using (auth.uid() = user_id);
create policy "Users can delete own tasks"
on public.tasks for delete
using (auth.uid() = user_id);
-- updated_at を自動更新するトリガー
create or replace function update_updated_at()
returns trigger as $$
begin
new.updated_at = now();
return new;
end;
$$ language plpgsql;
create trigger tasks_updated_at
before update on public.tasks
for each row execute function update_updated_at();RLS は Supabase の最も強力な機能の一つです。サーバー側でアクセス制御ロジックを書かなくても、データベースレベルで認可ルールを強制できます。Antigravity のエージェントに「このテーブルに RLS ポリシーを追加して」と依頼すれば、テーブル構造を読み取って適切なポリシーを提案してくれます。
Supabase の型を自動生成する
TypeScript プロジェクトでは、Supabase CLI で型定義を自動生成しておくと、エージェントがより正確なコードを書いてくれます。
# Supabase CLI のインストール
npm install -D supabase
# 型定義の生成
npx supabase gen types typescript --project-id your-project-id > src/types/database.ts生成された型を使うことで、クエリの戻り値に自動的に型が付きます。
// src/types/database.ts から自動生成される型の例
export type Task = Database["public"]["Tables"]["tasks"]["Row"];
export type InsertTask = Database["public"]["Tables"]["tasks"]["Insert"];
export type UpdateTask = Database["public"]["Tables"]["tasks"]["Update"];リアルタイム同期の実装
Supabase Realtime は、PostgreSQL の変更を WebSocket 経由でクライアントに即座に配信する仕組みです。チームでのタスク共有や、複数デバイス間の同期に活用できます。
// src/hooks/useRealtimeTasks.ts
"use client";
import { useEffect, useState } from "react";
import { createClient } from "@/lib/supabase/client";
import type { Task } from "@/types/database";
export function useRealtimeTasks(userId: string) {
const [tasks, setTasks] = useState<Task[]>([]);
const supabase = createClient();
useEffect(() => {
// 初回データ取得
const fetchTasks = async () => {
const { data, error } = await supabase
.from("tasks")
.select("*")
.eq("user_id", userId)
.order("created_at", { ascending: false });
if (!error && data) {
setTasks(data);
}
};
fetchTasks();
// リアルタイムサブスクリプション
const channel = supabase
.channel("tasks-realtime")
.on(
"postgres_changes",
{
event: "*", // INSERT, UPDATE, DELETE を全て監視
schema: "public",
table: "tasks",
filter: `user_id=eq.${userId}`,
},
(payload) => {
switch (payload.eventType) {
case "INSERT":
setTasks((prev) => [payload.new as Task, ...prev]);
break;
case "UPDATE":
setTasks((prev) =>
prev.map((t) =>
t.id === (payload.new as Task).id
? (payload.new as Task)
: t
)
);
break;
case "DELETE":
setTasks((prev) =>
prev.filter((t) => t.id !== payload.old.id)
);
break;
}
}
)
.subscribe();
// クリーンアップ
return () => {
supabase.removeChannel(channel);
};
}, [userId, supabase]);
return tasks;
}Antigravity のエージェントに「タスクテーブルのリアルタイム同期フックを作成して」と指示すると、上記のようなカスタムフックを生成してくれます。ポイントは、filter パラメータで自分のタスクのみ受信対象にしている点です。これにより、不要なイベントの受信を防ぎ、パフォーマンスを最適化できます。
なお、Supabase ダッシュボードで対象テーブルの Realtime を有効にするのを忘れないでください(Database → Tables → tasks → Realtime を ON)。
認証フローの実装
Supabase Auth を使えば、メール認証と OAuth の両方を簡単に実装できます。
// src/app/auth/login/page.tsx
"use client";
import { useState } from "react";
import { createClient } from "@/lib/supabase/client";
import { useRouter } from "next/navigation";
export default function LoginPage() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const supabase = createClient();
const router = useRouter();
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError(null);
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
setError(error.message);
} else {
router.push("/dashboard");
router.refresh();
}
setLoading(false);
};
const handleGoogleLogin = async () => {
await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
});
};
return (
<form onSubmit={handleLogin}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="メールアドレス"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="パスワード"
required
/>
{error && <p className="text-red-500">{error}</p>}
<button type="submit" disabled={loading}>
{loading ? "ログイン中..." : "ログイン"}
</button>
<button type="button" onClick={handleGoogleLogin}>
Google でログイン
</button>
</form>
);
}OAuth コールバックの処理も忘れずに実装しておきましょう。
// src/app/auth/callback/route.ts
import { createClient } from "@/lib/supabase/server";
import { NextResponse } from "next/server";
export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url);
const code = searchParams.get("code");
if (code) {
const supabase = await createClient();
const { error } = await supabase.auth.exchangeCodeForSession(code);
if (!error) {
return NextResponse.redirect(`${origin}/dashboard`);
}
}
return NextResponse.redirect(`${origin}/auth/error`);
}よくあるエラーと対処法
Antigravity で Supabase プロジェクトを開発する際によくあるトラブルと、その解決策をまとめます。
RLS ポリシーが原因でデータが取得できない
Supabase はデフォルトで RLS が有効な場合、ポリシーが設定されていないとデータを一切返しません。Antigravity のエージェントに「tasks テーブルのデータが取得できない」と伝えると、RLS ポリシーの確認と修正を提案してくれます。
-- デバッグ: RLS を一時的に無効化して確認
-- ⚠️ 本番環境では絶対に無効化しないこと
alter table public.tasks disable row level security;リアルタイムイベントが届かない
Supabase ダッシュボードで対象テーブルの Realtime が ON になっているか確認してください。また、RLS ポリシーが Realtime にも適用される点に注意が必要です。
環境変数が読み込めない
NEXT_PUBLIC_ プレフィックスがないとクライアント側で参照できません。SUPABASE_SERVICE_ROLE_KEY はサーバー側専用のため、プレフィックスなしが正しい設定です。
まとめ
Antigravity × Supabase の組み合わせは、フルスタックアプリ開発の新しいスタンダードになりつつあります。PostgreSQL ベースの堅牢なデータベース、組み込みの認証とリアルタイム同期、そして Antigravity エージェントの AI 支援を組み合わせることで、従来なら数日かかっていた実装を数時間で完了できます。
本記事のポイントを振り返ると、Supabase クライアントのサーバー/クライアント分離、RLS によるセキュアなデータアクセス制御、そしてリアルタイムサブスクリプションの実装が重要な柱です。
さらに本格的なプロダクション向けのセキュリティ対策やバリデーション設計を知りたい方は、Antigravity エージェントでセキュアな API バックエンドを構築するで詳しく解説しています。また、課金機能の統合を検討している方には、Antigravity × SaaS × Stripe フルスタックガイドもおすすめです。