【Next.js & Supabase】Supabaseって実際どうなの？

# はじめに
こんにちは！[MYJLab Advent Calendar 2021](https://qiita.com/advent-calendar/2021/myjlab)の2日目を担当するサッチーです！
昨日の[@chiba___さんの記事](https://qiita.com/chiba___/items/fae8271cad6b9d2604fd)では、かわいいタートルを使って、アドベントカレンダー初日が盛り上がる記事を書いてくれました！⭐
わたしはアドベントカレンダーは３年連続の参加です！たのしんでいくぞ🔥

# これは？
最近お友達との開発（あれ、研究...あれ、就活...）で、DBやAPIまわりの技術リサーチをしています。
その際に、最近自分の中で流行っている「GraphQL+Prisma」と、世間的に流行っている？「[Supabase](https://supabase.com/)」を比較したいと思っていたので、今回はSupabaseを試してみました〜という記事をかきます！
次回の記事でGraphQLとPrismaのことを書く予定です。

# Supabaseってなに？
Supabaseは、Firebaseの代替プラットフォームとして、登場しました。
詳しい説明は、[こちらの記事](https://qiita.com/kabochapo/items/6a2a391832825d17af7d)で詳しくまとめられています！
今回はDBのみを試しますが、認証やストレージ機能も充実しています。
FirebaseとSupabaseの最もわかりやすい違いは、DBがPostgreSQLである点だと思います！
私はNo SQLが苦手なので、それだけでうれしいです。

# やること
事前にProductsテーブルと、Tagsテーブルと、2つのテーブルの中間テーブルであるProductTagsテーブルを用意し、外部キーの設定までしておきます。（次回の記事でも使います）
ひとつの製品が複数のタグを持っていて、ひとつのタグは複数の製品につけられている多対多の状態です。
Supabaseでは、DBの作成〜外部キー設定などの基本的な操作は、GUIで簡単に行なえます。SQL Editorを使えばインデックスをはったり、RLCを設定したりなど、普通のPostgreSQLと同様に扱うことができます。
各テーブルのschemaは、以下のような感じです。

```typescript
type Product = {
  id: number
  product_name: string
  released_at: string
  image_url: string
  hp_url: string
  average_price: number
  tags: Tag[]
}

type Tag = {
  id: number
  tag_name: string
  products: Product[]
}

type ProductTag = {
  id: number
  product_id: number
  tag_id: number
}
```
このDBを使って、

- product一覧の取得
- tagごとのproduct一覧の取得

を試してみます！
今回私のユースケースでは、データの更新をたまにしかしないので、Next.jsでSSGをする例になっています。
SSR、CSR、SSGなどの説明は本記事では割愛しますが、[こちらの記事](https://zenn.dev/bitarts/articles/37260ddb28ae5d)にとてもわかりやすくまとめられています〜⭐

# 実装
## supabaseの設定
[公式ドキュメント](https://supabase.com/docs/guides/with-nextjs)通りにセットアップをします。

「NEXT_PUBLIC_SUPABASE_URL」と「NEXT_PUBLIC_SUPABASE_ANON_KEY」を探し出して、``.env.local``に貼り付けます。
歯車→APIとたどると見つかると思います。
![スクリーンショット 2021-11-27 17.53.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533290/f5919034-aead-1599-926d-d6f56de7ca33.png)

```.env.local
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
```

```utils/supabaseClient.js
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY

export const supabase = createClient(supabaseUrl, supabaseAnonKey)
```
## Product一覧
いちばんシンプルなProduct一覧からやってみます。

```libs/product.ts
import { supabase } from '../utils/supabaseClient'

export async function getProducts(): Promise<Product[]> {
  const { data, error, status } = await supabase
    .from<Product>('products')
    .select(`
      *,
      tags (
        id,
        tag_name
      )
    `)

  if (error && status !== 406) {
    throw error
  }

  return data;
}
```

個人的に最高だなと思った点は、

- selectの書き方がGraphQLライクでレスポンスの形が直感的にわかりやすい
- 中間テーブル作るだけで、ネストしたデータまで一回のリクエストで取得できる

です！

データを表示する際は以下のように使えると思います。

```pages/products/index.tsx
import { FC } from 'react'
import { getProducts } from '../../libs/product'
import ProductList from '../../components/templates/productList'

type Props = {
  products: Product[];
};

const Products: FC<Props> = ({ products }) => {
  return (
    <div>
      <ProductList products={products}/>
    </div>
  )
}

export async function getStaticProps() {
  const products = await getProducts();
  return {
    props: {
      products,
    },
  }
}

export default Products;
```

## tagごとのproduct一覧
「いらすとや」というタグがついている商品のみを表示したい！みたいなケースです。
ルーティングは``tag/[tagId]``みたいな感じにしようと思います。
Next.jsでは、``getStaticPaths``というAPIを使うことで、SSGの際にも動的にルーティングを設定することができます。このAPIの中でpathを作るために、tagのID一覧を取得します。

```libs/tag.ts
import { supabase } from '../utils/supabaseClient'

export async function getTags(): Promise<Tag[]> {
  const { data, error, status } = await supabase
    .from<Tag>('tags')
    .select(`
      id
    `)
	
  if (error && status !== 406) {
    throw error
  }
  return data;
}
```

次に、tagのIDを引数にとって、そのtagの名前と、tagがついているproduct(複数)を取得します。

```libs/tag.ts
export async function getTags(): Promise<Tag[]> {
  // ・・・省略・・・
}

// ここから追加
export async function getTagProducts(tagId: number): Promise<Tag>{
  const { data, error, status } = await supabase
    .from<Tag>('tags')
    .select(`
      tag_name,
      products (
        *,
        tags (
          id,
          tag_name
        )
      )
    `)
    .eq('id', tagId)
    .single()
	
  if (error && status !== 406) {
    throw error
  }

  return data;
}
```

``eq()``でidがtagIdのものだけを取得するようにし、``single()``で返り値を配列ではなくて一つの要素にするようにしています。

取得したデータの表示

```pages/tags/[tagId].tsx
import { FC } from 'react'
import { getTags, getTagProducts } from '../../libs/tag'
import ProductList from '../../components/templates/productList'

type Props = {
  tagName: string,
  products: Product[];
};

const TagProducts: FC<Props> = ({ tagName, products }) => {
  return (
    <div>
      <h1>{tagName}</h1>
      <ProductList products={products}/>
    </div>
  )
}

export const getStaticPaths = async () => {
  const tags = await getTags();
  const paths = tags.map((tag:Tag) => `/tags/${tag.id}`);
  return { paths, fallback: false };
};

export const getStaticProps = async (context) => {
  const tagId = context.params.tagId;
  const tagProducts = await getTagProducts(tagId);
  return {
    props: {
      tagName: tagProducts.tag_name,
      products: tagProducts.products,
    },
  };
};

export default TagProducts;

```

# まとめ
- 導入が簡単
- PostgreSQL
- GraphQLっぽく、返してほしいカラムを指定できる
- TypeScriptと相性いい

など、趣味の開発では十分な機能が備わっているように感じました！
これから積極的に使っていきたいです。
気になる点や修正点などあればお申し付けください！フロントエンドまだまだ初心者なのでお手柔らかに🙏
最後までお読みいただきありがとうございました！

# 参考
- [飛躍的に伸びているBaaS「Supabase」の概要と所感](https://qiita.com/kabochapo/items/6a2a391832825d17af7d)
- [図解 CSR, SSR, SG(SSG), ISR](https://zenn.dev/bitarts/articles/37260ddb28ae5d)
- [Supabase docs](https://supabase.com/docs)
- [今話題のBaaS「supabase」をReactで試す](https://maasaablog.com/development/supabase/1412/)