話題のSupabaseでサクッと投稿の削除機能をつくってみた！

# はじめに
こんにちは！[かほ](https://twitter.com/kaho_eng)です♪🐥
現在、本業では技術広報業務やエンジニア業務などに従事しています。
今回の記事では、**Next.js**×**Supabase**を用いたアプリ開発における投稿の削除機能の実装について説明します。

# 前提
今回は、投稿の削除機能の実装を行います。
以下の記事の内容はすでに実装済みですので、まだ未実装の方は参考にしてみてください♪🐥

**Next.js**×**Supabase**×**Vercel**の連携方法について知りたい方

https://qiita.com/KAHO23340582/items/8a7faf77222a599fb31c

認証機能の実装について知りたい方

https://qiita.com/KAHO23340582/items/cb8d735b5b6ca1b3a6c5

**CRUD**処理(投稿機能、一覧機能)の実装について知りたい方

https://qiita.com/KAHO23340582/items/6f9ac01d77ab198881f4

# この記事の読者対象
* **Next.js**×**Supabase**でCRUD処理(投稿の削除機能)を含むアプリを作ってみたい方
* **Supabase**の**RLS**設定について知りたい方
* **Supabase**を今後使ってみたい方
* **Next.js**を使って高速でアプリを作りたい方

# 開発環境
```package.json
"dependencies": {
    "@supabase/supabase-js": "^2.2.1",
    "eslint": "8.28.0",
    "eslint-config-next": "13.0.6",
    "next": "13.0.6",
    "react": "18.2.0",
    "react-dom": "18.2.0",
  },
  "devDependencies": {
    "@types/node": "^18.11.13",
    "@types/react": "18.0.26"
  }
```
# 認証済みアカウントのuser_idを投稿に設定しよう！
投稿の削除機能を実装するにあたり、投稿のユーザーIDを取得する必要があります。
その際に必要なコードの全体像を下記に記載します。

```jsx:top.tsx
import { User } from "@supabase/supabase-js";

export default function Top() {
  const [user, setUser] = useState<User | null>(null);
  useEffect(() => {
    (async () => {
      await indexPost();
      const { data } = await supabase.auth.getUser();
      setUser(data.user);
    })();
  }, []);

  const addPost = async (e) => {
    if (user === null) return;
    
    e.preventDefault();
    try {
      const { error } = await supabase.from("posts").insert([
        {
          user_id: user.id,
          title: newTitle,
          content: newContent,
        },
      ]);
      if (error) throw error;
      await indexPost();
      setNewTitle("");
      setNewContent("");
    } catch (error) {
      alert("データの新規登録ができません");
    }
  };
}

```

コードを上から順に説明します。
`getUser()`を使用し、認証済みユーザーのデータを取得します。
`getUser()`を使用する方法は、下記の**Supabase**公式ドキュメントにも記載されていますので、ご興味のある方はご参照ください🙌

https://supabase.com/docs/reference/javascript/auth-getuser

取得したユーザーのデータを状態管理するため、`setUser(data.user)`を記載します。

```jsx:top.tsx
const [user, setUser] = useState<User | null>(null);
  useEffect(() => {
    (async () => {
      await indexPost();
      const { data } = await supabase.auth.getUser();
      setUser(data.user);
    })();
  }, []);
```
次に投稿したものに`user_id`を付与します。

https://qiita.com/KAHO23340582/items/6f9ac01d77ab198881f4

上記の記事で、投稿機能を実装していますが、`insert()`を用いて投稿データを挿入する際に
`user_id`を引数に入れます。`user.id`のuserは上記で取得し状態管理している認証済みのユーザー情報です。


```jsx:top.tsx
      const { error } = await supabase.from("posts").insert([
        {
          user_id: user.id,
          title: newTitle,
          content: newContent,
        }
      ]);
```

実装後、投稿してみると下記の画像のように、postのuser_idに値が入ります！
投稿後はuser_idが取得できているか確認しましょう♪🐥
![スクリーンショット 2023-02-03 22.14.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1547587/8d8dd756-383b-4a93-0eb8-b9f9ca3e9720.png)


# RLSを設定しよう
## Policyを作成しよう
次にPolicyを作成します。
1. RLS有効化を確認した画面の右端にある`New Policy`というボタンを押します。
1. ボタンを押すと下記の画面のように選択肢が2つ出てきます。
テンプレートを使用してPolicyを作成したい方は`Get started quickly`を選択し、完全にゼロからPolicyを作成したい方は`For full customization`を選択します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1547587/c40fa19e-2b62-aa4a-2e85-ac7556f682fd.png"  width="500" height="186" />.
1. 今回はテンプレートから作成したいので、`Get started quickly`を選択しました。下記のような画面ができます。上から`取得`、`挿入`、`更新`、`削除`の機能に対するSQLテンプレートがあります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1547587/02b93627-cc33-2957-0f43-c41dbe58b623.png"  width="700" height="186" />.

## 削除機能に対する認可を設定しよう
次に削除機能に対する認可の設定を行います。
1. 削除機能に対する認可を設定したいので、4つのSQLテンプレートの中から`Enable read access to everyone`(上から4つ目)を選択し、`Use this template`ボタンを押します。すると下記の画面が出てきます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1547587/546fe958-c125-2465-8bba-3c8b0fef6bdd.png"  width="700" height="186" />.
1. `Policy name`を設定します。今回は`認証済みのアカウントでかつ、投稿者本人のみが投稿できる`という名前を設定します。
1. `Allowed operation`を設定します。全てのデータを選択して表示させたいので`DELETE`を選択します。
1. `Target roles`を設定します。今回は認証済みのアカウントを対象とした認可のため、選択肢から`authenticated`を選択します。
1. `USING expression`を設定します。今回は、投稿者本人のみが投稿を削除できる状態にしたいので、認証済みのアカウント(auth.uid())と投稿のユーザー(post.user_id)が一致しているという意味で`auth.uid() = posts.user_id`と書きます。
1. 右下の`Review`のボタンを押します。設定したSQL情報が下記の画面のように出てくるため、情報に間違えがなければ`Save policy`を押してPolicyを保存します。

## Policyの作成を確認しよう
削除機能に対するPolicyの設定が終わりました。
Policyの設定が完了している場合は、下記の画像のように行として表示されるためご確認ください。また、Policyを編集・削除する場合は右端の縦3点のボタンを押すと可能です。
![スクリーンショット 2023-02-03 21.49.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1547587/68e9c25f-ee8b-5f46-121a-8f1ee378c1bd.png)



# 削除機能を実装しよう
次に削除機能についての説明を行います。
下記が`top.tsx`の削除機能における全体像です。
```jsx:top.tsx
const handleDelete = async (postID: number) => {
    try {
      const res = await supabase.from("posts").delete().eq("id", postID);
      const { data: posts } = await supabase.from("posts").select("*");
      setPosts(posts);
    } catch (error) {
      alert(error.message);
    }
  };

return (
    <>
      <div className={styles.container}>
        <Head>
          <title>トップページ</title>
          <link rel="icon" href="/favicon.ico" />
        </Head>
        <main className={styles.main}>
          <div>
            <h1>トップページ</h1>
            <div>
              <table>
                <thead>
                  <tr>
                    <th>投稿日</th>
                    <th>タイトル</th>
                    <th>内容</th>
                  </tr>
                </thead>
                <tbody>
                  {posts.map((post) => (
                    <tr key={post.id}>
                      <td>{post.created_at.substr(0, 10)}</td>
                      <td>{post.title}</td>
                      <td>{post.content}</td>
                      <td>
                        <button
                          onClick={() => handleDelete(post.id)}
                          className="border-gray-300 border-2 rounded p-1 w-12"
                        >
                          削除
                        </button>
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </div>
        </main>
        <footer className={styles.footer}></footer>
      </div>
    </>
  );
```

削除機能の実装方法について、コードを上から順に説明します。

次にsupabase.tsを呼び出し、実装を行います。
今回は`delete()`を使用し、postsテーブルのデータを削除します。
`from`の引数にテーブル名を記載し、`eq`の引数にテーブルのidと削除したいデータidの変数を記載します。

`delete()`を使用する方法は、下記のsupabase公式ドキュメントにも記載されていますので、ご興味のある方はご参照ください🙌

https://supabase.com/docs/reference/javascript/delete

データを削除した後に、削除していないデータを表示したいため、
`select()`を用いてsupabaseのデータを取得し、`setPosts(posts)`を用いてデータを表示します。
`try-catch`構文を用いることにより、例外処理を行い、エラーが出た場合の対処を行います。
```typescript:top.tsx
const handleDelete = async (postID: number) => {
    try {
      const res = await supabase.from("posts").delete().eq("id", postID);
      const { data: posts } = await supabase.from("posts").select("*");
      setPosts(posts);
    } catch (error) {
      alert(error.message);
    }
  };
```
削除機能に関わるマークアップは下記です。
`handleDelete`メソッドを呼び出し、引数に削除したデータのidを入れます。

```jsx:top.tsx
<button
  onClick={() => handleDelete(post.id)}
  className="border-gray-300 border-2 rounded p-1 w-12"
>削除</button>
 
```

# 削除ができたか確認してみよう！
では、最後にデモ動画で投稿の削除ができたかを確認します。
すでにアカウントが認証済みの段階から、投稿の登録~削除を行いました！

「投稿を投稿者本人が削除できる」ことを確認できました！成功です🎉😊
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1547587/8c75f026-2aa7-d392-56e2-66f2f5f7bad0.gif"  width="500" height="100" />.




# 最後に
これで実装はおしまいです！

今回は、**Next.js**×**Supabase**を用いたアプリ開発における投稿の削除機能の実装について説明しました。今後は主にフロントエンド周辺、コミュニティ運営、Tech PR(技術広報)の記事を中心に書いていく予定ですので、気になる方はぜひフォローをよろしくお願いします♪ではでは〜🐥


Twitterアカウント:https://twitter.com/kaho_eng

# 参考資料
https://qiita.com/KAHO23340582/items/8a7faf77222a599fb31c

https://qiita.com/KAHO23340582/items/cb8d735b5b6ca1b3a6c5

https://qiita.com/KAHO23340582/items/6f9ac01d77ab198881f4

https://supabase.com/docs/reference/javascript/auth-getuser

https://supabase.com/docs/reference/javascript/delete