今更ながらsupabaseを触ってみる

知ってはいたもののfirebaseでよくねと思っていましたが、最近触らずに何か言うのもどうなのと思いsupabaseを触ってみようと思いいたりました。

## supabaseとは

https://supabase.com/

> Supabase is an open source Firebase alternative. Start your project with a Postgres database, Authentication, instant APIs, Edge Functions, Realtime subscriptions, and Storage.
> --- [supabase公式](https://supabase.com/)より

supabaseとは、firebaseに代わるBaaSです。
もっとも、firebaseとは異なりRDBだったりと細かな違いがあり、firebaseとまったく同じように使えるサービスというわけではないです。

オープンソースであるため、ローカル上に環境を作成することも可能です。

## 使い始める

### プロジェクト作成

email/passの他、Githubアカウントからも作成できます。

作成後、新規プロジェクト作成から以下の画面に遷移します。

![supabase-create-new-project.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532032/f71e842a-6662-189a-75e7-ba8e2db8e602.png)

Regionについては、近くですと日本のTokyoと韓国のsoulがありました。
Create new projectを選択すると、プロビジョニングが始まります。
1分かからずに作成されたので、待つには待ちますがそれほど待たされることはないです。

### DBからデータを取得する

クライアントライブラリとして、JS(公式サポート)とDart(コミュニティサポート)のライブラリが用意されています。
(アルファ版ですがPythonもあります)

試しに適当なデータを突っ込んでみます。

`create-next-app --ts`で適当なプロジェクトを作成し、ライブラリをインストール

```bash
pnpm install @supabase/supabase-js
```

`lib/supabaseClient.ts`を作成し、supabaseとfetchする準備を整えます。

```lib/spabaseClient.ts
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = process.env.SUPABASE_URL
const supabaseKey = process.env.SUPABASE_KEY
export const supabaseClient = createClient(supabaseUrl, supabaseKey)
```
```global.ts
declare namespace NodeJS {
  interface ProcessEnv {
    readonly SUPABASE_URL: string;
    readonly SUPABASE_KEY: string;
  }
}
```

RLSを無効にしたテーブルを作成し、なんか適当なスキーマを設定。
その後、Nextjs側に最低限処理を実行して結果を見れるだけのcomponentを作成し、api側でなんか適当な処理を実行します。

```api/supabase-database.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { supabaseClient } from "libs/supabaseClient";

export const handler = async (
  req: NextApiRequest,
  res: NextApiResponse
) => {
  const insertSomethingIntoSupabase = async () => {
    const insert = await supabaseClient
      .from("foo")
      .insert({ title: "吾輩は猫である", auther: "夏目漱石" });
    const { data, error } = await supabaseClient.from("foo").select();
    return { data, inaseError: insert.error, selectError: error };
  };
  const resSupabase = await insertSomethingIntoSupabase();
  res.status(200).json(resSupabase);
};

export default handler;
```
```components/Supabase.tsx
import { FC, useState } from "react";

export const Supabase: FC = () => {
  const [resSupabaseBody, setResSupabaseBody] = useState("");
  const fetchSupabase = async () => {
    const res = await fetch("/api/supabase-database");
    setResSupabaseBody(JSON.stringify(await res.json(), null, 2));
  };

  return (
    <div>
      <button onClick={() => fetchSupabase()}>Run Fetch</button>
      <p style={{ whiteSpace: "pre-wrap" }}>{resSupabaseBody}</p>
    </div>
  );
};

export default Supabase;
```

その結果がこちら。

![supabase-insert.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532032/fc4f3e40-b4cf-e2e4-80a3-daa831a043ab.png)

supabase側の管理画面で見ても、無事に反映されていることがわかります。

![supabase-inserted.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532032/fd0b8a8a-0877-60f5-536c-dc9b5eec3e6a.png)

### ファイルをアップロードする

バケットを作成し、ファイルをアップロードします。
バケットの作成はJS側で行うことも可能ですが、今回はダッシュボード側で作成してそこにJSからアップロードするという手法をとります。

ダッシュボード側でselectとinsertを許可したPolisyを設定後、APIを追加してそこにファイルを投げられるようにフロントを修正

```api/supabase-strage.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { supabaseClient } from "libs/supabaseClient";
import multer from "multer";

export const config = {
  api: {
    bodyParser: false,
  },
};

const upload = multer({
  storage: multer.memoryStorage(),
});

export const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  const body: any = await new Promise((resolve, reject) => {
    upload.single("file")(req, res, (err: any) => {
      if (err) return reject(err);
      resolve({ file: req.file, path: req.body.path });
    });
  });
  const { data, error } = await supabaseClient.storage
    .from("bar")
    .upload(
      `sample/${body.file.originalname}`,
      new Blob([body.file.buffer], { type: body.file.mimetype })
    );
  console.log(error);
  console.log(data);

  let status = 200;
  if (error) status = 500;
  return res.status(status).send("");
};

export default handler;
```

```components/Supabase.tsx
import { ChangeEvent, FC, useState } from "react";

export const Supabase: FC = () => {
  const [resSupabaseBody, setResSupabaseBody] = useState("");
  const [imageFile, setImageFile] = useState<Blob | undefined>(undefined);
  const fetchSupabase = async () => {
    const res = await fetch("/api/supabase-database");
    setResSupabaseBody(JSON.stringify(await res.json(), null, 2));
  };
  const onChangeFileInput = (e: ChangeEvent<HTMLInputElement>) => {
    if (!e.target.files) return;
    if (e.target.files[0]) setImageFile(e.target.files[0]);
  };
  const postFile = async () => {
    if (!imageFile) return;
    const formData = new FormData();
    formData.append('file', imageFile);
    const res = await fetch("/api/supabase-strage", {method: "POST", body: formData});
  };

  return (
    <div>
      <button onClick={() => fetchSupabase()}>Run Fetch</button>
      <p style={{ whiteSpace: "pre-wrap" }}>{resSupabaseBody}</p>
      <input
        type="file"
        id="input"
        accept="image/*"
        onChange={(e) => onChangeFileInput(e)}
      />
      <button onClick={() => postFile()}>Post File</button>
    </div>
  );
};

export default Supabase;
```

試しに↑のスクショを投げましたが、無事に投げれているみたいですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532032/dbe446dc-7812-322f-ebbb-8e31f9576937.png)

## おわりに

というわけでとりあえずsupabaseを触ってみました。
firebaseの無料枠が強いので無料で使う範囲だとsupabaseの方が弱そうですが、オープンソースなのでローカルに構築できるというのは個人的にすごく魅力的に見えます。
時間があったら認証系とかもっといろいろ触ってみたいですね。