【Supabase】サインアップ時に別テーブルにもデータを保存する方法

【Supabase】サインアップ時に別テーブルにもデータを保存する方法

投稿日: 2024年10月27日

Tips
要約
  • Supabaseを使用してユーザー認証機能を実装し、emailとpasswordに加え、ニックネームをownersテーブルに保存する手法を解説。
  • ユーザーがサインアップする際、SupabaseのsignUpメソッドのoptionsでnicknameを含め、auth.usersテーブルとownersテーブルを外部キーで紐付ける。
  • トリガーを設定してauth.usersテーブルの新規レコード追加時にownersテーブルにデータを保存する関数を実行する方法。

はじめに

Supabaseを用いてユーザー認証機能を実装する時、emailやpassword以外の情報も保存したいと思う時ないですか?

例えば、ユーザーのニックネームを保存したり、プロフィール画像を保存したり。

僕のオリジナルアプリの場合は、ユーザーのemailとpasswordに加え、ニックネームを一緒に保存する想定でテーブル設計をしていました。

しかし、公式サイトの情報が古かったり、英語の記事しか出てこなかったりと時間をかなり溶かしたので、今後実装される方の手助けになればと思い、備忘録もかねて実装方法をまとめていきます。

この記事の対象者

  • Supabaseでサインアップの実装が完了している方(emailとpasswordで認証ができていればOK)
  • 英語が苦手で公式サイトやstackflowで英語読むの疲れたよって方
  • サインアップ時に、email,password以外の情報をデータベース保存したい方

前提

  • Next.jsでSupabaseでバックエンドを構築していること。
  • emailとpasswordを用いてSignUp機能が作成できていること。

実装したこと

まず僕が実装した内容を簡単に説明します。

DB設計当初は、ユーザー情報を保存するためにownersテーブルを作ることを想定していました。

model Owner {
  id            String      @id @default(uuid()) @db.Uuid
  nickname      String
  email         String
  password      String
  createdAt     DateTime    @default(now())
  updatedAt     DateTime    @updatedAt

  @@map("owners")
}

このOwnerテーブルをSupabaseのauth.usersテーブルに置き換えればいいかーくらいに思っていたのですが、ownersテーブルはnicknameを保存するのみ、あとのemailやpasswordはAuth.users側で管理する実装ができると知って、2つのテーブルに分けて保存する方針にしました。

↓このようなイメージです

実装手順

auth.usersテーブルに対してデータ保存された時に、一緒にownersテーブルに対してもデータ保存するにはトリガーという機能を使います。

トリガーは簡単に言うと、「特定のイベントが実行された時に、関数を発火させるためのスイッチ」みたいなものだと思っていただくとよいかなとおもいます。

auth.usersにレコードが追加された際に、関数実行する」という設定を作っていきます。

実装手順は大きく分けて4工程です。

  1. signUp()メソッドでnicknameの情報を渡す
  2. auth.usersテーブルとownersテーブルを外部キーで紐づける
  3. ownersテーブルに書き込みするfunctionを作る
  4. auth.usersテーブルにレコード追加がされたら、2.のfunctionが実行されるように設定する

1. signUpメソッドでnicknameの情報を渡す

まずsingUpする時に入力されたemail, password以外の情報を取得する必要があります。

その場合は、SupabaseのsignUpメソッドでoptionを設定します。こうすることで、JSON形式で追加情報を送ることができます。

僕の場合は、以下のようにnicknameカラムを設定しました。

const { data, error } = await supabase.auth.signUp({
  email: 'example@email.com',
  password: 'example-password',
  options: {
    data: {
      nickname: 'John', // 追加したい項目を記載
    },
  },
})

option設定はこちらも合わせて参照してみてください。

2. auth.usersテーブルとownersテーブルを外部キーで紐づける

次に、ownersテーブルのidカラムとauth.usersテーブルのidカラムを紐付けします。

auth.usersテーブルにもownersテーブルにもユーザー情報が複数登録されていく想定なので、auth.usersテーブルのユーザー情報がどのownersテーブルの情報と紐づくかを管理しておく必要があります。

idカラム同士を紐付けしておくことで、同じidを持つ = 同一ユーザーであると判別できるようになります。

2-1. Tableを編集する

Supabaseのdashboardを開き、サイドバーの「Table Editor」をクリックします。
その後変更したいテーブル名の右横にある「3点リーダー」> 「Edit Table」の順に選択してください。

2-2. idカラムに紐付け設定をする。

「Edit Table」をクリックすると、以下のような画面が表示されます。
真ん中あたりにあるColumnからidカラムを選択し、リンクのアイコンをクリックします。

リンクをクリックすると、紐づけるテーブルとカラムを選択できます。
画像と同じように設定してください。

2-3. オプション設定をする

画面下部にある「Action if referenced row removed」で「Cascade」を選択します。
この設定をすることで、auth.usersのレコードが削除された場合は、関連するownersテーブルのデータも一緒に削除することができます。

最後にSaveボタンを押して保存をしてください。

これでownersテーブルのidカラムに、auth.usersテーブルのidカラムを紐付けが完了です。

3. ownersテーブルに書き込みするfunctionを作る

続いて、ownersテーブルにnicknameカラムを書き込みするためのfunction(関数)を作ります。

こちらの動画のように、SQLで作成することもできますが、今回はdashboardから作成します。

3-1. functionを作成する

dashboardのサイドバーにある「Functions」をクリックします。
すると設定画面が表示されるので、schemaで「public」を選択した状態で「Create a new function」をクリックします。

以下の画面でfunctionの作成をしていきます。画像のように設定してください。

  • Name of function
    「関数名」を記載する箇所です。任意で名前を付けられます。
  • Return type
    関数の戻り値を設定する箇所です。「trigger」を選択します。
  • Definition
    実行する関数を記載する箇所です。以下のように記載してください。
begin
  insert into public.テーブル名 (カラム名1, カラム名2) //保存したいテーブル名とカラム名を記載する
  values (
    new.id,
    new.raw_user_meta_data->>'保存したいカラム名',
    );
  return new;
end;

newとは?

valuesの値にnew.idnew.raw_user_meta_dataの記述があります。
このnewは、新しくデータベースにデータ保存、更新、削除された際の新しいレコードを指します。

つまり、今回はauth.usersのテーブルにデータ保存がされた際に、このfunctionが実行されるためnew.idでauth.usersテーブルのidカラムのデータを取得できます。

また、new.raw_user_meta_dataは、Supabaseのauth.users テーブルで使用されるフィールドの1つです。

このフィールドには、ユーザーに関連する追加情報を保存するためのJSONオブジェクトが含まれていて、メタデータやカスタムデータが格納されています。

{
	"sub": "uuid形式のデータ",
	"email": "example@email.com",
	"nickname": "John",
	"email_verified": false,
	"phone_verified": false
}

なお、このraw_user_meta_dataには、signUpメソッドで定義したoptionsのdataオブジェクトが含まれます。

const { data, error } = await supabase.auth.signUp({
  email: 'example@email.com',
  password: 'example-password',
  options: {
    data: {      
      nickname: 'John',
    },
  },
})

3-2. Securityの設定をする

画面下部の「Type of Security」で「SECURITY DEFINER」を設定します。
この設定をすることで、この関数を作成したユーザーの権限を使って関数実行が可能になります。

4.auth.usersテーブルにレコード追加がされたら、2.のfunctionが実行されるように設定する

最後にトリガーの作成です。トリガー設定はSQL Editから実行しましょう。

4-1. triggerの作成をする

dashboardのサイドバーから「SQL Editor」をクリックします。
エディタが開くので、画像のように記述をします。

create trigger トリガー名 // ①任意のトリガー名
after insert on テーブル名 for each row // ②イベント対象になるテーブル。今回はauth.users
execute function 関数名 // ③2.の工程で作成した関数名を記載する

上記のように記載をしたら、画面の右下の「Run」ボタンをクリックします。
クリックすることで新規トリガーが作成されます。

4-2. トリガーの作成を確認する

dashboardの「Database」から「Trigger」を選択する。
schemaで「auth」をプルダウンから選択すると、先ほど作成したtriggerが確認できます。

挙動確認

ここまでの設定ができたら、アプリからユーザー登録をしましょう。
以下のようにauth.usersテーブルに紐づけたテーブルにもデータが保存されていることが確認できればOKです。

注意事項

この実装をする上でハマったエラーを踏まえて、いくつか注意事項があるのでまとめておきます!

1. idカラムはuuid型にしておくこと

最初にエラーにハマったのは、ownersテーブルのPrimary KeyをString型で設定していたことです。

Supabaseはuuidでidカラムが保存されるので、String型で設定しておけばOK!かと思いきや、NGでした。

String型のままだと「2. auth.usersテーブルとownersテーブルを外部キーで紐づける」の行程で、auth.usersテーブルのidカラムとの紐付けができません。(型が違うからだめだよ!と怒られます)

回避するために、schema.prismaでしっかりとidカラムをuuid型で指定しておきましょう。

model Owner {
  id            String      @id @default(uuid()) @db.Uuid  ⇦ここ大事。
  nickname      String
  email         String
  password      String
  createdAt     DateTime    @default(now())
  updatedAt     DateTime    @updatedAt

  @@map("owners")
}

2. テーブル名に注意する

schema.prismaでmodel名を大文字スタートで記載した場合、そのモデル名がそのままSupabaseのデータベースに反映されてしまいます。

本来テーブル名は、小文字でスネークケース、かつ複数形で記載するのが慣例なので、@@mapを使用してテーブル名を変換しておきましょう。

model Owner {
  id            String      @id @default(uuid()) @db.Uuid
  nickname      String
  email         String
  password      String
  createdAt     DateTime    @default(now())
  updatedAt     DateTime    @updatedAt

  @@map("owners")  ⇦@@mapを活用して、テーブル名を変換
}

PosgreSQLはテーブル名やカラム名の大文字、小文字を区別せtずに全て小文字として処理します。

そのため、この変換をしておかないと、functionを実行してもテーブル名が見つからず、データ保存ができません。

ここで僕は時間をすごく溶かしたので、テーブル名の命名には注意しておきましょう!

begin
  insert into public.Owner (id, nickname)  ⇦@@map変換しないと、Ownerownerに変換してテーブル検索をしてしまう
  values (
    new.id,
    new.raw_user_meta_data->>'nickname',
    );
  return new;
end;

さいごに

長々と書いてしまいましたが、実装手順は上記の通りです。

細かい設定の部分(RLSやポリシーなど)は調べきれていないところが多々ありますが、よかったら参考にしてみてください。

シェア!

Threads
記事一覧に戻る
Threads
0