【Supabase】サインアップ時に別テーブルにもデータを保存する方法
投稿日: 2024年10月27日
Supabaseを用いてユーザー認証機能を実装する時、emailやpassword以外の情報も保存したいと思う時ないですか?
例えば、ユーザーのニックネームを保存したり、プロフィール画像を保存したり。
僕のオリジナルアプリの場合は、ユーザーのemailとpasswordに加え、ニックネームを一緒に保存する想定でテーブル設計をしていました。
しかし、公式サイトの情報が古かったり、英語の記事しか出てこなかったりと時間をかなり溶かしたので、今後実装される方の手助けになればと思い、備忘録もかねて実装方法をまとめていきます。
まず僕が実装した内容を簡単に説明します。
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工程です。
まず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設定はこちらも合わせて参照してみてください。
次に、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カラムを紐付けが完了です。
続いて、ownersテーブルにnicknameカラムを書き込みするためのfunction(関数)を作ります。
こちらの動画のように、SQLで作成することもできますが、今回はdashboardから作成します。
3-1. functionを作成する
dashboardのサイドバーにある「Functions」をクリックします。
すると設定画面が表示されるので、schemaで「public」を選択した状態で「Create a new function」をクリックします。
以下の画面でfunctionの作成をしていきます。画像のように設定してください。
begin
insert into public.テーブル名 (カラム名1, カラム名2) //保存したいテーブル名とカラム名を記載する
values (
new.id,
new.raw_user_meta_data->>'保存したいカラム名',
);
return new;
end;
newとは?
valuesの値にnew.id
やnew.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」を設定します。
この設定をすることで、この関数を作成したユーザーの権限を使って関数実行が可能になります。
最後にトリガーの作成です。トリガー設定は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です。
この実装をする上でハマったエラーを踏まえて、いくつか注意事項があるのでまとめておきます!
最初にエラーにハマったのは、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")
}
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変換しないと、Ownerをownerに変換してテーブル検索をしてしまう
values (
new.id,
new.raw_user_meta_data->>'nickname',
);
return new;
end;
長々と書いてしまいましたが、実装手順は上記の通りです。
細かい設定の部分(RLSやポリシーなど)は調べきれていないところが多々ありますが、よかったら参考にしてみてください。