Supabase を基本から一歩づつ理解する（Database編）

# はじめに
新しいサービスを生み出していく際に、はじめから重厚長大な作りをすることは少なくなってきているかと思います。はじめはスピーディにサクッと作りたい、でも大きくなってきたらキッチリ作りたい。「サクッと」から「キッチリ」へ切り替える際に、データ構造がガラッと変わってしまうとサービスを育てていく時の課題になるのではないでしょうか。

![do-the-right-thing-1024x458.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/ac85a921-0688-47fe-86fb-2da2be272cc8.png)

（出典）[正しいものを正しくつくる](https://redjourney.jp/do-the-right/)


「サクッと」React をはじめとしたフロントエンドを開発する際に、バックエンドには `Firebase` や `Amplify` などを選択することが多いかと思います。私も両方とも好きでよく使っています。しかしながら基本的には `NoSQL` です。`RDB` で「サクッと」バックエンドを提供してくれるものが `Supabase` です。

https://supabase.com/

`Supabase` は「The Open Source Firebase Alternative / オープンソースの Firebase の代替」と自らを謳っており、慣れ親しんだリレーショナル・データベース（PostgreSQL）でありながら使いやすい BaaS（Backend as a Service）なのです。

本稿では、手を動かしながら `Supabase` を基本から一歩づつ理解していきたいと思います。第一弾は Database 編です。

# Supabase とは
読み方は「スーパーベース」と読みます。
主要な機能は以下です。

- Database　←　イマココ
    - [Row Level Security](https://qiita.com/t-kurasawa/items/754b4fea3d69c958e60d)
- Auth
- Storage
- Realtime
- Edge Functions


## Database

https://supabase.com/docs/guides/database/overview

shops と books というテーブルを作ります。GUI でも SQL でもどちらでも作ることが出来ます。Supabase が便利なのは**テーブルを作ると自動的に RESTful API と GraphQL を生成してくれる**ことです。

``` bash
curl 'http://localhost:8000/rest/v1/books?select=*' \
-H "apikey: SUPABASE_CLIENT_ANON_KEY" \
-H "Authorization: Bearer SUPABASE_CLIENT_ANON_KEY"          
```

```JavaScript
let { data: books, error } = await supabase
  .from('books')
  .select('*')          
```

ダッシュボードサンプルキャプチャ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/6bbaad6c-05f7-4e81-87b9-3740aa22f4d6.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/6ab7f1af-e8b0-45e2-aa49-83947821cd14.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/bb9271d0-d1a1-4036-96be-0f9d83f36b35.png)

SQL Editor で JOIN のクエリもかけます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/ab08081d-f5d9-46c8-a137-c8e0cc868bfc.png)


### 単純なクエリを書きたい

JavaScript（React）から Supabase のクエリを発行してみます。 `@supabase/supabase-js` を使います。

```JavaScript
import { useEffect, useState } from "react";
import { createClient } from "@supabase/supabase-js";

const supabase = createClient("http://localhost:8000", "your-anon-key");

function App() {
  const [shops, setShops] = useState([]);
  const [books, setBooks] = useState([]);
  const [shopsJoinBooks, setShopsJoinBooks] = useState([]);

  useEffect(() => {
    getShops();
    getBooks();
    getShopsJoinBooks();
  }, []);

  async function getShops() {
    const { data } = await supabase.from("shops").select();
    setShops(data);
  }

  async function getBooks() {
    const { data } = await supabase.from("books").select();
    setBooks(data);
  }

  async function getShopsJoinBooks() {
    const { data } = await supabase.from('shops')
    .select(`
      id, name,
      books (
        id, name
      )
    `);
    setShopsJoinBooks(data);
  }

  return (
    <>
      <ul>
        {shops.map((shop) => (
          <li key={shop.id}>{shop.name}</li>
        ))}
      </ul>

      <ul>
        {books.map((book) => (
          <li key={book.id}>{book.name}</li>
        ))}
      </ul>

      <ul>
        {shopsJoinBooks.map((shop) => (
          <li key={shop.id}>
            <h3>{shop.name}</h3>
            <ul>
              {shop.books.map((book) => (
                <li key={book.id}>{book.name}</li>
              ))}
            </ul>
          </li>
        ))}
      </ul>

    </>
  );
}

export default App;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/994c3c5b-3708-4133-9c97-d4f6f139c304.png)

単純なテーブル検索で
`const { data } = await supabase.from("shops").select();`
は
`GET http://localhost:8000/rest/v1/shops?select=*`
となります。返却値は以下となります。

```JSON
[
  {
    "id": "7294ad9a-535a-406e-87c9-2c33aee1e629",
    "created_at": "2025-02-28T07:31:04+00:00",
    "name": "shop1"
  }
]
```

次に、
```JavaScript
const { data } = await supabase
                        .from('shops')
                          .select(`
                             id, name,
                             books (
                               id, name
                             )
                          `);
```
というJOINのクエリは、
`GET http://localhost:8000/rest/v1/shops?select=id,name,books(id,name)`
となります。返却値は以下となります。

```JSON
[
  {
    "id": "7294ad9a-535a-406e-87c9-2c33aee1e629",
    "name": "shop1",
    "books": [
      {
        "id": "6b086feb-bc6b-461b-b5ec-6f09ae94d8e2",
        "name": "book1"
      },
      {
        "id": "41c9bc66-c2cc-4537-b50b-2ebecb6d7673",
        "name": "book2"
      }
    ]
  }
]
```

便利ですね。サクッとアプリが作れそうです


### 複雑なクエリを書きたい
複雑なクエリを書く時には `VIEW` か `RPC` の２つの方法があります。
今回は `VIEW` を作成してみます。

```SQL
CREATE VIEW public.shops_books_view AS
SELECT 
  shops.id AS shop_id, 
  shops.name AS shop_name, 
  books.id AS book_id, 
  books.name AS book_name
FROM 
  shops
JOIN 
  books ON shops.id = books.shop_id;

```

`VIEW` ができました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/79e8ebc9-aea1-48b9-990e-ee4b51a315f0.png)

React / JavaScript から参照してみます。
`const { data } = await supabase.from("shops_books_view").select();`
は
`GET http://localhost:8000/rest/v1/shops_books_view?select=*`
になります。返却値は以下となります。

```JSON
[
  {
    "shop_id": "7294ad9a-535a-406e-87c9-2c33aee1e629",
    "shop_name": "shop1",
    "book_id": "6b086feb-bc6b-461b-b5ec-6f09ae94d8e2",
    "book_name": "book1"
  },
  {
    "shop_id": "7294ad9a-535a-406e-87c9-2c33aee1e629",
    "shop_name": "shop1",
    "book_id": "41c9bc66-c2cc-4537-b50b-2ebecb6d7673",
    "book_name": "book2"
  }
]
```

RPC は以下の記事を参照してください。

https://qiita.com/kabochapo/items/26b1bb753116a6904664#%E6%96%B9%E6%B3%95-2---rpc


### 注意点

```.env
SUPABASE_PUBLIC_URL=http://localhost:8000
ANON_KEY=your-anon-key
```

ブラウザで動く React / CSR で Supabase を使う場合、アクセスキー（ANON_KEYとSUPABASE_PUBLIC_URL）を隠す必要はありません。

テーブルごとに `Row Level Security(RLS) / 行レベルセキュリティ` を適切に設定することでアクセスコントロールをします。そのため、各種アクセスキーを隠す必要が無いという考え方になります。

[（出典）開発者を魅了するオープンソースソフトウェア Supabase とは | AWS Dev Day 2022 Japan #AWSDevDay](https://youtu.be/XWYkpQRLsFk?si=gjQ4TS66GAStKctF&t=2485)


## 番外編（環境構築）
Supabase は公式で Docker Componse の構成を提供しています。
ローカル環境の構築手順のメモを記載します。

**環境構築**

```bash
# Supabase のリポジトリを取得
git clone https://github.com/supabase/supabase.git

# フォルダを移動
cd supabase/docker

# .env ファイルの設定
cp .env.example .env

# Supabase を起動
docker compose up -d
```

**動作確認**

```bash
curl --location 'http://localhost:8000/rest/v1/' \
--header 'apikey: your-anon-key'
```

Postman を使うと便利です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/120cfe60-5458-4f06-8ab8-c436415cc322.png)

レスポンスが返ってきたら Supabase はローカルで動いています。
管理画面は `http://localhost:8000/` で確認できます。



## 番外編（API I/F 仕様書）
以下のAPIを叩くと `Swagger` が返却されます。[Swagger Editor](https://editor.swagger.io/) に入力すると API I/F 仕様書を表示することが出来ます。

たまたま気がついたのですが、便利ですねぇ。


```bash
curl --location 'http://localhost:8000/rest/v1/' \
--header 'apikey: your-anon-key'
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/3bf375b4-36d3-4db5-a288-80490e3d11e0.png)



# おわりに
今回は shops, books テーブル両方とも `SELECT Enable read access for all users` という公開権限の Row Level Security(RLS) にして検証をしました。

次回は RLS の深掘りと Auth 機能を触り、一歩づつ理解していきたいと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127260/6501af67-5bac-4459-8941-fed7b063862a.png)


（参考）

https://speakerdeck.com/mats16/supabase-aws-devday-2022