SupabaseのDB環境をローカル作成して開発(2025年3月版)

# 久しぶりにSupabaseのローカル開発
1年半ほど前にsupabaseのローカル環境構築について、
以下の記事を公開しました。

https://qiita.com/eno49conan/items/4596c1983ee5e3f9e324

しかし1年半が経過すれば、変わっていることも多いはずです。
破壊的変更があったり、サービス自体が既に終了していたり。

今どうなっているのか、キャッチアップ目的も含めて再度取り組んでみました。
ちなみに、直近で控えているSupabaseの大きな変更の1つは、以下です。
（`Pinned`してあるDiscussion、なのでそれくらいの）

https://github.com/orgs/supabase/discussions/29260

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/1973aaca-e85e-41ff-a7e5-5153df31b0c6.png)

**API Keyに関する変更**です。
APIキーについて呼び方・キー名の構成が以下の通り変更されます。
- `anon` Key →`publishable` key
- `service_role` Key →　`secret` key

**今年（2025年）の5月1日から次のフェーズに入ります。**

> We will start sending you monthly reminders to migrate off legacy API keys and start using the new keys.
New projects will be created with only new API keys.
Projects restored from 1st May 2025 will no longer be restored with the legacy API keys.

これ以降に作成したプロジェクトには、新しいAPIキーが導入されるようです。

:::note warn
**現在のAPIキーは、今年(2025年)の9月一杯で利用できなくなります**
> Legacy API keys will be deleted and removed from the Docs / Dashboard.
You have to migrate to use the new API keys by this point or your app will break.

アプリケーションが機能しなくなるので、それまでに対応してください、とのことでした。

:::

## 作業に至ったきっかけ

現在FlutterでAndroidアプリ作成を進めています。データが必要になってきたので、データベースの準備をしていました。他にも選択肢はあるようですが、
サービス自体は使い慣れているSupabaseがいいなと思い、今回も利用しています。

また、UIの見た目を都度確認しながらデータ取得をしていると、データ通信が発生し、無料枠を超える可能性もあります。
ローカルでDB構築できれば、気兼ねなく動作確認できる！ということでやりました。

## 作業の前提
環境面・技術面、とちらも少ない方だと思いますが、ご確認ください。
- 環境面の前提
    - Dockerがインストール済
    - Supabaseのプロジェクトが作成済（デプロイまで行うため）
- 技術面の前提
    - テーブル作成・データ投入など、基本的なSQLは知っている
    - `npx`,`npm`コマンドの基本を理解をしている

## ローカル開発環境の準備
Dockerコンテナ作成まで行います。

#### 公式ドキュメント

ドキュメントの`Local Development`セクションの以下項目を参考にしています。
- Getting started
- Declarative database schemas
- Seeding your database

https://supabase.com/docs/guides/local-development

#### 作業用ディレクトリ作成
既に何か作業中のフォルダがあるとします。
ここにフォルダを追加します。（フォルダ名：任意）
作成後、対象フォルダへ移動します。

まず、以下コマンドを実行すると、`package.json`が作成されます。
```shell
npm install supabase --save-dev
```
続いて、以下コマンドでsupabase関連の構築を進めます。
```shell
npx supabase init
npx supabase start
```

#### エラーが発生
以下コマンドでエラーが発生しました。
```
npx supabase start
```
エラーが発生したのは、`supabase_vector_xxx`というコンテナでした。
（`xxx`：作成したフォルダ名）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/17f4e135-4bdb-4048-b769-84e53bf77dcb.png)

> 2025-02-04 08:11:14 2025-02-03T23:11:14.987467Z ERROR source{component_kind="source" component_id=docker_host component_type=docker_logs component_name=docker_host}: vector::sources::docker_logs: Listing currently running containers failed. error=error trying to connect: tcp connect error: Network unreachable (os error 101)

#####  Githubのissue確認

issueが起票されていました。

https://github.com/supabase/supabase/issues/32570

記事公開の1週間ほど前にも、上述したエラーが発生している、というコメントありました。

https://github.com/supabase/cli/issues/2505#issuecomment-2676336536

##### 解決方法
`作成フォルダ名/supabase`フォルダに、`config.toml`があります。
`analytics`セクションの先頭にあるフィールド：`enabled`をfalseに更新します。

analytics機能はまだ把握できていませんが、false設定でもDB関連の機能利用には特に影響なさそうです。

```toml
[analytics]
enabled = false # ここをfalseに！
port = 54327
# Configure one of the supported backends: `postgres`, `bigquery`.
backend = "postgres"

# Experimental features may be deprecated any time
[experimental]
# Configures Postgres storage engine to use OrioleDB (S3)
orioledb_version = ""
# Configures S3 bucket URL, eg. <bucket_name>.s3-<region>.amazonaws.com
s3_host = "env(S3_HOST)"

# 以下略
```

#### コンテナ作成完了
上記の対応を行えば、成功するはずです。
（ここで失敗した方はすみませんが、ご自身で解決をお願いいたします。）

以下の通り、ターミナルにURL情報が表示されます。
```text
         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
      Studio URL: 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: eiJhbGciOuJIUzI1AiIsInR5cEJ6IknDHWJ5.eyJpc3MiOiJzdGBhYmFzZS1kZW1vIiwicm9sZSI3ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
service_role key: eyJhbGciOiJKOzI1NiIsInR5cCI6IkpXKAQ5.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp8fsn3W0YpN81IU
   S3 Access Key: 625729a08b95bf1b7ff351a663f3a23d
   S3 Secret Key: 850181e4652cc023b7a98c58ae0d2d34bd487ee0cc1257aed6eda37307425907
       S3 Region: local
```

以下URLにアクセスすると、ブラウザが立ち上がり、実際のポータルサイトと同じような画面が表示されます。

> Studio URL: http://127.0.0.1:54323

これで準備は完了しましたので、この後テーブル作成に進みます。

## ローカル開発実施

作業のゴールは、
**ローカルで作業したスキーマをリモート（実際）のプロジェクトに反映することです。**

#### 公式ドキュメント

https://supabase.com/docs/guides/local-development/overview

#### テーブル作成とデータ投入

1つ前のセクションで記載した、`http://127.0.0.1:54323`にアクセスします。
左側のメニューから、`SQL Editor`を選択します。

このエディタ上でSQLを実行して、データを投入します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/7616ec1d-4824-426c-9d64-bfecbb0f3fa6.png)


今回は、以下2つのテーブルを作成しました。
- 店舗情報テーブル
- 都道府県一覧テーブル（店舗情報テーブルで使用）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/b5fd521c-fe02-4575-9e83-9b50255ab03b.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/31ed171a-ab0d-45ac-8446-6e374944c9db.png)

#### テーブル定義とデータ管理
実行したSQLについて、今後ローカル開発をする際に再利用できる方が後々楽です。
**Seeding**という概念です。（以降この単語を使います。）

`config.toml`ファイルと同階層の`seed.sql`ファイルを使って、データ投入のためのSQLを残しておきましょう。

ちなみにファイル名は、`seed.sql`の必要はありません。`config.toml`ファイルに、Seedingに関するセクションが存在します。
どのファイルをSeedingに利用するか、カスタマイズ可能です。

以下は、公式ドキュメントからの引用です。

https://supabase.com/docs/guides/local-development/seeding-your-database

```toml
[db.seed]
enabled = true
sql_paths = ['./countries.sql', './cities.sql']
```

この対応により、`npx supabase start`実行時に、
記載内容に沿ってSeedingを行ってくれます。

:::note info
**Docker Volumeへの保存**
`npx supabase stop`でコンテナ停止時、ターミナルに以下内容が表示されます。(`xxxxx`：作成フォルダ名)

> 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=xxxxx

Docker Volumeにデータを保存してくれます。

:::

:::note info

**環境の再構築**
> `npx supabase start`実行時に、データ投入を行ってくれます。

一度環境を構築している場合、
Seedingの内容を適用するためには、`npx supabase db reset`で環境のリセットが必要です。

コマンドを実行すると、以下の通りターミナルに表示されます。「再構築」という単語が近いと思います。
**スキーマの初期化・テーブル再作成・Seedingを実施**してくれます。
慣れてくると、ありがたいことを実感できる仕組み（コマンド）です。

```shell
$ npx supabase db reset
Resetting local database...
Recreating database...
Initialising schema...
Seeding globals from roles.sql...
Applying migration 20250301062014_create_stores_table.sql...
NOTICE (42P06): schema "supabase_migrations" already exists, skipping
Seeding data from supabase/seed.sql...
Restarting containers...
Finished supabase db reset on branch main.
```

:::

#### アプリケーションの動作確認
言語によって異なる部分はありますが、
インスタンス定義とテーブル呼び出し部分の実装を記載します。言語はDartですが、言語によって差分が大きく表れる箇所ではないです。

Supabase Clientのインスタンス初期化のパラメータとして、`url`と`anonKey`を設定します。
（先ほど共有した、ターミナルの出力情報を利用）
- **url**：`http://127.0.0.1:54321`(API URLの値)
- **anonKey**：`eiJhbGciOuJIUzI1AiIsInR5cEJ6IknDHWJ5...`(冒頭のみ記載)

**インスタンス作成**
```dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Supabase.initialize(url: AppConstants.supabaseUrl, 
  anonKey: AppConstants.supabaseAnonKey);
}
```
**データ取得の実装**
```
final SupabaseClient supabase = Supabase.instance.client;
final response = await supabase.from('stores').select();
    print(response);
```

URLとKeyが正しく設定できていれば、ローカルで作成したテーブルのデータを取得することができます。

:::note warn
**ホスト番号の設定**
これはFlutter特有（Swiftも該当するかもしれません）の話だと思いますが、
実機で確認する場合、`127.0.0.1`では接続ができません。接続している`WifiのIPアドレス`に置き換えたURLを設定する必要があります。
:::

#### マイグレーションファイル作成
ここから、リモートに反映するための準備に入ります。
```
npx supabase db diff --use-migra -f {create_stores_table}
```
上記コマンドは、既に作成済のマイグレーションファイルと現在のDB状況の差分を元にしてマイグレーションファイルを生成します。
`{create_stores_table}`はマイグレーション内容が判断できる文字列を記載します。

ファイル`20250301062014_create_stores_table.sql`のように、
`yyyyMMddhhmmss_マイグレーション内容`.sqlが、migrations`フォルダに作成されれば成功です。

#### 実際のプロジェクトへのデプロイ

ローカルの作業を通して、テーブル定義に問題がなさそう！と確認できたら、デプロイ作業に進みます。
ここでsupabaseへのログインを行います。
```shell
npx supabase login
```
Enterを押すと、ブラウザが起動します。
表示された認証コードをコピーし、ターミナルに貼り付けます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/756de6d1-4722-4aeb-b381-8654645ffc03.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/513b0530-8181-48f6-af08-fb0ad3be0f4c.png)

続いて、紐づけるプロジェクトをIDを設定します。
```shell
supabase link --project-ref {project_id}
```
ここでローカルとリモートの設定差分が表示されます。DBとは直接関係しない差分も表示されます。
今回はDBに関する更新を行うので、関係ない箇所は気にせず進めます。

以下コマンドで、DBに関する差分は確認できます。
```
npx supabase db diff
```

:::note warn
**予期せぬ設定の反映を防ぐために**
今回は私（開発者）自身の環境なので、予期せぬ変更があっても問題はないです。差分も１つひとつ丁寧には確認していません。

しかし実際のプロジェクトでは、差分が後々影響するかもしれないので、差分が把握できる状態（元に戻せる状態）は用意いただければと思います。
:::

最後にDBのデプロイを実行します。
```
npx supabase db push
```
データベースのパスワードを求められるので、入力してください。
入力後、マイグレーションをリモートに反映してよいか聞かれるので、OKであれば、`Y`を入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/458dc456-e0a5-4f95-9f50-377ecdde3642.png)

これでターミナルを用いた作業は終了です。

#### 結果確認
実際のプロジェクトの状態を、ブラウザで確認してみます。

URLを見ていただくと分かりますが、
ローカル環境ではなく、実際のプロジェクトが表示されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/5f084e44-1b1c-49ac-b0a3-3a6880469a36.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/82d8db63-c2f7-49ba-aa2d-8a2e86172fb4.png)

ローカルで作成したテーブルと同じ情報が表示できていることを確認できました。

これで最初に記載したゴールまで到達できました！

#### コマンド実行を楽にしたい
これは余談になりますが、
毎回`npx supabase start`入力は面倒なので、コマンドをカスタマイズしたくなりました。

`package.json`(作成フォルダと同階層)に`scripts`セクションを追加しました。
```json
{
  "devDependencies": {
    "supabase": "^2.15.8"
  },
  "scripts": {
    "start": "supabase start",
    "stop": "supabase stop",
    "login": "supabase login",
    "link": "supabase link --project-ref",
    "db:diff": "supabase db diff",
    "db:diff:name": "supabase db diff --use-migra -f",
    "db:push": "supabase db push",
    "db:reset": "supabase db reset"
  }
}
```
これで以下のようにコマンド実行ができます。

**startしたい場合**
```
npm run start
```
**マイグレーションを作成したい場合（引数を渡す）**

```
npm run db:diff:name -- create_stores_table
```

## まとめ
久しぶりにローカルでSupabaseのDBを利用して開発をしてみました。
冒頭だと発生したエラー以外、大きく躓くことなく進んでよかったです。

以前記事を書いたとき、Supabaseとセットで利用していたのは、Next.jsだったと思います。
**今回はFlutterで、NextjsのときよりもUIの微調整などでDBにアクセスする回数が多いと感じていたので、ローカルの環境構築をしておいてよかったと、改めて思いました。**

この後、StorageやFunctionの機能も確認ができればと考えています。