セットアップ

プロジェクト作成、API キーの種類、ローカル開発環境、CLI、最小サンプルまで。

1. プロジェクト作成(クラウド)

  1. supabase.com/dashboard でアカウント作成
  2. 「New Project」→ 組織を選んで名前・パスワード・リージョンを入力
  3. リージョンはユーザに近い場所(日本なら ap-northeast-1 / Tokyo)
  4. プロビジョニングが完了するまで 1〜2 分

2. API キーの種類(最重要)

プロジェクト Settings → API で 3 種類のキーが見られる。役割を絶対に間違えない

キー用途場所
Project URLAPI のベース URLクライアント / サーバ両方 OK
anon keyRLS 前提の公開可キークライアント(ブラウザ)に置いてよい
service_role keyRLS をバイパスする管理者キーサーバ専用、絶対漏らさない
事故が起きる典型

service_role key をブラウザに埋め込んでしまう → 全データが読み書き可能に。 フロントは常に anon key。サーバ(Route Handler / Edge Function)でだけ service_role を使う。

3. supabase-js のインストール

npm install @supabase/supabase-js

4. クライアントの初期化

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

export const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
)

環境変数

# .env.local
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOi...

# サーバ専用
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOi...

5. 最小サンプル(テーブル作成 → 取得)

SQL Editor でテーブル作成

create table posts (
  id uuid primary key default gen_random_uuid(),
  title text not null,
  content text,
  created_at timestamptz default now()
);

-- まず RLS を有効化(最初に必ず)
alter table posts enable row level security;

-- 全員 read OK の例
create policy "public can read posts"
  on posts for select
  using (true);

-- サンプル
insert into posts (title, content) values ('hello', 'world');

取得

const { data, error } = await supabase
  .from("posts")
  .select("*")

if (error) console.error(error)
console.log(data) // [{ id, title, content, created_at }]

6. CLI(ローカル開発)

本番にいきなり書き込むより、ローカル Docker でフルスタックを立ち上げる方が安全。

インストール

# macOS
brew install supabase/tap/supabase

# その他
npm install -g supabase

初期化と起動

cd my-app
supabase init       # supabase/ ディレクトリが作られる
supabase start      # Docker で Postgres / Auth / Storage / Studio が起動

# URL / キーが表示される
#   API URL:   http://localhost:54321
#   anon key:  ...
#   service:   ...
#   Studio:    http://localhost:54323

主要コマンド

コマンド用途
supabase startローカルスタック起動
supabase stop停止
supabase status状態確認
supabase db resetマイグレーション全部適用してリセット
supabase migration new <name>マイグレーション作成
supabase db push本番にマイグレーション反映
supabase db pull本番のスキーマをローカルへ
supabase link --project-ref xxxxクラウドプロジェクトと紐付け
supabase functions new my-fnEdge Function 作成
supabase functions deploy my-fnデプロイ
supabase gen types typescript --linked型生成

7. 型を生成する(推奨)

テーブル定義から TypeScript の型を自動生成。これを使わないなら Supabase の旨みを活かしきれない

supabase gen types typescript --linked > lib/database.types.ts
import { createClient } from "@supabase/supabase-js"
import type { Database } from "./database.types"

export const supabase = createClient<Database>(URL, KEY)

// 型推論が効く
const { data } = await supabase.from("posts").select("title")
data?.[0].title  // string で推論される

CI で定期的に再生成するのが運用の定石。

8. Next.js (App Router) との統合

現代では @supabase/ssr を使うのが推奨。

npm install @supabase/ssr @supabase/supabase-js

Server Component で使う

// 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(cookies) {
          cookies.forEach(({ name, value, options }) =>
            cookieStore.set(name, value, options))
        },
      },
    }
  )
}

// page.tsx
const supabase = await createClient()
const { data } = await supabase.from("posts").select("*")

Client Component で使う

// lib/supabase/client.ts
"use client"
import { createBrowserClient } from "@supabase/ssr"

export const supabase = createBrowserClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
)

middleware で session を refresh

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

export async function middleware(req: NextRequest) {
  const res = NextResponse.next()
  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() { return req.cookies.getAll() },
        setAll(cookies) {
          cookies.forEach(({ name, value, options }) =>
            res.cookies.set(name, value, options))
        },
      },
    }
  )
  await supabase.auth.getUser()  // session を更新
  return res
}

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
}

9. 共通エラー

症状原因
Invalid API keyURL / Key が違う / .env が読まれていない
row-level security policy violationRLS でブロックされている。ポリシー作成必要
JWT expiredセッション切れ。再ログイン or refresh
fetch failed (Edge / Cloudflare)Node 互換問題。@supabase/supabase-js v2 の Web fetch を使う
CORSAPI は CORS OK のはず。リバースプロキシ経由なら設定確認

10. ディレクトリ構成例

my-app/
├── supabase/
│   ├── config.toml         # CLI の設定
│   ├── migrations/
│   │   └── 20260101120000_init.sql
│   ├── seed.sql
│   └── functions/
│       └── hello/
│           └── index.ts
├── lib/
│   ├── supabase/
│   │   ├── client.ts
│   │   └── server.ts
│   └── database.types.ts
├── app/                    # Next.js
└── .env.local
      
最初にやるべき 5 つ

1) ローカル CLI で supabase start を動かす / 2) supabase migration new でスキーマを書く / 3) supabase gen types で型を生成 / 4) RLS を有効化してポリシーを 1 個書く / 5) supabase-js でクエリを 1 回成功させる。