メインコンテンツへスキップ

DrizzleからPrisma ORMへの移行方法

15分

はじめに

このガイドでは、アプリケーションをDrizzleからPrisma ORMに移行する方法を説明します。移行手順を示すために、Drizzle Next.jsの例に基づいたサンプルプロジェクトを使用します。このガイドで使用されている例はGitHubにあります。

Prisma ORMとDrizzleの比較については、Prisma ORM vs Drizzleのページをご覧ください。

前提条件

このガイドを開始する前に、以下を確認してください

  • 移行したいDrizzleプロジェクト
  • Node.js がインストールされていること(バージョン16以上)
  • PostgreSQLまたはその他のサポートされているデータベース
  • DrizzleとNext.jsの基本的な知識
注意

この移行ガイドでは、Neon PostgreSQLを例示データベースとして使用していますが、Prisma ORMでサポートされている他のリレーショナルデータベースにも同様に適用できます。

Prisma ORMとDrizzleの比較については、Prisma ORM vs Drizzleのページをご覧ください。

移行プロセスの概要

DrizzleからPrisma ORMへの移行手順は、アプリケーションやAPIレイヤーの種類に関わらず常に同じであることに注意してください

  1. Prisma CLIをインストールする
  2. データベースをイントロスペクションする
  3. ベースライン移行を作成する
  4. Prisma Clientをインストールする
  5. DrizzleクエリをPrisma Clientで徐々に置き換える

これらの手順は、REST API(例:Express、koa、NestJS)、GraphQL API(例:Apollo Server、TypeGraphQL、Nexus)、またはデータベースアクセスにDrizzleを使用するその他の種類のアプリケーションを構築している場合でも適用されます。

Prisma ORMは、段階的な導入に非常に適しています。つまり、DrizzleからPrisma ORMへのプロジェクト全体の移行を一度に行う必要はなく、データベースクエリをDrizzleからPrisma ORMに段階的に移行できます。

ステップ1. Prisma CLIをインストールする

Prisma ORMを採用する最初のステップは、プロジェクトにPrisma CLIをインストールすることです

npm install prisma --save-dev

ステップ2. データベースをイントロスペクションする

2.1. Prisma ORMを設定する

データベースをイントロスペクションする前に、Prismaスキーマを設定し、Prismaをデータベースに接続する必要があります。プロジェクトのルートで次のコマンドを実行して、基本的なPrismaスキーマファイルを作成します

npx prisma init --output ../generated/prisma

このコマンドは、prismaという新しいディレクトリを作成し、以下のファイルを生成します

  • schema.prisma:データベース接続とモデルを指定するPrismaスキーマ
  • .env:データベース接続URLを環境変数として構成するdotenv
注意

すでに.envファイルが存在する場合があります。その場合、prisma initコマンドは新しいファイルを作成するのではなく、行を追加します。

Prismaスキーマは現在次のようになっています

prisma/schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

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

generator client {
provider = "prisma-client-js"
}
ヒント

VS Codeを使用している場合は、構文の強調表示、フォーマット、オートコンプリート、およびその他多くの便利な機能のために、Prisma VS Code拡張機能をインストールしてください。

2.2. データベースに接続する

PostgreSQLを使用していない場合は、datasourceブロックのproviderフィールドを現在使用しているデータベースに合わせて調整する必要があります

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

それが完了したら、.envファイルでデータベース接続URLを設定できます。DrizzleとPrisma ORMは接続URLに同じ形式を使用しているため、既存の接続URLは問題なく動作するはずです。

2.3. Prisma ORMを使用してデータベースをイントロスペクションする

接続URLが設定されたら、データベースをイントロスペクションしてPrismaモデルを生成できます

npx prisma db pull

サンプルプロジェクトを使用している場合、次のモデルが作成されます

prisma/schema.prisma
model todo {
id Int @id
text String
done Boolean @default(false)
}

生成されたPrismaモデルはデータベーステーブルを表します。Prismaモデルは、データベースにクエリを送信できるプログラム的なPrisma Client APIの基盤です。

2.4. ベースライン移行を作成する

Prisma Migrateを使用してデータベーススキーマを進化させ続けるには、データベースをベースライン化する必要があります。

まず、migrationsディレクトリを作成し、その中に移行用の任意の名前のディレクトリを追加します。この例では、移行名として0_initを使用します

mkdir -p prisma/migrations/0_init

次に、prisma migrate diffで移行ファイルを生成します。次の引数を使用します

  • --from-empty:移行元のデータモデルが空であることを前提とします
  • --to-schema-datamodel:datasourceブロックのURLを使用した現在のデータベース状態
  • --script:SQLスクリプトを出力する
npx prisma migrate diff --from-empty --to-schema-datamodel prisma/schema.prisma --script > prisma/migrations/0_init/migration.sql

生成された移行を確認して、すべてが正しいことを確認してください。

次に、prisma migrate resolve--applied引数を使用して、移行を適用済みとしてマークします。

npx prisma migrate resolve --applied 0_init

このコマンドは、_prisma_migrationsテーブルに追加することにより、0_initを適用済みとしてマークします。

これで、現在のデータベーススキーマのベースラインができました。データベーススキーマにさらに変更を加えるには、Prismaスキーマを更新し、prisma migrate devを使用して変更をデータベースに適用できます。

2.5. Prismaスキーマを調整する(オプション)

イントロスペクションによって生成されたモデルは、現在、データベーステーブルに正確にマッピングされています。このセクションでは、Prismaモデルの名前を調整して、Prisma ORMの命名規則に準拠させる方法を学びます。

これらの調整はすべて完全にオプションであり、今のところ何も調整したくない場合は、次のステップにスキップしてもかまいません。後でいつでも戻って調整を行うことができます。

Drizzleモデルの現在のcamelCase表記とは対照的に、Prisma ORMの命名規則は次のとおりです

  • モデル名にはPascalCase
  • フィールド名にはcamelCase

@@mapおよび@mapを使用して、Prismaモデルとフィールド名を基になるデータベースの既存のテーブル名と列名にマッピングすることにより、名前を調整できます。

上記のモデルを変更する方法の例を次に示します

prisma/schema.prisma
model Todo {
id Int @id
text String
done Boolean @default(false)

@@map("todo")
}

ステップ3. Prisma Clientをインストールして生成する

次のステップとして、Prisma Clientをプロジェクトにインストールして、現在Drizzleで行われているプロジェクト内のデータベースクエリの置き換えを開始できます

npm install @prisma/client

インストール後、スキーマをTypeScriptの型とオートコンプリートに反映させるために、generateを実行する必要があります。

npx prisma generate

ステップ4. DrizzleクエリをPrisma Clientで置き換える

このセクションでは、サンプルREST APIプロジェクトのサンプルルートに基づいて、DrizzleからPrisma Clientに移行されるいくつかのサンプルクエリを示します。Prisma Client APIがDrizzleとどのように異なるかの包括的な概要については、比較ページを確認してください。

まず、さまざまなルートハンドラーからデータベースクエリを送信するために使用するPrismaClientインスタンスを設定します。dbディレクトリにprisma.tsという名前の新しいファイルを作成します

touch db/prisma.ts

次に、PrismaClientをインスタンス化し、後でルートハンドラーで使用できるようにファイルからエクスポートします

db/prisma.ts
import { PrismaClient } from '@prisma/client'

export const prisma = new PrismaClient()

4.1. getDataクエリの置き換え

フルスタックNext.jsアプリには、getDataを含むいくつかのアクションがあります。

getDataアクションは現在次のように実装されています

actions/todoActions.ts
import db from "@/db/drizzle";
import { todo } from "@/db/schema";

export const getData = async () => {
const data = await db.select().from(todo);
return data;
};

Prisma Clientを使用して実装された同じアクションを次に示します

src/controllers/FeedAction.ts
import { prisma } from "@/db/prisma";

export const getData = async () => {
const data = await prisma.todo.findMany();
return data;
};

4.2. POSTリクエストでのクエリの置き換え

サンプルプロジェクトには、POSTリクエスト中に使用される4つのアクションがあります

  • addTodo:新しいTodoレコードを作成します
  • deleteTodo:既存のTodoレコードを削除します
  • toggleTodo:既存のTodoレコードのブール値doneフィールドを切り替えます
  • editTodo:既存のTodoレコードのtextフィールドを編集します

addTodo

addTodoアクションは現在次のように実装されています

actions/todoActions.ts
import { revalidatePath } from "next/cache";

import db from "@/db/drizzle";
import { todo } from "@/db/schema";

export const addTodo = async (id: number, text: string) => {
await db.insert(todo).values({
id: id,
text: text,
});
revalidatePath("/");
};

Prisma Clientを使用して実装された同じアクションを次に示します

actions/todoActions.ts
import { revalidatePath } from "next/cache";

import { prisma } from "@/db/prisma";

export const addTodo = async (id: number, text: string) => {
await prisma.todo.create({
data: { id, text },
})
revalidatePath("/");
};

deleteTodo

deleteTodoアクションは現在次のように実装されています

actions/todoActions.ts
import { eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";

import db from "@/db/drizzle";
import { todo } from "@/db/schema";

export const deleteTodo = async (id: number) => {
await db.delete(todo).where(eq(todo.id, id));
revalidatePath("/");
};

Prisma Clientを使用して実装された同じアクションを次に示します

actions/todoActions.ts
import { revalidatePath } from "next/cache";

import { prisma } from "@/db/prisma";

export const deleteTodo = async (id: number) => {
await prisma.todo.delete({ where: { id } });
revalidatePath("/");
};

toggleTodo

ToggleTodoアクションは現在次のように実装されています

actions/todoActions.ts
import { eq, not } from "drizzle-orm";
import { revalidatePath } from "next/cache";

import db from "@/db/drizzle";
import { todo } from "@/db/schema";

export const toggleTodo = async (id: number) => {
await db
.update(todo)
.set({
done: not(todo.done),
})
.where(eq(todo.id, id));
revalidatePath("/");
};

Prisma Clientを使用して実装された同じアクションを次に示します

actions/todoActions.ts
import { revalidatePath } from "next/cache";

import { prisma } from "@/db/prisma";

export const toggleTodo = async (id: number) => {
const todo = await prisma.todo.findUnique({ where: { id } });
if (todo) {
await prisma.todo.update({
where: { id: todo.id },
data: { done: !todo.done },
})
revalidatePath("/");
}
};

Prisma ORMにはブールフィールドをインプレースで編集する機能がないため、レコードを事前にフェッチする必要があることに注意してください。

editTodo

editTodoアクションは現在次のように実装されています

actions/todoActions.ts
import { eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";

import db from "@/db/drizzle";
import { todo } from "@/db/schema";

export const editTodo = async (id: number, text: string) => {
await db
.update(todo)
.set({
text: text,
})
.where(eq(todo.id, id));

revalidatePath("/");
};

Prisma Clientを使用して実装された同じアクションを次に示します

actions/todoActions.ts
import { revalidatePath } from "next/cache";

import { prisma } from "@/db/prisma";

export const editTodo = async (id: number, text: string) => {
await prisma.todo.update({
where: { id },
data: { text },
})
revalidatePath("/");
};

さらに詳しく

暗黙的な多対多リレーション

Drizzleとは異なり、Prisma ORMでは多対多リレーションを暗黙的にモデル化できます。つまり、スキーマでリレーションテーブル(JOINテーブルとも呼ばれます)を明示的に管理する必要がない多対多リレーションです。DrizzleとPrisma ORMを比較した例を次に示します

schema.ts
import { boolean, integer, pgTable, serial, text } from "drizzle-orm/pg-core";

export const posts = pgTable('post', {
id: serial('serial').primaryKey(),
title: text('title').notNull(),
content: text('content'),
published: boolean('published').default(false).notNull(),
});

export const categories = pgTable('category', {
id: serial('serial').primaryKey(),
name: text('name').notNull(),
});

export const postsToCategories = pgTable('posts_to_categories', {
postId: integer('post_id').notNull().references(() => users.id),
categoryId: integer('category_id').notNull().references(() => chatGroups.id),
});

このスキーマは、次のPrismaスキーマと同等です

schema.prisma
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
postsToCategories PostToCategories[]

@@map("post")
}

model Category {
id Int @id @default(autoincrement())
name String
postsToCategories PostToCategories[]

@@map("category")
}

model PostToCategories {
postId Int
categoryId Int
category Category @relation(fields: [categoryId], references: [id])
post Post @relation(fields: [postId], references: [id])

@@id([postId, categoryId])
@@index([postId])
@@index([categoryId])
@@map("posts_to_categories")
}

このPrismaスキーマでは、多対多リレーションは、リレーションテーブルPostToCategoriesを介して明示的にモデル化されています。

代わりにPrisma ORMリレーションテーブルの規則に従うことで、リレーションは次のようになります

schema.prisma
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
categories Category[]
}

model Category {
id Int @id @default(autoincrement())
name String
posts Post[]
}

これにより、このリレーションのレコードを変更するための、より人間工学的で冗長性の少ないPrisma Client APIも実現します。PostToCategoriesモデルを最初にトラバースする必要がなく、PostからCategoryへの直接パス(およびその逆)があるためです。

警告

データベースプロバイダーがテーブルに主キーを要求する場合、明示的な構文を使用し、主キーを持つ結合モデルを手動で作成する必要があります。これは、暗黙的な構文を使用して多対多リレーション用にPrisma ORM(@relationを介して表現)によって作成されたリレーションテーブル(JOINテーブル)には主キーがないためです。


Prismaとのつながりを保つ

以下とつながってPrismaの旅を続けましょう 活発なコミュニティ。常に情報を入手し、参加し、他の開発者と協力しましょう

  • Xでフォローする お知らせ、ライブイベント、役立つヒントをお届けします。
  • Discordに参加する 質問をしたり、コミュニティと話したり、会話を通じて積極的なサポートを得ることができます。
  • YouTubeで購読する チュートリアル、デモ、ストリームをお届けします。
  • GitHubで交流する リポジトリにスターを付けたり、問題を報告したり、問題に貢献したりできます。
私たちはあなたの参加を心から大切にし、コミュニティの一員としてあなたをお迎えできることを楽しみにしています!