【Next.js】Database Functionsを使ってSupabaseのデータベースに値を保存する方法を解説


今回は、Database Functionsという機能を使ってSupabaseのデータベースに値を保存する方法を解説していきます。

また、Next.jsからSupabaseにアクセスしてデータを取得して表示する方法についても合わせて解説していきます。

## Database Functionsを使って、ユーザーがボタンをクリックした数をSupabaseのDBに保存する

こちらの記事では、ログインしたユーザーがボタンを押した回数をSupabaseに記録するという処理を、SupabaseのDatabase Functionsという機能を使って実装していきます。

![スクリーンショット 2023-02-08 16のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/67c9268d-ad57-d55c-16c5-737429749e5a.png)

## 開発環境

- macOS Catalina 10.15.7
- Next.js 13.1.6
- Supabase 2.5.0
- Reactstrap 9.1.5

※Next.jsはJavaScriptを使っています。

## Supabaseでログイン機能を作成する

Supabaseを使ったログイン機能の実装に関しては、以前にこちらの記事で解説していますので、今回の実装はこちらの内容の続きとして解説していきます。

https://qiita.com/masakiwakabayashi/items/716577dbfebf83665378

上記の記事で実装したコードはこちらのリポジトリにアップしてありますので、まずは以下のコマンドでリポジトリの内容をクローンしてください。

```
git clone https://github.com/masakiwakabayashi/nextjs_supabase_auth
```

以下のコマンドでパッケージのインストールとプロジェクトを立ち上げを行います。

```
npm install
```

```
npm run dev
```

また、ログイン機能を作る上でSupabseの管理画面から設定が必要な部分もありますが、それに関しても上記の記事で解説しています。

Supabaseの管理画面からの設定も終わったら、ユーザー登録とログインができるかどうかを確認してください。

ユーザー登録は http://localhost:3000/register から、ログインは http://localhost:3000/login から確認できます。

ログインができることを確認したら、ユーザーがボタンをクリックした回数が表示される画面とクリックするためのボタンを作成していきます。

## ページコンポーネントを作成

pagesディレクトリにclick_count.jsというファイルを新しく作成してください。click_count.jsの中身は以下のように書きます。

```pages/click_count.js
import styles from '../styles/Home.module.css'
import { Button } from 'reactstrap';
import { useEffect, useState } from 'react';
import { supabase } from '../utils/supabase';
import { useRouter } from 'next/router';

const ClickCountPage = () => {
  const router = useRouter();
  // ユーザーのメールアドレス
  const [currentUserEmail, setcurrentUserEmail] = useState('');
  // クリック数
  const [clickCount, setClickCount] = useState(0);

  // 現在ログインしているユーザーを取得する処理
  const getCurrentUser = async () => {
    // セッションがある場合はログインしているユーザーを取得し、ない場合はログインページに遷移させる
    const { data } = await supabase.auth.getSession()
    if (data.session !== null) {
      const { data: { user } } = await supabase.auth.getUser()
      setcurrentUserEmail(user.email)
    } else {
      router.push('/login')
    }
  }

  // ページが読み込まれたときにgetCurrentUser()を実行
  useEffect(()=>{
    getCurrentUser()
  },[])

  // クリック数を取得する処理
  const handleClick = async () => {
    setClickCount(clickCount + 1)
  }

  return (
    <div className={styles.card}>
      <div style={{ paddingBottom: "1rem" }}>
        { currentUserEmail } でログインしています。
        クリック数: { clickCount }
      </div>
      <Button color="primary" onClick={handleClick}>
        クリック
      </Button>
    </div>
  );
}

export default ClickCountPage;
```

実際に、http://localhost:3000/click_count にアクセスしてみると、ユーザーがボタンをクリックした数が表示されるようにはなっていますが、ページをリロードするとクリック数のカウントがゼロに戻ってしまいます。
![スクリーンショット 2023-02-08 16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/c59ff274-a86c-1938-e54d-d954e338427d.png)


ここからは、ページをリロードしてもユーザーがボタンをクリックした数がゼロに戻らないようにするために、

- クリック数をSupabaseのデータベースに保存する処理
- Supabaseのデータベースに保存されているクリック数を画面に表示させる処理

を作成していきます。

## Supabaseにボタンのクリック数を保存するためのテーブルを作成する

まずはSupabaseにユーザーがボタンをクリックした数を保存するためのテーブルを作成していきます。

Supabaseにログインしてプロジェクトの管理画面を開いて、サイドバーからデータベースを選択してください。
![スクリーンショット 2023-02-08 13.46.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/1b983d03-395a-c8ad-a6e3-f8578f8be91d.png)

右上にある「new Table」をクリックします。
![スクリーンショット 2023-02-08 11.32.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/48ff810e-f26c-a178-9076-59bcc2077ec9.png)

クリックすると以下のような画面になります。
![スクリーンショット 2023-02-08 13.51.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/9a8a286f-e01c-cb7a-669e-d361e5ff3c2e.png)

一番上のNameのところにprofilesと入力します。これが作成するテーブルのテーブル名になります。
![スクリーンショット 2023-02-08 13.52.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/2d678d06-bb3d-212b-f35f-e0476b24c33a.png)

次に「Enable Row Level Security (RLS)」をオフにします。
![スクリーンショット 2023-02-08 11.34.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/a43bbee5-0176-b1ba-1690-82766876f623.png)

その下にあるColumnsのidにユーザーのidが紐づくようにします。idのNameとTypeの間にあるチェーンのようなマークをクリックしてください。
![スクリーンショット 2023-02-08 14.00.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/0ae18e85-40c6-921c-f8e4-5b6aad9264c2.png)


すると外部キー制約を設定できる画面が表示されるので、authのusersを選択してください。
![スクリーンショット 2023-02-08 14.04.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/a7bfd482-21eb-d777-aac7-c7a14941a8e4.png)

usersを選択するとどのカラムを参照するのかを選択できるセレクトボックスが表示されるので、「id uuid」を選択してsaveをクリックしてください。

![スクリーンショット 2023-02-08 14.07.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/b8ff2da7-67be-fc8d-3bdd-29136aeaa2c6.png)
これでテーブルの外部キー制約が設定できました。

次はクリック数を格納するためのカラムを追加するので「Add column」を選択してください。
![スクリーンショット 2023-02-08 14.10.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/06e83516-9587-500c-3533-1c17b9e7e693.png)

新しいカラムの入力欄が表示されるので、Nameをclicks、Typeをint8、Default Valueを0としてSaveをクリックしてください。

![スクリーンショット 2023-02-08 14.19.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/dc2ac2d3-655f-4540-1530-9aa41e97265b.png)

テーブルの作成はこれで完了です。次はテーブルのデータを作成します。

まず、サイドバーから「Authentication」を選択してください。
![スクリーンショット 2023-02-08 14.56.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/eb4aa04d-985f-4dd3-cc43-2f6a67fa1b14.png)

自身でユーザー登録の画面から作成したユーザーが表示されているので、そのユーザーの「User UID」をコピーしておきます(クリックするとコピーできます)。
![スクリーンショット 2023-02-08 14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/60a63af4-2aa2-162d-0fa4-885460fdc13f.png)

サイドバーから「Table Editor」を選択してください。
![スクリーンショット 2023-02-08 15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/c4a78d2b-3e97-b44f-a611-f13d7c3bc300.png)

テーブルの一覧に先ほど作成したprofilesテーブルがありますので、それを選択します。
![スクリーンショット 2023-02-08 14.40.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/420a6da2-a135-410d-2c3b-6a58d6de3eb1.png)

「Insert」のボタンをクリックします。
![スクリーンショット 2023-02-08 11.37.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/082d1d29-14a8-4a2b-67ed-7b84270e4713.png)

「Insert row」と「Insert column」が出てくるので、「Insert row」を選択します。
![スクリーンショット 2023-02-08 12.02.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/73ee4840-b7ca-1bef-6870-72e077586037.png)

idに先ほどコピーした自身で作成したユーザーのuuidを貼り付け、created_atに適当な日時を選択し、clicksの値を0として「Save」をクリックします。
![スクリーンショット 2023-02-08 12.02.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/b35f29dc-3e3b-e9ff-88ad-85b40e714025.png)

これで自身で作成したユーザーがログインした際にボタンをクリックした数を保存するためのテーブルデータの作成が完了しました。

## Database Functionを作成する

では、ここからユーザーがボタンをクリックした数を先ほどのテーブルデータに格納するためのDatabase Functionを作成していきます。

Supabaseのプロジェクト管理画面を開いて、サイドバーから「Database」を選択します。
![スクリーンショット 2023-02-08 15.13.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/0ac77bf9-6b83-3c8e-404a-788b69051d14.png)

Databaseの画面の中に「Functions」というところがあるので、そちらを選択します。
![スクリーンショット 2023-02-08 15.13.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/dc18b0e2-bde4-9b84-de25-dc7431d1292b.png)

次に「Create a new function」を選択します。
![スクリーンショット 2023-02-08 15.54.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/cac6fb8a-9b08-02e4-40f2-16304e02e210.png)

すると新しいDatabase Functionの作成画面が表示されるので、「Name of function」にtest_incrementと入力して「Return type」にint8を選択します。
![スクリーンショット 2023-02-08 15.55.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/ec566e7b-215a-f3de-2ce3-a6bb022aa32a.png)

また、Definitionは以下のように書きます。

```sql
declare
  new_clicks int;
begin
  -- ログインしているユーザーのクリック数を取得
  select clicks
  into new_clicks
  from public.profiles
  where id = auth.uid();
  -- クリック数を増やす
  new_clicks = new_clicks + 1;
  -- クリック数を更新
  update public.profiles
  set clicks = new_clicks
  where id = auth.uid();
  -- 増やしたクリック数をリターン
  return new_clicks;
end;
```
ここまで入力したらConfirmを押して内容を確定させます。Database Functionの作成はこれで完了です。



## Next.jsからDatabase Functionを呼び出す

Next.jsから先ほど作成したDatabase Functionを呼び出す処理を作成していきます。

click_count.jsの中身を以下のように編集します。

```pages/click_count.js
import styles from '../styles/Home.module.css'
import { Button } from 'reactstrap';
import { useEffect, useState } from 'react';
import { supabase } from '../utils/supabase';
import { useRouter } from 'next/router';


const ClickCountPage = () => {
  const router = useRouter();
  // ユーザーのメールアドレス
  const [currentUserEmail, setcurrentUserEmail] = useState('');
  // クリック数
  const [clickCount, setClickCount] = useState(0);

  // 現在ログインしているユーザーを取得する処理
  const getCurrentUser = async () => {
    // セッションがある場合はログインしているユーザーを取得し、ない場合はログインページに遷移させる
    const { data } = await supabase.auth.getSession()
    if (data.session !== null) {
      const { data: { user } } = await supabase.auth.getUser()
      setcurrentUserEmail(user.email)
    } else {
      router.push('/login')
    }
  }

  // ページが読み込まれたときにgetCurrentUser()を実行
  useEffect(()=>{
    getCurrentUser()
  },[])

  // クリック数を取得する処理
  const handleClick = async () => {
    // database functionを呼び出す処理
    const { data, error } = await supabase.rpc('test_increment')
    // エラーが起きたときはコンソールにエラーを表示
    if (error) console.error(error)
    // 更新されたクリック数をコンソールに表示
    console.log(data);
    // 画面に表示するクリック数を更新する処理
    setClickCount(clickCount + 1)
  }

  return (
    <div className={styles.card}>
      <div style={{ paddingBottom: "1rem" }}>
        { currentUserEmail } でログインしています。
        クリック数: { clickCount }
      </div>
      <Button color="primary" onClick={handleClick}>
        クリック
      </Button>
    </div>
  );
}

export default ClickCountPage;
```

変更したのは以下の部分です。こちらの処理でDatabase Functionを呼び出して、ユーザーがボタンを押したときにクリック数が更新されるようにしています。

```pages/click_count.js
  // クリック数を取得する処理
  const handleClick = async () => {
    // database functionを呼び出す処理
    const { data, error } = await supabase.rpc('test_increment')
    // エラーが起きたときはコンソールにエラーを表示
    if (error) console.error(error)
    // 更新されたクリック数をコンソールに表示
    console.log(data);
    // 画面に表示するクリック数を更新する処理
    setClickCount(clickCount + 1)
  }
```

実際にボタンをクリックすると、Sudpabaseに保存されているクリック数が更新されていることが確認できます。
![スクリーンショット 2023-02-08 17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/799f3251-ffd4-a54f-c6f9-53ff7643f3d7.png)

また、Supabaseの管理画面からもクリック数が更新されていることが確認できます。
![スクリーンショット 2023-02-08 17.40.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/4e56b6ed-3f1e-aa28-f34c-d5804f7f03a2.png)

## Supabaseに保存されているクリック数を取得して、画面に表示させる処理を作成する

続いてはSupabaseに保存されているユーザーのクリック数を取得して画面に表させるためのNext.jsの処理を書いていきます。

click_count.jsの中身を以下のように編集してください。

```pages/click_count.js
import styles from '../styles/Home.module.css'
import { Button } from 'reactstrap';
import { useEffect, useState } from 'react';
import { supabase } from '../utils/supabase';
import { useRouter } from 'next/router';

const ClickCountPage = () => {
  const router = useRouter();
  // ログインユーザーのid
  const [currentUserId, setcurrentUserId] = useState('');
  // ユーザーのメールアドレス
  const [currentUserEmail, setcurrentUserEmail] = useState('');
  // クリック数
  const [clickCount, setClickCount] = useState(0);

  // 現在ログインしているユーザーを取得する処理
  const getCurrentUser = async () => {
    // セッションがある場合はログインしているユーザーを取得し、ない場合はログインページに遷移させる
    const { data } = await supabase.auth.getSession()
    if (data.session !== null) {
      const { data: { user } } = await supabase.auth.getUser()
      setcurrentUserId(user.id)
      setcurrentUserEmail(user.email)
    } else {
      router.push('/login')
    }
  }

  // ページが読み込まれたときにgetCurrentUser()を実行
  useEffect(()=>{
    getCurrentUser()
  },[])


  // supabaseのclicksテーブルからクリック数を取得する処理
  const getClickCount = async () => {
    // supabaseのprofilesテーブルから現在ログインしているユーザーのclicksカラムの値を取得
    const { data, error } = await supabase
      .from('profiles')
      .select('clicks')
      .eq('id', currentUserId)
      .single();
    // エラーが起きたときにコンソールに出力
    if (error) console.error(error)
    // supabaseから取得したクリック数をclickCountにセット
    setClickCount(data.clicks)
  }

  // ログインしているユーザーを取得できているときだけgetClickCount()を実行
  if (currentUserId !== '') {
    getClickCount()
  }

  // クリック数を取得する処理
  const handleClick = async () => {
    // database functionを呼び出す処理
    const { data, error } = await supabase.rpc('test_increment')
    // エラーが起きたときはコンソールにエラーを表示
    if (error) console.error(error)
    // 更新されたクリック数をコンソールに表示
    console.log(data);
    // 画面に表示するクリック数を更新する処理
    setClickCount(data)
  }

  return (
    <div className={styles.card}>
      <div style={{ paddingBottom: "1rem" }}>
        { currentUserEmail } でログインしています。
        クリック数: { clickCount }
      </div>
      <Button color="primary" onClick={handleClick}>
        クリック
      </Button>
    </div>
  );
}

export default ClickCountPage;
```

こちらの部分がSupabaseに保存されているクリック数を取得する処理です。

```pages/click_count.js
  // supabaseのclicksテーブルからクリック数を取得する処理
  const getClickCount = async () => {
    // supabaseのprofilesテーブルから現在ログインしているユーザーのclicksカラムの値を取得
    const { data, error } = await supabase
      .from('profiles')
      .select('clicks')
      .eq('id', currentUserId)
      .single();
    // エラーが起きたときにコンソールに出力
    if (error) console.error(error)
    // supabaseから取得したクリック数をclickCountにセット
    setClickCount(data.clicks)
  }

  // ログインしているユーザーを取得できているときだけgetClickCount()を実行
  if (currentUserId !== '') {
    getClickCount()
  }

```

ここまでできたらページをリロードしても表示されているクリック数がゼロに戻らないことを確認してみてください。

![スクリーンショット 2023-02-08 17のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/30a4e327-4d37-9667-7710-d06be92dda98.png)


## まとめ

Database Functionsを使えば、SQLの処理を事前に用意しておき、Next.jsで用意しておいた処理を実行することができる。

今回の解説に使ったコードはこちらのリポジトリに上げています。
https://github.com/masakiwakabayashi/nextjs_supabase_db_functions