27
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-01-16

はじめに

こんにちは!かほです♪🐥
現在、本業では技術広報業務やエンジニア業務などに従事しています。
今回の記事では、Next.js×Supabaseを用いたアプリ開発における投稿機能の実装とそれに伴うRLS(Row Level Security)の使用方法について説明します。

今回は下記の2点を実装しました。

  • 投稿機能・・・タイトルと内容を投稿する
  • 一覧機能・・・投稿した内容を表示する

Next.js×Supabase×vercelの連携方法から知りたい方は、下記の記事を参考にしてください🙌

本記事の投稿機能では、下記の認証機能が実装済みです。
認証機能をまだ実装していない方は、下記の記事を参考にしてください🙌

この記事の読者対象

  • Next.js×SupabaseでCRUD処理(投稿機能、一覧機能)を含むアプリを作ってみたい方
  • SupabaseRLS設定について知りたい方
  • 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"
  }

前提

今回は、postsテーブルを使って投稿機能を実装します。
テーブルはSupabase上のsqlエディターから作成します。
todosテーブルを作った事例があるため、よければ下記の記事「Supabaseのプロジェクトを作成しよう!」の項目を参考にしてください🙌

また、今回のテーブル構造は下記になります。

                                                                                                                                                                    
カラム デフォルト値
id int4
title text
created_at timestamptznow()
user_id uuid
content text

そもそもRLSとはなんぞや?

最初に、すべての機能に対する認可をRLSで設定します。
そもそもRLSを知らない人が多いと思うので簡単にご紹介。

RLSRow Level Securityの略称で、いわゆる行単位セキュリティーと呼ばれるものです。
ポリシーを設定することにより、テーブルに紐づけて、アクセス制御の記述を簡潔に書くことができます。

詳しくは下記の記事を参考にしてください🙌

RLSを設定しよう

RLSの有効化を確認しよう

最初に、postsテーブルに対してRLSを設定できる状態か確認します。

  1. 左側のバーの人マークであるAuthenticationを押します。
  2. ConfigurationPoliciesを選択します。
  3. テーブル名「posts」の右横に、下記のようなマークが出ていることを確認します。
    スクリーンショット 2023-01-16 9.11.56.png

Policyを作成しよう

次にPolicyを作成します。

  1. RLS有効化を確認した画面の右端にあるNew Policyというボタンを押します。
  2. ボタンを押すと下記の画面のように選択肢が2つ出てきます。
    テンプレートを使用してPolicyを作成したい方はGet started quicklyを選択し、完全に0からPolicyを作成したい方はFor full customizationを選択します。
    .
  3. 今回はテンプレートから作成したいので、Get started quicklyを選択しました。下記のような画面ができます。上から取得挿入更新削除の機能に対するSQLテンプレートがあります。
    .

投稿機能に対する認可を設定しよう

最初に投稿機能に対する認可の設定を行います。

  1. 投稿機能に対する認可を設定したいので、4つのSQLテンプレートの中からEnable insert access for authenticated users only(上から2つ目)を選択し、Use this templateボタンを押します。すると下記の画面が出てきます。
    .
  2. Policy nameを設定します。今回はログイン済みのアカウントのみが投稿できるようにしたいので、認証済みのアカウントのみ投稿できるという名前を設定します。
  3. Allowed operationを設定します。選択肢が4つありますが、データを挿入したいのでINSERTを選択します。
  4. Target rolesを設定します。今回はログイン済みのアカウントを対象とした認可のため、選択肢からauthenticatedを選択します。
  5. WITH CHECK expressionを設定します。ここに書くもののイメージとしてはSQLでいうWHERE句だと考えていいでしょう。今回は、ログイン済みのアカウントであれば、常に投稿できる状態にしたいのでtrueと書きます。
  6. 右下のReviewのボタンを押します。設定したSQL情報が下記の画面のように出てくるため、情報に間違えがなければSave policyを押してPolicyを保存します。
    スクリーンショット 2023-01-16 10.40.08.png

一覧機能に対する認可を設定しよう

次に一覧機能に対する認可の設定を行います。

  1. 一覧機能に対する認可を設定したいので、4つのSQLテンプレートの中からEnable read access to everyone(上から1つ目)を選択し、Use this templateボタンを押します。すると下記の画面が出てきます。
    .
  2. Policy nameを設定します。今回は全アカウントが閲覧できるようにしたいので、全員データの閲覧を可能にするという名前を設定します。
  3. Allowed operationを設定します。全てのデータを選択して表示させたいのでSELECTを選択します。
  4. Target rolesを設定します。今回は全てのアカウントを対象とした認可のため、設定は不要です。
  5. USING expressionを設定します。今回は、全てのアカウントが常にデータを閲覧できる状態にしたいのでtrueと書きます。
  6. 右下のReviewのボタンを押します。設定したSQL情報が下記の画面のように出てくるため、情報に間違えがなければSave policyを押してPolicyを保存します。
    スクリーンショット 2023-01-16 11.22.58.png

Policyの作成を確認しよう

投稿機能と一覧機能に対するPolicyの設定が終わりました。
Policyの設定が完了している場合は、下記の画像のように行として表示されるためご確認ください。また、Policyを編集・削除する場合は右端の縦3点のボタンを押すと可能です。
スクリーンショット 2023-01-16 11.27.55.png

投稿機能を実装しよう

最初に、投稿機能についての説明を行います。
下記がtop.tsxの投稿機能における全体像です。

top.tsx
import Head from "next/head";
import styles from "../styles/Home.module.css";
import { supabase } from "../utils/supabase";
import { useRouter } from 'next/router';
import { useState,useEffect } from 'react';


export default function Top(){
  const [newTitle, setNewTitle] = useState("");
  const [newContent, setNewContent] = useState("");

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

  return(
    <>
      <div className={styles.container}>
        <main className={styles.main}>
        <div>
          <form onSubmit={addPost}>
             <div>
                <label>タイトル</label><br/>
                <input
                  type="text"
                  value={newTitle}
                  onChange={(e) => setNewTitle(e.target.value)} />
              </div>
              <div>
                <label>内容</label><br/>
                <textarea value={newContent} rows={10} cols={40}  onChange={(e) => setNewContent(e.target.value)}/>
              </div>
              <div>
                <button type="submit">登録</button>
              </div>
          </form>
        </div>
      </main>
      <footer className={styles.footer}>
      </footer>
        </div>
    </>
  )
}

では、コードを上から順に追って説明します。
最初にタイトル、内容の各値を状態管理するための記述を行います。

top.tsx
const [newTitle, setNewTitle] = useState("");
const [newContent, setNewContent] = useState("");

次にsupabase.tsを呼び出し、認可部分の実装を行います。
今回はinsert()を使用し、postsテーブルにデータを挿入します。
引数には挿入したいデータカラムとそれに伴う登録フォームの変数を設定します。
insert()を使用する方法は、下記のsupabase公式ドキュメントにも記載されていますので、ご興味のある方はご参照ください🙌

await indexPost()に関しては、投稿機能の実装後に説明する一覧機能のメソッドを呼び出しています。登録した値が一覧画面に表示されるように設定しています。

投稿完了後は投稿フォームの値を空にしたいため、setNewTitle("")setNewContent("")によりタイトルと内容の変数の値を空に設定しています。
try-catch構文を用いることにより、例外処理を行い、エラーが出た場合の対処を行います。

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

投稿機能に関わる全体のマークアップを行います。
タイトルと内容のフォームにonChangeイベントを設定し、入力した各値を保持します。

top.tsx
return (
    <>
       <form onSubmit={addPost}>
          <div>
            <label>タイトル</label><br/>
            <input
              type="text"
              value={newTitle}
              onChange={(e) => setNewTitle(e.target.value)}
            />
          </div>
          <div>
            <label>内容</label><br/>
            <textarea value={newContent} rows={10} cols={40}  onChange={(e) => setNewContent(e.target.value)}/>
          </div>
          <div>
            <button type="submit">登録</button>
          </div>
      </form>
    </>
  )

下記がマークアップした投稿画面の画像です。
.

一覧機能を実装しよう

次に一覧機能についての説明を行います。
下記がtop.tsxの一覧機能における全体像です。

top.tsx
import Head from "next/head";
import styles from "../styles/Home.module.css";
import { supabase } from "../utils/supabase";
import { useState,useEffect } from 'react';


export default function Top(){
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    (async () => await indexPost())();
  }, []);


  const indexPost = async () => {
      try {
        const { data, error } = await supabase.from("posts").select("*");
        if (error) throw error;
        setPosts(data);
      } catch (error) {
        alert(error.message);
        setPosts([]);
      }
    };

  return(
    <>
      <div className={styles.container}>
      <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>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
        </div>
      </main>
      <footer className={styles.footer}>
      </footer>
    </div>
    </>
  )
}

一覧機能の実装方法について、コードを上から順に説明します。
最初に一覧表示をする投稿(posts)の値を状態管理するための記述を行います。
また、投稿内容の繰り返し表示を防ぐためにuseEffectindexPost()を呼び出します。

top.tsx
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    (async () => await indexPost())();
  },[]);

次にsupabase.tsを呼び出し、認可部分の実装を行います。
今回はselect()を使用し、postsテーブルのデータを全選択します。

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

一覧表示するデータをマークアップの際に使用したいため、setPosts()の変数にdataを設定しています。try-catch構文を用いることにより、例外処理を行い、エラーが出た場合の対処を行います。

top.tsx
const indexPost = async () => {
    try {
      const { data, error } = await supabase.from("posts").select("*");
      if (error) throw error;
      setPosts(data);
    } catch (error) {
      alert(error.message);
      setPosts([]);
    }
  };

一覧機能に関わる全体のマークアップを行います。
posts変数には上記で設定したsetPost(data)data(挿入した投稿の値)が入っています。
そのため、postsを用いてmap構文で値を出力します。

top.tsx
 return(
    <>
      <div className={styles.container}>
      <Head>
        <title>トップページ</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={styles.main}>
        <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>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </main>
      <footer className={styles.footer}>
      </footer>
    </div>
    </>
  )

下記がマークアップした一覧画面の画像です。
.

認可ができているか確認しよう!

RLSの設定と投稿機能の実装を終えました!
認可の設定が上手くいっているかを確認してみましょう♪
下記画像のデモは、以下の順ですすみます。

  1. ログイン済みの状態で投稿
  2. ログアウト
  3. ログアウト状態で投稿

「すべてのユーザーが投稿の一覧表示を見ることができる」「ログイン済みのユーザーのみがタイトルと内容を投稿できる状態になっている」ことを確認することができました😚成功です✌️
.

最後に

これで実装はおしまいです!

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

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

参考資料

27
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?