Supabaseの関数をNext.jsアプリから呼び出してトランザクション処理をする
前回の記事でSupabaseを使って、簡単な関数を作成し、Next.jsのアプリから呼びだしてみました。
今回は、テーブルの操作を絡めた関数を作成してみます。
本記事は、Supbaseが公開している動画のコードをベースに、ステップバイステップで自身の環境で試せるようにアレンジしています。
セットアップ
前回の記事をベースにしていきます。
degit経由で取得して、必要なパッケージをインストールします。
npx degit hirotaka/examples/supabase-nextjs-database#atlanta-0.0.4 supabase-nextjs-database
cd supabase-nextjs-database
npm install
環境変数のセットなどは次を参考にしくてださい。
コンポーネントを作成
まずは、コンポーネントを作成して、クリックした数を表示するようにしてみましょう。
import { useState } from 'react'
export default function Counter() {
const [clicked, setClicked] = useState(0)
const [loading, setLoading] = useState()
const incrementCount = async () => {
setClicked(clicked + 1)
}
return (
<div className="mt-6">
<label className="block">カウンター</label>
<p className="mt-2">{loading ? '読み込み中...' : `${clicked}回`}</p>
<button className="primary mt-4" onClick={incrementCount}>
クリック
</button>
</div>
)
}
ダッシュボードにコンポーネントを読み込みます。
@@ -1,15 +1,17 @@
import { useUser } from '@/contexts/user'
import Message from '@/components/message'
import Profile from '@/components/profile'
+import Counter from '@/components/counter'
export default function Dashboard() {
- const { logout } = useUser()
+ const { logout, user } = useUser()
return (
<div className="max-w-md mx-auto">
<h1 className="text-2xl">ダッシュボード</h1>
<Message />
<Profile />
+ {user.profile && <Counter />}
<div className="mt-6">
<button className="secondary" onClick={() => logout()}>
ログアウト
これでクリックボタンをクリックすると、カウントアップすることを確認できます。
このままだと、再読み込みをしてしまうと0に戻ってしまいます。
関数を追加
Supbaseのテーブルにクリック数を保持して、インクリメントする関数を作成します。
SQL Editorでprofiles
テーブルにクリック数を保持するカラムを追加するSQLを実行します。
alter table profiles add column clicks int8 default 0;
まず、アプリでprofiles
テーブルのclickes
カラムの値を取得して表示します。
@@ -1,8 +1,15 @@
-import { useState } from 'react'
+import { useEffect, useState } from 'react'
+import { useUser } from '@/contexts/user'
+import { supabase } from '@/lib/supabase-client'
export default function Counter() {
const [clicked, setClicked] = useState(0)
const [loading, setLoading] = useState()
+ const { user } = useUser()
+
+ useEffect(() => {
+ if (user) setClicked(user.profile.clicks)
+ }, [user])
const incrementCount = async () => {
setClicked(clicked + 1)
例えば、Table Editorでprofiles
テーブルのclicks
の値を「12」にしてみます。
再読み込みすると、クリック数にTable Editorで入力した値が表示されることを確認できます。
次に、クリック数をインクリメントする関数を作成します。
下記のコードをSQL Editorで実行します。
create or replace function increment_clicks()
returns int8
language plpgsql
as $$
declare
new_clicks int;
begin
-- 現在のクリック数をselect
select clicks
from public.profiles
where id = auth.uid();
-- クリック数をインクリメント
new_clicks = new_clicks + 1;
-- クリック数を更新
update public.prifiles
set clicks = new_clicks
where id = auth.uid();
-- インクリメントした数を返す
return new_clicks;
end;
$$;
関数内で、2つのSQLを実行しています。これによりアプリからは1回の呼びだしだけで1連の処理を実現します。
アプリのincrementCount
メソッドを書き換えて、Supabaseの関数を呼びだすようにします。
@@ -12,15 +12,25 @@ export default function Counter() {
}, [user])
const incrementCount = async () => {
- setClicked(clicked + 1)
+ try {
+ setLoading(true)
+ const { data } = await supabase.rpc('increment_clicks')
+ setClicked(data)
+ } finally {
+ setLoading(false)
+ }
}
return (
<div className="mt-6">
<label className="block">カウンター</label>
<p className="mt-2">{loading ? '読み込み中...' : `${clicked}回`}</p>
- <button className="primary mt-4" onClick={incrementCount}>
- クリック
+ <button
+ className="primary mt-4"
+ onClick={incrementCount}
+ disabled={loading}
+ >
+ {loading ? '読み込み中...' : 'クリック'}
</button>
</div>
)
関数のreturn
句で指定した値がrpc
でdata
として返されます。その値をステートに保持します。
ボタンをクリックすると、Supbaseの関数が呼びだされ、クリック数をインクリメントして保存されます。
これで、再読み込みしても、保存されたクリック数が表示されるのを確認できます。
ここまでの、コード全体はGitHubリポジトリーを参照してください。
おわりに
SupbaseでテーブルをSELECT
して、UPDATE
する簡単なトランザクション処理する一連の流れを紹介しました。関数を使うことで、制御構造や複雑な演算をPostgresで処理ができます。
もちろん、同様な処理をミドルウェアで処理も可能です。ただ、その場合に比べて、複数の問い合わせをひとまとめに処理し、ネットワークのオーバーヘッドを軽減できます。
ただ、「PL/pgSQLでの記述は…」という方もいらっしゃることでしょう。その場合は、plv8という拡張機能を使うことで、JavaScriptでの記述ができます。
次回以降でまた詳しく紹介して参ります。
参考
Supbaseの知識を深めるために、ドキュメントの翻訳に取り組んでいます。
関数について、こちらも参考にしてください。
Discussion