3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Supabaseで"トランザクションっぽい"処理を行う

Last updated at Posted at 2025-06-03

はじめに

Supabaseは手軽に使えるBaaSですが、トランザクション制御をクライアント側から直接扱う機能は公式には提供されていません。
そのため、複数テーブルにまたがる一連の操作を安全に行いたい場合、トランザクションのような処理を自前で工夫して実装する必要があります。
公式の方法ではないのでおそらく推奨でもないでしょうが、知っておくのは何かと良いのではないかと思い記事にしました。

なぜトランザクションが必要?

問題の例

たとえば、以下の2つのテーブル構成があります。
users はユーザの基本情報(ユーザ名, 自己紹介, GitHubID)を保存するテーブル。
user_skill は各ユーザに紐づくスキルを登録するテーブル。1ユーザに複数スキルを持たせることができ、user_idusers テーブルと関連づけられている。

users テーブル

カラム名 制約・説明
user_id uuid 主キー ユーザID
name text NOT NULL ユーザ名
description text 自己紹介
github_id text GitHub ID

user_skill テーブル

カラム名 制約・説明
id number 主キー
user_id uuid 外部キー(user.user_id)
skill text NOT NULL スキル名

たとえば、ユーザー登録時に:

  • users テーブルにユーザー基本情報をINSERT
  • user_skill テーブルにスキル情報を複数INSERT
    このとき、片方だけ成功してもう片方が失敗すると「データの整合性が壊れる」状態になります。
    これを避けるために必要なのが 「すべて成功か、すべて失敗か」 を保証する トランザクション です。

Supabaseではどうするか(→rpcを使ってストアドプロシージャを呼び出す)

Supabaseでは、クライアントからトランザクションを制御するAPIはありません。
そのため、Supabase側でPostgreSQLの関数(=ストアドプロシージャ)を定義し、クライアントから rpc() を使って呼び出すことで、無理やりトランザクションライクな操作が実現できます。

方法

ストアドプロシージャ定義(Supabase側)

  1. 該当のプロジェクト > SQL Editorにて、ストアドプロシージャを作成(作成例は以下)
  2. 右下の「Run」を実行
  3. 左メニュー > Database > Functionにて、作成できているか確認できます

PostgreSQL関数の定義

create or replace function insert_user_and_userskill(
  _name text,
  _description text,
  _github_id text,
  _skills text[]
)
returns void
language plpgsql
as $$
declare
  new_user_id uuid;
begin
 -- ユーザー情報を users テーブルに挿入し、生成された user_id を取得
  insert into users (name, description, github_id)
  values (_name, _description, _github_id)
  returning user_id into new_user_id;

 -- スキル配列を展開して、user_skill テーブルに一括挿入
  insert into user_skill (user_id, skill)
  select new_user_id, skill from unnest(_skills) as skill;

  -- 正常終了:何も返さない
  return;
exception
 -- 途中で何らかのエラーが発生した場合、エラーメッセージ付きで例外を投げる
  when others then
    raise exception '登録処理でエラー発生: %', SQLERRM;
end;
$$;

PostgreSQLの関数は1関数=1トランザクション単位で処理されるため、明示的なbeginやcommitは不要。
unnest は配列を行に展開するSQL関数

クライアント側の呼び出し例(TypeScript)

const { error } = await supabase.rpc('insert_user_and_userskill', {
  _name: '田中一郎',
  _description: 'フロントエンドエンジニア',
  _github_id: 'tanaka123',
  _skills: ['React', 'TypeScript'],
});

if (error) {
   // → PostgreSQLのraiseで指定したエラーメッセージがここに出る
  console.error('登録エラー:', error.message);
} else {
  // → 正常終了時は error が null で、data も null
  console.log('登録成功!');
}

Supabaseクライアントの設定(Url, AnonKey)は省略

最後に

Supabaseでは、現時点では公式にトランザクションAPIがあるわけではありません。
しかしPostgreSQLのストアドプロシージャ(関数)を活用し、rpc() 経由で実行することで、Supabaseでもトランザクションっぽいことができました。

その他懸念点

  • テストやモックでの扱いがやや面倒…?
  • raiseによる例外は文字列しか返せないらしい。エラーハンドリングの際は、クライアントでの細かい設計が必要
  • rpc関数はRLSをバイパスすることがあるらしい。認可周りに要注意
  • drizzle ORM を使った方法もあるようです。全く知らないので、引き続き調査

参考

https://github.com/orgs/supabase/discussions/4562#discussioncomment-1844073
https://zenn.dev/smallstall/articles/596d3981984587

3
1
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
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?