DrizzleからPrisma ORMへの移行方法
はじめに
このガイドでは、DrizzleからPrisma ORMにアプリケーションを移行する方法を説明します。Drizzle Next.jsのサンプルをベースにしたサンプルプロジェクトを使用して、移行手順を説明します。このガイドで使用されているサンプルはGitHubで確認できます。
Prisma ORM vs Drizzleページで、Prisma ORMとDrizzleの比較について学ぶことができます。
前提条件
このガイドを始める前に、以下を確認してください。
- 移行したいDrizzleプロジェクト
- Node.jsがインストールされていること (バージョン16以上)
- PostgreSQLまたはその他のサポートされているデータベース
- DrizzleとNext.jsの基本的な知識
この移行ガイドでは、Neon PostgreSQLを例のデータベースとして使用していますが、Prisma ORMでサポートされている他のリレーショナルデータベースにも同様に適用できます。
Prisma ORM vs Drizzleページで、Prisma ORMとDrizzleの比較について学ぶことができます。
移行プロセスの概要
DrizzleからPrisma ORMへの移行手順は、どのような種類のアプリケーションやAPIレイヤーを構築しているかに関わらず、常に同じであることに注意してください。
- Prisma CLIのインストール
- データベースのイントロスペクション
- ベースラインマイグレーションの作成
- Prisma Clientのインストール
- 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 && npm install @prisma/client
ステップ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スキーマは以下のようになります。
// 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`フィールドを現在使用しているデータベースに合わせて調整する必要があります。
- PostgreSQL
- MySQL
- Microsoft SQL Server
- SQLite
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
datasource db {
provider = "sqlserver"
url = env("DATABASE_URL")
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
これが完了したら、`.env`ファイルでデータベース接続URLを設定できます。DrizzleとPrisma ORMは接続URLに同じ形式を使用するため、既存の接続URLで問題なく動作するはずです。
2.3. Prisma ORMを使用したデータベースのイントロスペクション
接続URLが設定されたら、データベースをイントロスペクトしてPrismaモデルを生成できます。
npx prisma db pull
サンプルプロジェクトを使用している場合、以下のモデルが作成されます。
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
このコマンドは、`0_init`を`_prisma_migrations`テーブルに追加することで、適用済みとしてマークします。
これで、現在のデータベーススキーマのベースラインができました。データベーススキーマにさらに変更を加えるには、Prismaスキーマを更新し、`prisma migrate dev`を使用して変更をデータベースに適用できます。
2.5. Prismaスキーマの調整 (オプション)
イントロスペクションによって生成されたモデルは、現在、データベーステーブルに**正確に**マッピングされます。このセクションでは、Prisma ORMの命名規則に準拠するようにPrismaモデルの命名を調整する方法を学びます。
これらの調整はすべて完全にオプションであり、現時点では何も調整したくない場合は、次のステップに進んでも構いません。後でいつでも戻って調整を行うことができます。
Drizzleモデルの現在のcamelCase表記とは対照的に、Prisma ORMの命名規則は以下のとおりです。
- モデル名にはPascalCase
- フィールド名にはcamelCase
`@@map`と`@map`を使用して、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`をインスタンス化し、後でルートハンドラーで使用できるようにファイルからエクスポートします。
import { PrismaClient } from '@prisma/client'
export const prisma = new PrismaClient()
4.1. `getData`クエリの置き換え
フルスタックNext.jsアプリには、`getData`を含むいくつかの`actions`があります。
`getData`アクションは現在、以下のように実装されています。
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を使用して実装された同じアクションを示します。
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`アクションは現在、以下のように実装されています。
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を使用して実装された同じアクションを示します。
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`アクションは現在、以下のように実装されています。
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を使用して実装された同じアクションを示します。
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`アクションは現在、以下のように実装されています。
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を使用して実装された同じアクションを示します。
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`アクションは現在、以下のように実装されています。
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を使用して実装された同じアクションを示します。
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を比較した例を次に示します。
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スキーマと同等です。
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のリレーションテーブルの規約に従うと、リレーションは以下のようになります。
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[]
}
これにより、`PostToCategories`モデルをまず横断する必要がなく、`Post`から`Category`へ(そしてその逆も)直接パスがあるため、このリレーションのレコードを変更するためのPrisma Client APIがより人間工学的に優れ、冗長でなくなります。
データベースプロバイダーがテーブルにプライマリキーを要求する場合、明示的な構文を使用し、プライマリキーを持つ結合モデルを手動で作成する必要があります。これは、Prisma ORM(`@relation`で表現される)によって暗黙的な構文で作成される多対多リレーションのリレーションテーブル(結合テーブル)にはプライマリキーがないためです。
Prismaとつながり続ける
Prismaの旅を続けるには、以下とつながりましょう 私たちの活発なコミュニティ。情報を入手し、参加し、他の開発者と協力しましょう
- Xでフォローする アナウンス、ライブイベント、役立つヒントについて。
- Discordに参加する 質問したり、コミュニティと話したり、会話を通じて積極的にサポートを受けたりするために。
- YouTubeを購読する チュートリアル、デモ、ストリームについて。
- GitHubで関わる リポジトリをスターしたり、問題を報告したり、問題に貢献したりすることで。