あるWebサービスの開発メモ・Supabase

- [あるWebサービスの開発メモ・目次ページに戻る](https://qiita.com/ishi32/items/152a1ecf7eeee7a8fb57)
- [AWSDevDay-Day3](https://2mtazgaizx.ap-northeast-1.awsapprunner.com/ja-JP)
- [Self-Hosting with Docker](https://supabase.com/docs/guides/self-hosting/docker)
- [Supabaseをローカルで立ち上げてReactから使ってみる](https://kohsuk.tech/2021/8/24/)

## Supabase とは

Supabase は自ら Firebase Alternative だと言っています (2022年8月現在)

## Firebase とは

Firebase をご存じない方には説明が必要かもしれません。Firebase はモバイルとWebアプリケーションの開発プラットフォームで 2012 年にサービスを開始しています。2014年に Google に買収され現在に至ります

私たちが　Firebase でよく利用していた機能には以下のものがあります

- Authentication
よくある ID とパスワードによる認証をはじめ、メールを用いた認証、Google認証や iCloud認証などのプロバイダ認証をカジュアルに実現できるインターフェースを提供してくれます。 売り言葉は「アプリの認証システム全体をコード１０行以内で設定できます」です。この機能だけでも Firebase を採用するのに十分すぎるメリットがあります。また Firebase Authentication には匿名認証というとてもユニークな認証方式があって、ユーザはとりあえずユーザ登録をしなくても匿名でアプリの利用を開始することができ、継続して利用する場合には後でユーザ登録してもらう　というユースケースに対応することができます。これは匿名でのユーザ管理を行うことが可能で、ユーザ登録された場合は、このユーザ情報との紐付けを行うことで実現されています

- Cloud Firestore
NoSQL (キー、バリュー) 型のデータストアです。Firestore にはちょっと変わった利用形態がありまして、通常、クラウド上のデータベースやデータストアを利用する場合には、データをキャッシュするまでネットワークを経由してクラウド上の各リソースとリアルタイムでやり取りする必要があります。Firestore はクライアント側でデータをキャッシュするだけでなく、設定を入れればクライアント側でもデータを永続化できます。この機能を利用すれば、オフラインの時はクライアント側のデータベースを参照、更新し、オンラインになったタイミングで更新データをクラウド上のリソースに同期する運用が可能になります。通信環境が安定しない場合にもクラウドアプリを継続して利用することが可能です
- Cloud Storage
クラウド上のファイルストレージです。
- Hosting
React で開発したアプリをホスティングできます。

## なぜ Supabase を採用したのか

- フロントエンドサーバとバックエンドが aws 東京リージョンのネットワーク内にある (2022/10/31 追記)
Vercel CDN はグローバルネットワークなのですが、オリジンサーバは aws 東京リージョン を選択しています。Supabase も aws 東京リージョン を選択していますので、認証、データベースアクセス、ファイルアクセスなどの通信が全て aws の同一リーション内で行われるため、アプリの応答性能にとっては極めて有利な環境を作ることができます

- Authentication
Supabase の認証機能は Firebase の認証機能とほぼイコールなのですが、Firebase の匿名認証にあたる機能はありません。匿名認証は素晴らしいのですが、ユーザの同意を得ずに使うことは今のご時世では難しいように思え、仕組みをご説明してもとてもわかりにくそうなので、スタンダードなメール、パスワード認証のみ提供することにしました
- PostgreSQL
Supabase のデータベースは PostgreSQL です。この老舗のデータベースを利用できることが Supabase を利用したい最大のモチベーションになっています。その他にもデータベースに関してはいくつか Supabase が有利な仕様があります。
  - アクセス回数の制限がない
Firebase ユーザはデータベースに対するアクセス回数によって課金されています。無料プランの場合、１日のデータベース更新回数は２万回までなので、その回数を超えると、米国太平洋時間の０時までアクセスできなくなります。(そのためお試し期間中に従量制の有償プランに変更することになります) アプリのバグでとんでもない回数をアクセスすると、クラウド破産ということになります。Firebaseユーザにとっては信じられないことなのですが、Supabase はアクセス回数に対する課金がありません。ストレージとネットワークのデータサイズのみに課金されます
  - 権限設定がわかりやすい
Firebase Cloud Firestore の権限設定は、私たちにとってはとてもわかりにくいものでした。PostgreSQL の RLS (Row Level Security: 行単位のセキュリティポリシー) はとてもわかりやすく、ミスの可能性が低くなるのが大きな魅力です。
  - やっぱり SQL が好き
時代は NoSQL なんでしょうけど、やっぱり慣れているデータベースの方が開発スピードは速くなります。
- Storage
ストレージに関しては甲乙つかず、だと思っています。
- Hosting
新サービスの Hosting は Vercel を採用しますので Hosting 機能は不要です。

## 認証

### 接続

supabase との接続は supabase-js パッケージの createClient モジュールにて行われます。認証に関する処理はすべて createClient モジュールから取得した SupabaseClient オブジェクトを用いて supabase と連携します。

./utils/supabase.ts で createClient モジュールに supabaseプロジェクトのプロジェクトURL と API Key を渡して SupabaseClient オブジェクトを取得しています。

#### supabaseプロジェクトのプロジェクトURL と API Key

(./.env.local)

```
NEXT_PUBLIC_SUPABASE_URL=https://xxxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_API_KEY=eyJhbGxxxxxxJ8Li48
```

(./utils/supabase.ts)

```
import { createClient } from "@supabase/supabase-js";

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_API_KEY!;

export const supabase = createClient(supabaseUrl, supabaseAnonKey);
```

### ユーザ登録

[supabase.auth.signUp](https://www.supabase.jp/docs/reference/javascript/auth-signup) で行います。

### ログイン

[supabase.auth.signIn](https://www.supabase.jp/docs/reference/javascript/auth-signin) で行います。

### ログアウト

[supabase.auth.signOut](https://www.supabase.jp/docs/reference/javascript/auth-signout) で行います。

### ユーザ登録済みユーザの情報

auth.users テーブルに格納されています。

- select id, email, email_confirmed_at from auth.users;

| id                                   | email                  | email_confirmed_at            |
| ------------------------------------ | ---------------------- | ----------------------------- |
| 1a679275-2447-... | ia...@mail.sirokuro.site | 2022-07-25 11:11:36.935121+00 |
| df971572-8be0-... | as...@gmail.com   | 2022-08-13 10:41:49.529416+00 |
| d35b807f-ab47-... | yt...@gmail.com  | 2022-08-14 11:16:34.129986+00 |
| 0b0393b1-cdb5-... | is...@gmail.com     | 2022-08-15 07:43:41.56201+00  |

### パスワードリセット

- [パスワードリセットの実装](https://zenn.dev/n_o_n_a_m_e/books/de49cce3d044c8/viewer/c8542c)

### メールアドレス変更

- [update()](https://supabase.com/docs/reference/javascript/auth-update)

## データベース

Supabase のデータベースは PostgreSQL です。Supabase 内では Postgres と呼んでいます。本サービスでは react-query を用いてアクセスしています

### データベース接続

認証と同じ SupabaseClient オブジェクトを用いて supabase と連携します

### select 例

- [Fetch data: select()](https://www.supabase.jp/docs/reference/javascript/select)

### insert 例

- [Create data: insert()](https://www.supabase.jp/docs/reference/javascript/insert)

### update 例

- [Modify data: update()](https://www.supabase.jp/docs/reference/javascript/update)

### delete 例

- [Delete data: delete()](https://www.supabase.jp/docs/reference/javascript/delete)

### Postgres function call 例

- [Postgres functions: rpc()](https://www.supabase.jp/docs/reference/javascript/rpc)

### Filter 例

すぐ忘れてしまうのでまとめておきます

- [.eq()](https://www.supabase.jp/docs/reference/javascript/eq)
- [.neq()](https://www.supabase.jp/docs/reference/javascript/neq)
- [.or()](https://www.supabase.jp/docs/reference/javascript/or)
- [.gt()](https://www.supabase.jp/docs/reference/javascript/gt)
- [.gte()](https://www.supabase.jp/docs/reference/javascript/gte)
- [.lt()](https://www.supabase.jp/docs/reference/javascript/lt)
- [.lte()](https://www.supabase.jp/docs/reference/javascript/lte)
- [.not()](https://www.supabase.jp/docs/reference/javascript/not)
- [.like()](https://www.supabase.jp/docs/reference/javascript/like)

### Subscription

Subscription を実装することにより、他のユーザが投稿やコメントを追加、変更、削除した際、即座に自分のブラウザにその内容が反映されるようになります。Subscription はテーブル全体にかける方式と、行単位にかける方式があるようなので、なるべく行単位にかけてい戦略をとっていこうと思っています



## Storage

### Crate a new bucket

Supabase GUI からつくっています

- Storage > [New bucket]
- Name of bucket を指定する
- Public bucket のスイッチは Off がデフォルトなのでそのままにしておく
  - プライベート (非公開) バケットとして作成する
- [Create bucket]

### ファイルアップロード

- [supabase 公式より](https://www.supabase.jp/docs/guides/storage)

```
const avatarFile = event.target.files[0]
const { data, error } = await supabase.storage
  .from('avatars')
  .upload('public/avatar1.png', avatarFile)
```

### ファイルダウンロード (supabase 公式より)

```
const { data, error } = await supabase.storage.from('avatars').download('public/avatar1.png')
```

## プロジェクトが tier の上限を超えた場合 (2022/11/3 追記)

本日はじめて Supabase GUI にこんなメッセージが表示されました

![スクリーンショット 2022-11-03 13.05.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/539175/0913a070-f6cf-e9fb-836f-e65ae44e3f91.png)

この赤字のメッセージはリンクになっていて、クリックすると何が上限を超過してしまったのかがわかります。

![スクリーンショット 2022-11-03 13.11.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/539175/340e89bb-1dce-13db-ab69-3803bccf941d.png)

ファイル転送の帯域を超過してしまったようです。まだほとんどユーザ様がアクセスしていない状況なのに「なぜ？」とは思いますが、とりあえずはプランを上げるしかないようです。

クレジットカード情報を設定して [Confirm payment] をクリックすると Your project has been updated! となって赤字のメッセージは表示されなくなりました

うーん、どうしてまだこんなにアクセスがスカスカの状態でファイル転送の帯域を 2GB も使ってしまったのでしょうか。tier を Pro にしたので当分大丈夫そうなんですが、人気サイトになった時を想定して今から対策を考えておこうと思います

### イメージのリサイズ

今は何も考えないでファイルをアップロードしています。ストレージを確認したところ最大で 6MB を超えるイメージファイルがあったのでリサイズを検討した方が良さそうです。理想はクライアント側でアップロード前にリサイズです

対応方針は browser-image-compression によるリサイズです

- [Browser Image Compressionで画像をアップロード前に圧縮する](https://bashalog.c-brains.jp/21/03/08-190000.php)
- [browser-image-compression](https://www.npmjs.com/package/browser-image-compression)

### Image Component を使う

いろいろな記事で「Image Component 使えよ」と言われ続けてきましたが、img タグを使っています。こちらの記事を見かけて「これならいけるかも」と思っているところです

- [Next.jsのImageが使いにくいという人へ](https://zenn.dev/nbr41to/articles/365e8105efa448)

## Authentication 上の User を削除したい

supabase GUI から削除オペすると ``` Failed to delete user: Failed to delete user ``` というわかりにくいメッセージが表示されて削除できません

Storage 上にある、この削除対象のユーザがアップロードしたイメージファイルをすべて削除すると　Authentication で　User を削除できるようになります

今回つくったサービスでは以下のバケットにユーザファイルを配置しています。

- avators
- posts
- comments
- reply

すべてのバケットでは User の Authentication で付与されている UUID でサブフォルダを作成していたので、ユーザごとにファイルを削除することが容易に実施できました

## Supabase が一時停止したらどうなるか

有償プラン (Pro) は停止されませんが、無償プラン (free-tier) は７日間なにもしないとプロジェクトが一時停止されてしまいます。メールで警告してくれますが、一時停止されるとどうなるのか試してみようと思い、放置してみることにしました。

### わかったこと

- All projects の画面で [unpause] (だったかな？) をクリックしないとプロジェクトは再開されない
- プロジェクトURL と API KEY が変わってしまう
- Authentication > Templates の設定が消えてしまう
- アプリから認証がまったく使えなくなってしまう

最後の問題は当然解決策があるんだと思いますが、短時間では解決に至らず諦めてしまいました。

つまり今後利用する可能性があるプロジェクトは、絶対に一時停止してはならないということなんです。

## 気をつけていること

Supabase GUI (クライアントブラウザアプリ) が時々とても重いというか、動作が遅くなってしまい、最終的に動かなくなってしまうことがありました

一例ですが TableEditor の EditTable でテーブルのデータ項目や属性を追加、変更し、[Save]ボタンを押した後のアプリの動きが、極端に遅かったり、１０分以上応答がないということが発生していました

(Create new table のスピナーが表示されたまま無反応状態になる)

![スクリーンショット 2022-08-13 17.57.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/539175/6cd00b27-6554-7c1a-1461-a03d137a559e.png)

(Updating table も無反応に)

![スクリーンショット 2022-08-13 17.57.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/539175/ca0c1577-3aa4-bd7f-e4b6-e460afa35144.png)

この現象はブラウザを再起動すると解消しますので、Supabase アプリに問題があるのか、Supabase 以外に問題があって、Supabase が被害を受けているのかはわかりませんが、とりあえず Supabase アプリだけを別のブラウザで動かすようにしたところ、問題なく利用できることがわかりました。私は Firefox ブラウザを使っていなかったので、Supabase アプリのみを Firefox ブラウザでつかうようにして様子見したところ、いい感じで利用できています