supabaseのローカル環境を整える

# アップデートしました！
2025年3月に改めて、再びローカル環境構築をやってみました。

大きく変わったところは無かった印象ですが、コマンドごとに挙動などを再度確認しながら進めてみましたので、ぜひご覧ください！

https://qiita.com/eno49conan/items/2169072ed6e4aa1d0164

# 概要
[先日公開した記事](https://qiita.com/eno49conan/items/6d3e98df2ac82613c3b3)の中で、supabaseを利用していました。

https://supabase.com/

[issue](https://github.com/supabase/supabase/issues)や[pull request](https://github.com/supabase/supabase/pulls)の数を見ていても、
まだまだ機能の追加・改善がされるサービスと思っています！

何か作成中、動作確認の度にクラウドからデータ取得をしていると、データ受取量が増え、コストとなる場合もあります。
**ローカルに構築したsupabase環境からデータ取得することで、
コストセーブに加えて、開発しやすさも向上する**と思いました。
ということで今回はローカル環境を構築してみました。


チュートリアルを見ていると大きく2種類の流れがあります。
 - ローカル環境に1から構築
 - 既にクラウド上に作成済のデータベースの情報をローカルに反映

今回私の場合は、後者の状況です。
操作がシンプルで、公式ドキュメントだけで完了するのは助かります。

## セットアップ
supabaseのローカル環境構築に必要なコンテナのダウンロードまでは、以下のページに記載されています。
既にローカルにあるReactのプロジェクトなどがある前提で話をします。

https://supabase.com/docs/guides/cli/getting-started

```shell
npx npm install supabase --save-dev
```
初期化を実施します。
```shell
npx supabase init
```
`supabase`というフォルダが作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/b8bce01a-887a-fff8-002b-8d831ab04b89.png)

続けて以下を実行します。
```shell
npx supabase start
```
コンテナがいくつか作成された後に、いくつか情報が出力されます。
`http://127.0.0.1:XXXX`といった情報がいくつかあるので、ローカル動作確認用と確認できますね。
セキュリティの都合上、載せていませんが、この画像の続きとして、`JWT secret`・`anon key`・`service_role key`が記載されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/a1febb57-b93e-9b34-16f8-92593e529afb.png)
今回の作業では、以下が必要になります。
- API URL
- DB URL
- anon key

## リモートのテーブル定義をローカルにpull
ここからは以下のページを参考にして進めます。

https://supabase.com/docs/guides/cli/managing-environments

以下を実行して、TOKENを取得します。ただし今回は認証までは実施していないので、使う場面はありません。
```shell
npx supabase login
```
続けて、特定のプロジェクトと結びつけるため、以下を実行します。
ダッシュボードのURLの、projects以降の文字列を使います。
`https://supabase.com/dashboard/project/XXXXXXXXXXXXXXXXXXXX`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/44567b73-6d6d-4f04-059d-b02843d2f8d7.png)

```shell
npx supabase link --project-ref $PROJECT_ID
```
実行すると、パスワードを聞かれます。
このパスワードは、DB作成時に設定した値を入力します。入力して問題なければ、以下の通り出力されます。
```shell
Enter your database password (or leave blank to skip):
Finished supabase link.
```

これでリモートからローカルにDBの情報を反映する準備ができました。
続けて以下を実行し、リモートの状態をローカルで取得します。この段階では、まだローカルにテーブルは作成されません。
```shell
npx supabase db pull
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/6ea19ae4-fe0f-ede1-3a2c-fa3a4b8f29a2.png)
history更新するかどうか聞かれます。
今回私の場合は、履歴を残す必要もなさそうだったので、`no`にしました。
ターミナルに出力された通りですが、supabaseのディレクトリを見ると、sqlファイルが追加されていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/ab159c47-1365-be7c-0525-92381f5e1c8f.png)
作成されたファイルについて、一部ですが、以下のようにDDLなどが記載されています。
（私はmoviesテーブルを作成していたので、以下の情報がありました。）
```sql
CREATE TABLE IF NOT EXISTS "public"."movies" (
    "id" bigint NOT NULL,
    "name" "text",
    "description" "text"
);
```

## テーブル作成とデータ投入
ローカルにテーブル定義やデータを反映する作業は、以下ページの`Add sample data`セクションの情報を確認します。

https://supabase.com/docs/guides/cli/local-development#add-sample-data

既にテーブル定義はpullしたので、初期データ投入して準備完了です。
`supabase/seed.sql`というファイルに、データを投入するためのSQLを記述します。
<details><summary>seed.sqlの例</summary>

```sql:seed.sql
INSERT INTO "public"."movies" ("id", "name", "description") 
VALUES 
  (1, 'The Empire Strikes Back', 'After the Rebels are brutally overpowered by the Empire on the ice planet Hoth, Luke Skywalker begins Jedi training with Yoda.'),
  (2, 'Return of the Jedi', 'After a daring mission to rescue Han Solo from Jabba the Hutt, the Rebels dispatch to Endor to destroy the second Death Star.'),
  (12, 'name', 'description'),
  (14, 'name', 'description_2023/11/26 9:45:10'),
  (16, 'name', 'description_2023/11/26 9:45:30'),
  (17, 'name', 'description_2023/11/26 9:45:33'),
  (18, 'name', 'description_2023/11/26 9:46:27'),
  (22, 'name', 'description_2023/11/26 9:47:6'), 
  (24, 'name', 'description_2023/11/26 9:47:39'),
  (27, 'name', 'description_2023/11/26 9:47:52');
```

</details>

私は、supabaseのdashboardからcsvファイルをデータをexportしていたので、そのデータをSQLに変更して、記述しました！
こういう作業でも、chatGPTは役立ちますね。

> Reset your database (apply current migrations), and populate with seed data

マイグレーションを適用し、データ投入まで実施します。
```shell
npx supabase db reset
```
コンテナが再起動し、
resetが完了、というメッセージが表示されました。
```text
5c712df4ab14: Pull complete
Digest: sha256:ad820ed920ba8dbf7759982b67958902943e3c887979c5ea04fd3b4b8bb20225
Status: Downloaded newer image for public.ecr.aws/supabase/gotrue:v2.109.0
Applying migration 20231203070610_remote_schema.sql...
Seeding data supabase\seed.sql...
Restarting containers...
Finished supabase db reset on branch main.
```

#### ローカルのデータを確認してみる
`npx supabase start`実行後、URLなどが一覧表示された出力がありました。その中の以下のURLをブラウザで開くと、クラウド上で確認していたdashboardと同じUIで投入したデータを確認できます。
- Studio URL: http://127.0.0.1:54323

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/43da4b42-272d-6453-0faa-07769f003bb6.png)

特に話をしていませんでしたが、ローカルにFastAPIのアプリケーションを用意してあります。
（冒頭に記載した別記事にアプリケーション）
特定のエンドポイントにアクセスすると、supabase上（クラウド）からデータ取得します。
**取得元がクラウドからローカルに切り替わったか、確認してみます。**

supabaseからデータを取得するためには、URLとKEYが必要です。
`npx supabase start`で出力された情報からURLとKEYを設定します。
```text:.env.local
SUPABASE_URL=http://127.0.0.1:54321
SUPABASE_KEY=eyJhXXXXXXXXXXXXXXXXXXXXXXH6IkpXVCJ9.eyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_I0
```

ローカルに対してリクエストを送信していることが確認できました。
（クラウド上のテーブルから取得した場合、
赤線部分が`https://XXXXXXXXXXXXXXXXXXXX.supabase.co/`）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/e46255da-1fb4-2771-1ce0-abcacc89ea47.png)


## リクエスト数（データ転送量）は本当に増えていない？
ローカル開発でデータ取得を行った際に、クラウドへのリクエストがないのか、
一応確かめてみました。
ダッシュボードのメニュー`Reports`から`API`を選択すると、以下のグラフがあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/b1d7396d-9680-ebce-82d1-5533439427fe.png)

- AM9時ごろ：クラウド側のテーブルに、データ取得を6回実施（赤線・矢印）
- PM6時ごろ：ローカル側のテーブルに、データ取得を6回実施
- PM10時ごろ：状況確認のため、ダッシュボードにアクセス（紫矢印）
**ローカルで操作を行った、PM6時ごろのリクエスト情報がグラフにありません。**
ローカルで完結しており、クラウドへのリクエストは増えていないことが確認できました。
---
ローカル開発とは関係ないですが、先ほどの画像右上に四角と矢印がセットになったアイコンがあります。
クリックすると、`Logs Explorer`が表示されます。
SQLを組むことで、取得する情報をカスタマイズできるようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/a7d8ab14-5acc-1f4e-1232-df46abe9dc92.png)

表示されていたSQLは、以下の処理を行っています。
- edge_logs テーブルからログデータを読み込み
- timestamp を1時間単位で丸めている (timestamp_trunc)
- 1時間ごとに集計(GROUP BY)し、順に並べ替え(ORDER BY)

<details><summary>SQL全量</summary>

```sql
        select
          cast(timestamp_trunc(t.timestamp, hour) as datetime) as timestamp,
          count(t.id) as count
        FROM edge_logs t
          cross join unnest(metadata) as m
          cross join unnest(m.response) as response
          cross join unnest(m.request) as request
          cross join unnest(request.headers) as headers
          
        GROUP BY
          timestamp
        ORDER BY
          timestamp ASC
```
</details>

## 片付けと再開
以下を実行し、コンテナを削除します。
```shell
npx supabase stop
```
volumeに設定した情報が出力され、ローカル環境用のコンテナは削除されました。
```text
Postgres database saved to volume: supabase_db_clerk-nextjs-app-quickstart
Postgres config saved to volume: supabase_config_clerk-nextjs-app-quickstart                                                
・・・
Stopped supabase local development setup.
```

<details><summary>再開時のトラブルシュート</summary>

皆さんの環境でも発生するか分かりませんでしたが、私の環境では、ローカル環境再開時にエラーが発生したので、その内容と解決手段を記載しておきます！

再度コンテナを立てるためにコマンドを実行
```
npx supabase start
```
私の場合は、以下エラー発生しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/b46c1d6a-4d99-42b8-fedf-8656e1a5deda.png)
以下のフォルダを削除
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/345414/7f9e6b52-8c6b-7904-0bf7-58efe3158acf.png)

</details>

## まとめ
Dockerのみあれば構築ができて、最初のセットアップもコマンド数本で完了するので、サクッと構築できる印象を受けました。

ローカル開発に限らず、今後のアップデート動向を眺めながら、定期的にキャッチアップをしたいと思います。