「スキーマ変更で障害が起きたら...」から解放!Supabase CLIで安心チーム開発

「スキーマ変更で障害が起きたら...」から解放!Supabase CLIで安心チーム開発

投稿日: 2025年06月21日

Tips
要約
  • チーム開発において Supabase をローカルで構築する手順を解説し、開発環境を整える重要性を強調している。
  • ローカル Supabase を使用することで、データベースの初期化やスキーマ変更を行う際のリスクを軽減できる。
  • この記事では、Supabase CLI を使ったローカル環境の構築から、Prisma との接続や認証機能の設定まで詳述している。
音声で記事を再生
0:00

はじめに

この記事では、チーム開発で Supabase を利用する際、開発用としてローカル環境に Supabase を構築して利用する方法について解説します。

アプリ開発の初期には、テーブルレコードの追加や削除だけでなく、データベースの初期化やスキーマの変更といった破壊的な操作を繰り返し行なう場面が多々あります。しかし、これをチーム共有の Supabase で行なってしまうと、他のメンバーの開発作業に思わぬ影響を与えてしまうことがあります。こうしたリスクを避けつつ、安心して開発を進める方法として、自分の PC 上に Supabase を構築してしまう、という手があります(無料アカウントでのプロジェクト数の制限も気にせずに済みます)。

公式が提供している Supabase CLI を使えば、比較的かんたんにローカル環境に Supabase を立ち上げることができます。この資料では、その手順をチュートリアル形式で説明していきます。

なお、ここでは Windows 環境を想定し、WSL2 (Windows Subsystem for Linux)と Docker Desktop がすでにインストールされ有効化されていること、また Next.js を開発フレームワークとして使用していることを前提とします。

WSL2 と Docker Desktop のインストール方法については、丁寧な解説記事がウェブに多数あるので、そちらを参照してください。

プロジェクトの作成とパッケージのインストール

ここでは例として、プロジェクト名を「fuga」として進めていきます。まず、以下の手順で Next.js プロジェクトを作成します。

PS> npx create-next-app@latest fuga --typescript
PS> cd fuga
PS> npm i

次に、追加で以下のライブラリをインストールします。

PS> npm i -D supabase prisma tsx
PS> npm i @supabase/supabase-js

このなかの supabase が、今回の主役である Supabase CLI となります(CLI は Command Line Interface の略です)。このツールを使って、ローカル環境に開発用の Supabase を構築していきます。

念のため、この段階で「開発モードの起動」と「ビルド」が正常に動くことを確認しておいてください。

PS> npm run dev
PS> npm run build

SupabaseCLIの設定

まず、バージョンを確認しておきます。

PS> npx supabase --version
2.26.9

つづいて、次のコマンドでローカル Supabase を構築するための初期化を実行します。途中、設定に関する質問が表示されますが、すべて「N(デフォルト)」で問題ありません。

PS> npx supabase init
Generate VS Code settings for Deno? [y/N]
Generate IntelliJ Settings for Deno? [y/N] 
Finished supabase init.

処理が完了するとプロジェクトのルートに supabase というフォルダが作成されます。このフォルダ内の config.toml という設定ファイルを開いて、次のように project_idfuga から fuga-supabase に変更しておきます。

project_id = "fuga-supabase"

これは、アプリ本体(fuga)の Docker コンテナと混同しないための工夫です(今回はアプリ本体を Docker で動かすわけではありませんが…)。

ローカル Supabase の起動

次のコマンドで、ローカル Supabase を起動します。

PS> npx supabase start

初回は構築処理も含まれるため、かなり大きめの Docker イメージをダウンロードと、その展開が必要なためそこそこの時間がかかります。

もし Docker や WSL2 のインストールや設定が正しくできていない場合は、ここでエラーになります(例として、次のようなメッセージが表示されます)。

failed to inspect service: error during connect: Get "http://%2F%2F.%2Fpipe%2FdockerDesktopLinuxEngine/v1.50/containers/supabase_db_fuga-supabase/json": open //./pipe/dockerDesktopLinuxEngine: The system cannot find the file specified.
Docker Desktop is a prerequisite for local development. Follow the official docs to install: https://docs.docker.com/desktop

ローカル Supabase 起動に成功すると、最終的に以下のようなメッセージが表示されます。

Started supabase local development setup.

         API URL: http://127.0.0.1:54321
     GraphQL URL: http://127.0.0.1:54321/graphql/v1
  S3 Storage URL: http://127.0.0.1:54321/storage/v1/s3
          DB URL: postgresql://postgres:postgres@127.0.0.1:54322/postgres
       http://127.0.0.1:54323
    Inbucket URL: http://127.0.0.1:54324
      JWT secret: super-secret-jwt-token-with-at-least-32-characters-long
        anon key: eyJhbGc....
service_role key: eyJhbGc....
   S3 Access Key: 625729....
   S3 Secret Key: 850181....
       S3 Region: local

Studio URL として表示される http://127.0.0.1:54323 にブラウザでアクセスすると、クラウド版の Supabase で見慣れた管理画面が開きます(ただし、クラウド版と完全に同じというわけではなく、一部の機能に違いがあります)。

ローカル Supabase の停止と再起動

ローカル Supabase を「停止」するには、以下のコマンドを実行します。CPU やメモリの使用量がそこそこ大きいので、開発作業が終わったら必ず明示的に停止してください。

PS> npx supabase stop
Stopped supabase local development setup.
Local data are backed up to docker volume. Use docker to show them: docker volume ls --filter label=com.supabase.cli.project=fuga-supabase

VSCode を閉じても、ローカル Supbase が自動で停止はされるわけではない点に注意してください。コマンドを使って明示的に指定が必要です。

再びローカル Supabase を起動するために npx supabase start を実行すれば OK なのですが、Windows 環境では次のようなエラーが出て失敗することがあります(僕の環境では再現率100%です🫠)。

PS> npx supabase start
failed to create docker container: Error response from daemon: Conflict. The container name "/supabase_vector_fuga-supabase" is already in use by container "e83ed8c92100c6ea865b87477e420d347c3479c74af034a9178f69835e2d7e7a". You have to remove (or rename) that container to be able to reuse that name.

これは npx supabase stop終了処理が不完全な場合に発生し、本来削除されるはずのコンテナが残ってしまっていることが原因です。状況を確認するには、docker ps -a というコマンドを使います。

PS> docker ps -a
CONTAINER ID   IMAGE                                          COMMAND                   CREATED              STATUS                          PORTS     NAMES
df9695df9ea9   public.ecr.aws/supabase/vector:0.28.1-alpine   "sh -c 'cat <<'EOF' …"   About a minute ago   Exited (0) About a minute ago             supabase_vector_fuga-supabase

残っているコンテナは、以下のコマンドで削除します(name=supabase_vector_fuga-supabase の部分はプロジェクトに合わせてください)。

docker rm $(docker ps -a -q --filter "name=supabase_vector_fuga-supabase")

もう一度状況を確認し、以下のようにコンテナが残っていなければ OK です。

PS> docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

この状態になれば、npx supabase start でローカル Supabase を問題なく起動できます。ハマりがちなポイントなので覚えておいてください。

以降、ローカル Supabase が起動している状態で作業してください。

Prismaの設定

ここからは、ローカル Supabase と Prisma を接続するための設定を進めていきます。

まず、ローカル Supabase への接続情報を .env ファイルに環境変数として書き込みます。設定する値は、ローカル Supabase を起動したときに表示される DB URL(例: postgresql://postgres:postgres@127.0.0.1:54322/postgres)です。

DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres
DIRECT_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres

次に、Prisma の初期化コマンドを実行します。

PS> npx prisma init --datasource-provider postgresql

これにより、プロジェクトのルートに prismaというフォルダが作成され、そのなかに schema.prisma というファイルが作成されます。このファイルを次のように編集します。

// 1. npx prisma db push
// 2. npx prisma generate

generator client {
  provider = "prisma-client-js"
  // output   = "../src/generated/prisma" // 要コメントアウト
}

datasource db {
  provider  = "postgresql"
  url       = env("DATABASE_URL")
  directUrl = env("DIRECT_URL")
}

model Post {
  id         String   @id @default(uuid())
  title      String
  content    String
  createdAt  DateTime @default(now()) @map("created_at")
  @@map("posts")
}

次のコマンドで、スキーマに基づき、ローカル Supabase にテーブルを作成されるはずです。

PS> npx prisma db push
PS> npx prisma generate

問題なく処理できているかを確認してみます。http://127.0.0.1:54323 にアクセスしてください。次のようにテーブルが作成できていればOKです。

「スキーマ変更で障害起きたら...」から解放!Supabase CLIで安心チーム開発|ShiftBブログ

データベースに初期データを投入

データベースに初期データを投入するスクリプトを作成します。prisma フォルダのなかに seed.ts という名前でファイルを作成して、以下の内容を記述します。

// npx prisma db seed

import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();
const main = async () => {
  // Postテーブルから既存の全レコードを削除
  await prisma.post.deleteMany();

  // Postレコードの挿入
  const p1 = await prisma.post.create({
    data: {
      title: "投稿1",
      content: "投稿1の本文。<br/>投稿1の本文。投稿1の本文。",
    },
  });

  const p2 = await prisma.post.create({
    data: {
      title: "投稿2",
      content: "投稿2の本文。<br/>投稿2の本文。投稿2の本文。",
    },
  });

  console.log(JSON.stringify(p1, null, 2));
  console.log(JSON.stringify(p2, null, 2));
};

main()
  .catch((e) => {
    console.error(e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });

このプログラムを実行するために、package.json に次の内容(prismaのセクション)を追加してください。

// ......
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "prisma": {
    "seed": "tsx prisma/seed.ts"
  },
  "dependencies": {
// ......

次のコマンドで seed.ts を実行して、初期データを投入します。

PS> npx prisma db seed

ブラウザから http://127.0.0.1:54323 にアクセスして、次のようにテーブルにレコードが追加されていればOKです。

「スキーマ変更で障害起きたら...」から解放!Supabase CLIで安心チーム開発|ShiftBブログ

Prisma を使って DB レコードを取得

ローカル Supabase と Prisma を接続し、Next.js のバックエンドでデータが正しく取得できるかを確認してみます。

まずは ./src/lib/prisma.ts を新規作成し、以下のコードを記述してください(クラウドの Supabase を使うときと同じ設定でOKです。ローカルに対応させるための特別な設定は含んでいません)。

import { PrismaClient } from "@prisma/client";

// データベース接続用のインスタンスを作成する関数
const prismaClientSingleton = () => {
  return new PrismaClient();
};

// グローバルスコープに prismaGlobal という変数が存在することをTypeScriptに伝える型定義
declare const globalThis: {
  prismaGlobal: ReturnType<typeof prismaClientSingleton>;
} & typeof global;

// 既存の prismaGlobal があればそれを使用し、なければ新しくインスタンスを作成
const prisma = globalThis.prismaGlobal ?? prismaClientSingleton();

// 作成した prisma インスタンスを他から利用できるようにエクスポート
export default prisma;

// 開発環境の場合のみ、作成したインスタンスをグローバルスコープに保存し、
// ホットリロード時の再利用を可能にする
if (process.env.NODE_ENV !== "production") globalThis.prismaGlobal = prisma;

つづいて、./src/app/api/posts/route.ts を新規作成し、次のコードを記述してください。

import prisma from "@/lib/prisma";
import { NextResponse } from "next/server";
import { Post } from "@prisma/client";

export const dynamic = "force-dynamic";

export const GET = async () => {
  try {
    const posts: Post[] = await prisma.post.findMany({
      orderBy: {
        createdAt: "desc",
      },
    });
    return NextResponse.json(posts);
  } catch (error) {
    console.error(error);
    return NextResponse.json(
      { error: "投稿記事の一覧の取得に失敗しました" },
      { status: 500 }
    );
  }
};

動作を確認してみます。次のようになればOKです。

「スキーマ変更で障害起きたら...」から解放!Supabase CLIで安心チーム開発|ShiftBブログ

Supabase の認証機能を利用

ローカル Supabase の認証機能が利用できることを確認していきます。

まずは、.env に以下のように環境変数を設定します。設定する値は、ローカル Supabase を起動したときに表示される API URLanon key の値となります。

DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres
DIRECT_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres

NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJxxxxxxxxxxxx

./src/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!
);

./src/app/signup/page.tsx を新規作成し、以下のコードを記述します。

"use client";

import { supabase } from "@/lib/supabase";
import { useState } from "react";

export default function Page() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();

    const { error } = await supabase.auth.signUp({
      email,
      password,
      options: {
        emailRedirectTo: "http://localhost:3000/",
      },
    });
    if (error) {
      alert("登録に失敗しました。");
    } else {
      setEmail("");
      setPassword("");
      alert("登録に成功しました。");
    }
  };

  return (
    <div className="flex justify-center pt-12">
      <form onSubmit={handleSubmit} className="space-y-4 w-full max-w-[400px]">
        <div>
          <label
            htmlFor="email"
            className="block mb-2 text-sm font-medium text-gray-900"
          >
            メールアドレス
          </label>
          <input
            type="email"
            name="email"
            id="email"
            className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
            placeholder="name@example.com"
            autoComplete="email"
            required
            onChange={(e) => setEmail(e.target.value)}
            value={email}
          />
        </div>
        <div>
          <label
            htmlFor="password"
            className="block mb-2 text-sm font-medium text-gray-900"
          >
            パスワード
          </label>
          <input
            type="password"
            name="password"
            id="password"
            placeholder="••••••••"
            autoComplete="off"
            className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
            required
            onChange={(e) => setPassword(e.target.value)}
            value={password}
          />
        </div>

        <div>
          <button
            type="submit"
            className="w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center"
          >
            登録
          </button>
        </div>
      </form>
    </div>
  );
}

実行します。

「スキーマ変更で障害起きたら...」から解放!Supabase CLIで安心チーム開発|ShiftBブログ

確認します。

「スキーマ変更で障害起きたら...」から解放!Supabase CLIで安心チーム開発|ShiftBブログ

ローカル環境での Supabase では、ユーザ確認用のメール認証は最初から「オフ」になっています。supabase/config.tomlenable_confirmations = false がそれにあたります。

おわりに

お疲れ様でした😆
開発環境(ローカル Supabase)と本番環境(クラウド Supabase)は、環境変数を切り替えるだけで使い分けられます。

これでもう「誰かがスキーマ変えてテーブル消えた!😱」なんて大惨事とは無縁です。ローカル環境では失敗を気にせず、大胆にトライしてテーブルを何回でも葬り去ってください。最高の状態だけをクラウドに😶‍🌫️。

シェア!

Threads
Loading...
記事一覧に戻る
Threads
0