Next.jsにPrismaを導入してみた

Learn Next.jsで実装したアプリケーションのデータ操作をPrismaで書き換えてみた。

Prismaとは

What is Prisma ORM?

  • Prisma ORM is an open-source next-generation ORM. It consists of the following parts:
  • Prisma Client: Auto-generated and type-safe query builder for Node.js & TypeScript
  • Prisma Migrate: Migration system
  • Prisma Studio: GUI to view and edit data in your database.
https://www.prisma.io/docs/orm/overview/introduction/what-is-prisma#what-is-prisma-orm

ORMとは

Object-Relational Mapping

データベースオブジェクト指向プログラミング言語の間の非互換なデータを変換するプログラミング技法である。

https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E9%96%A2%E4%BF%82%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0

業務先ではRailsを使用していて、ORMにはActive Recordを使用している。
テーブルとモデルを関連づけることで、SQLを書かずともデータを操作できるもの。そうすることで再利用可能なコードを書くことができ、保守性にもつながるメリットを持つ。とざっくり理解している。

インストール

npm install prisma --save-dev

初期設定

npx prisma init

prismaディレクトリとschema.prismaファイル爆誕!

既存のDBに接続

今回はVercelのStorage機能を使って作成したDB(Postgres)に繋ぐ

datasourceに環境変数で管理している、DBの接続先URLを指定する。

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

モデルの生成

Introspection

Prismaスキーマに現在のDBスキーマを反映したモデルを生成する。

ref: What does introspection do?
npx prisma db pull

datasourceに指定したDBに接続し、DBスキーマを読み取りモデルに変換する。

prisma/scheme.prismaにmodelが生成された!

model customers {
  id        String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
  name      String @db.VarChar(255)
  email     String @db.VarChar(255)
  image_url String @db.VarChar(255)
}

model invoices {
  id          String   @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
  customer_id String   @db.Uuid
  amount      Int
  status      String   @db.VarChar(255)
  date        DateTime @db.Date
}

model revenue {
  month   String @unique @db.VarChar(4)
  revenue Int
}

model users {
  id       String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
  name     String @db.VarChar(255)
  email    String @unique
  password String
}

リレーションの追加

モデル間のリレーションをスキーマファイルに追加していく。今回の例でいうと、customersinvoicesは1対多の関係性となる。
relation fieldsは実際のDBには存在しないがPrisma Clientで使用するために定義する必要がある。

model customers {
  id        String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
  name      String @db.VarChar(255)
  email     String @db.VarChar(255)
  image_url String @db.VarChar(255)
  invoices invoices[]
}

model invoices {
  id          String   @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
  customers customers @relation(fields: [customer_id], references: [id])
  customer_id String   
  amount      Int
  status      String   @db.VarChar(255)
  date        DateTime @db.Date
}

ref: relations

Prisma Client

npm install @prisma/client

installした@prisma/clientモジュールはnode_modules/.prisma/clientフォルダを参照している。/.prisma/clientには生成されたPrisma Clientが格納されている。

ref: Install and generate Prisma Client
prisma generate

スキーマを更新する度にprisma generateを実行する必要がある。実行することでPrisma Clientが更新される。

インスタンスの生成

Best practices for using Prisma Client with Next.jsに従い、lib/db.tsファイルを作成。
globalオブジェクトにPrismaClientのインスタンスを保存した。

import { PrismaClient } from '@prisma/client';

const prismaClientSingleton = () => {
  return new PrismaClient()
};

declare global {
  var prisma: undefined | ReturnType<typeof prismaClientSingleton>;
}

const prisma = globalThis.prisma ?? prismaClientSingleton();

export default prisma;

if (process.env.NODE_ENV !== 'production') globalThis.prisma = prisma;

Best practices for using Prisma Client with Next.jsには下記のように書いてあった。
余分なインスタンスの作成を防ぎ、コネクションプールが起きないようにするためと理解した。

In development, the command next dev clears Node.js cache on run. This in turn initializes a new PrismaClient instance each time due to hot reloading that creates a connection to the database. This can quickly exhaust the database connections as each PrismaClient instance holds its own connection pool.
開発では、next devコマンドは実行時にNode.jsキャッシュをクリアします。これは、データベースへの接続を作成するホットリロードにより、毎回新しいPrismaClientインスタンスを初期化します。これは、各PrismaClientインスタンスが独自の接続プールを保持するため、データベース接続をすぐに使い果たす可能性があります。

https://www.prisma.io/docs/orm/more/help-and-troubleshooting/help-articles/nextjs-prisma-client-dev-practices#problem

レコードを取得してみる

元々のSQLクエリで取得していたデータをPrisma Clientを使用して置き換えていく。
invoicesテーブルからamount、idcustomersテーブルからnameimage_urlemailをdateの降順で最大5件、取得している。
上のschemaで定義した通り、customer_idが外部キーとなってcustomersテーブルのidと一致するcustomersのレコードの情報を持ってきている。

SELECT invoices.amount, customers.name, customers.image_url, customers.email, invoices.id
      FROM invoices
      JOIN customers ON invoices.customer_id = customers.id
      ORDER BY invoices.date DESC
      LIMIT 5

findManyを使用して指定した条件に合うレコードのリストを取得する。
selectはどのプロパティを含めるかを指定することができる。

  const latestInvoices = await prisma.invoices.findMany({
    select: {
      amount: true,
      customers: {
        select: {
          name: true,
          image_url: true,
          email: true,
        },
      },
      id: true,
    },
    orderBy: {
      date: 'desc',
    },
    take: 5,
  });

エラー記録

PrismaClientInitializationError

PrismaClientInitializationError: Prisma has detected that this project was built on Vercel, which caches dependencies. This leads to an outdated Prisma Client because Prisma’s auto-generation isn’t triggered. To fix this, make sure to run the `prisma generate` command during the build process.

ビルド時に出たエラー。
エラー文言通りにprisma generate を追加した。

  "scripts": {
    "build": "prisma generate && next build",
...
  },

感想

ORM自体、触るのが初めてだったがドキュメントが丁寧で理解を助けてくれた。
業務先ではRailsのActiveRecordを使用していて、時々modelsファイルを見たりしていたのでとっかかりやすかった。あと、SQLクエリからPrismaで提供されているクエリに書き換える際に、SQLクエリを読むことに苦戦した。。

コメント

タイトルとURLをコピーしました