PostgreSQL トリガー関数を Prisma + Supabase でうまく使う方法

📕 新コースを公開しました。→クーポン掲載ページ

ある開発案件における、prismaでトリガー関数をうまくGitバージョン管理に含めたかったので、実践したその方法の紹介です。

  • Twitter やってますのでこちらもご覧ください →Twitter @takuxone
  • プログラミングコースも出したりしてます → コース一覧

トリガー関数をマイグレーション履歴に含めて開発が👍

Supabaseではダッシュボード上で、PostgreSQLのファンクションやトリガーを設定できます。

便利ですが、Git/Githubなどで開発を進めている場合、何もしないとソースコードに載ってこないのは不便です。

例えば開発用と本番用でデータベースを分けている場合、両方とも手動で設定しないといけないですし、

開発用などで頻繁にデータベースをリセットする場合は特に大変です。

Prismaは便利なORMサービスです。

マイグレーションもしっかりできるので、データベーススキーマ(モデル)定義は変更の都度、SQLファイルとして残っていきます。

プロジェクトでPrismaを使用している場合はこのマイグレーション履歴にトリガー関数のSQL文を含めることで、開発をスムーズに進めることが可能です。

前提

  • 今回はNext.jsのプロジェクトで使ってみたいと思いますが、Next.jsに限らず応用できる内容です。
  • DBについても今回はSupabaseですが、他でも問題ないと思います。
  • Supabaseの初期設定などについては解説しませんので、ググってみてください。

サンプルプロジェクトの準備

Next.jsはサンプルなので Create Next App で作成したいと思います。

npx create-next-app@latest --ts

prismaをインストールします。

yarn add -D prisma
yarn add @prisma/client

prismaを初期化します。

npx prisma init

プロジェクトに prisma ディレクトリが作成されます。

image.png

supabaseとの接続は詳細は省きますが、.envや.env.localなどのファイルに下記のように記載します。

それぞれの項目はご自身のsupabaseの情報に置き換えてください。

DATABASE_URL=xxx
SUPABASE_URL=xxx
SUPABASE_KEY=xxx

Prismaスキーマ定義

prismaディレクトリの中のshema.prismaファイルにモデルを定義します。

今回はシンプルに下記のような例で定義します。

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model post {
  id    Int    @id @default(autoincrement())
  title String
}

model notification {
  id    Int    @id @default(autoincrement())
  title String
}

postテーブルは投稿を、 notificationテーブルはトリガー関数が実行された時に挿入される役目とします。

はじめのマイグレーション

それではまずはここで初めのマイグレーションを行なってみます。

npx prisma migrate dev

マイグレーション履歴の名前(フォルダ名)を求められますが、ここでは first_migration と入力しておきます。入力せずにエンターでも大丈夫です。

完了すると、次のように migrations フォルダや sql文が記載されたファイル(migration.sql)などが作成されます。

image.png

PrismaStudioを立ち上げてテーブルを確認してみます。

npx prisma studio
image.png

テーブルができています。

一応supabaseダッシュボードからも確認すると次のように作成されています。

image.png

サンプルとして投稿機能を実装

簡単に投稿機能をつけてみます。

pages/api 直下に posts.ts ファイルを作成し、postを作成するエンドポイントとします。

import { PrismaClient } from "@prisma/client";
import { NextApiRequest, NextApiResponse } from "next";

const prisma = new PrismaClient();

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method === "POST") {
    const post = await prisma.post.create({
      data: {
        title: req.body.postTitle,
      },
    });
    if (!post) throw Error("not created");
    res.status(200).json(post);
  } else {
    res.setHeader("Allow", "POST");
    res.status(405).end("Method Not Allowed");
  }
}

次にpages直下のindex.tsxファイルを次のようにします。

import type { NextPage } from "next";

const Home: NextPage = () => {
  const handleSubmit: React.FormEventHandler<HTMLFormElement> = async (e) => {
    e.preventDefault();
    const response = await fetch("/api/posts", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        postTitle: e.currentTarget.post_title.value,
      }),
    });
    const createdPost = await response.json();
    console.log(createdPost);
  };
  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input type="text" name="post_title" placeholder="post title" />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
};

export default Home;

この状態で yarn dev でサーバーを立ち上げて確認してみます。

見にくいですがフォームが設置できています。

image.png

試しに TITLE 1 と入力して送信してみます。

うまくいけばコンソールに作成された post が表示されます。

image.png

Prisma Studio でも確認しておきます。

image.png

トリガー関数をマイグレーション履歴に含める

ここから本題のトリガー関数の作成です。

今回はサンプルとして、postテーブルに新しくレコードが挿入(INSERT)されたら、それをトリガーとして notificationテーブルにもレコードを挿入するようにします。

トリガー関数は shema.prisma に記載するわけではないので、sqlファイルを用意する必要があります。

prismaでは次のようにするとファイルのみ生成可能です。

npx prisma migrate dev --create-only

またまた名前を聞かれるので仮に trigger_function と入力しておきます。

image.png

xxx_trigger_function フォルダの中の migration.sql ファイルを編集します。

create or replace function public.insert_notification()
  returns trigger as $$
begin
  insert into public.notification(title) 
  values (new.title || ' was created');
  return new;
end;
$$ language plpgsql;

create trigger on_insert_post
  after insert
  on public.post
  for each row
  execute procedure public.insert_notification();
  • 関数名: insert_notification
  • トリガー名: on_insert_post

上記は post テーブルに新しくレコードが挿入された場合に発動し、

notificationテーブルにもレコードを生成します。

このとき、postレコードの title フィールドの値を使用して、

[その挿入されるレコードのタイトル] + " was created" という文字列とし、

notificationレコードのタイトルとします。

それではこれを適用するためにマイグレートを行います。

npx prisma migrate dev

supabaseを確認すると、Database > Functions のところに insert_notification という関数が作成されています。トリガーについても同様に作成されています。

image.png

では想定通り機能するか、試してみます。

まずはトップページで今度は TITLE 2 と入力して送信してみます。

image.png

これは通常通り postに追加されています。

image.png

合わせてトリガー関数がうまくいっているか、notificationテーブルを確認します。

image.png

notificationにもしっかりとデータが登録されていることが確認できました。

PrismaとSupabaseでトリガー関数を使う方法については以上です!

🎓✍️コース一覧

プログラミング関係のビデオコースを提供しています。クーポンも発行していますので、ぜひ一度チェックしてみてください。

Twitter @takumafujimoto

記事を読んでいただきありがとうございます。ツイッターではプログラミング以外についてや、たまにクーポン情報もツイートしたり。。。ツイッターでもお待ちしてます。