Drizzle からの移行
このガイドでは、Drizzle から Prisma ORM への移行方法について説明します。このガイドでは、Drizzle Next.js の例 をベースにしたサンプルプロジェクトを、移行手順を示すサンプルプロジェクトとして使用しています。このガイドで使用されているサンプルは、GitHub で確認できます。
この移行ガイドでは、例として Neon PostgreSQL をデータベースとして使用していますが、Prisma ORM でサポートされている他のリレーショナルデータベースにも同様に適用できます。
Prisma ORM と Drizzle の比較については、Prisma ORM vs 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
ステップ 2. データベースをイントロスペクトする
2.1. Prisma ORM を設定する
データベースをイントロスペクトする前に、Prisma スキーマを設定し、Prisma をデータベースに接続する必要があります。プロジェクトのルートで次のコマンドを実行して、基本的な Prisma スキーマファイルを作成します。
npx prisma init
このコマンドにより、次のファイルを含む 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
このコマンドは、_prisma_migrations
テーブルに追加することで 0_init
を適用済みとしてマークします。
これで、現在のデータベーススキーマのベースラインができました。データベーススキーマにさらに変更を加えるには、Prisma スキーマを更新し、prisma migrate dev
を使用してデータベースに変更を適用できます。
2.5. Prismaスキーマの調整(オプション)
イントロスペクションによって生成されたモデルは現在、データベースのテーブルと正確に対応しています。このセクションでは、Prisma ORMの命名規則に従うようにPrismaモデルの名前を調整する方法を学びます。
これらの調整はすべて完全にオプションであり、現時点で何も調整したくない場合は、次のステップに進んでもかまいません。後でいつでも戻って調整を行うことができます。
Drizzleモデルの現在のcamelCase表記とは対照的に、Prisma ORMの命名規則は次のとおりです。
- モデル名にはPascalCase
- フィールド名にはcamelCase
Prismaモデルとフィールドの名前を、@@map
および@map
を使用して、基盤となるデータベースの既存のテーブル名と列名にマッピングすることで、名前を調整できます。
上記のモデルを変更する方法の例を次に示します。
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
を含むいくつかのアクションがあります。
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[]
}
これにより、このリレーションのレコードを変更するための、より人間工学的で冗長性の少ないPrisma Client APIも実現します。なぜなら、最初にPostToCategories
モデルを走査する必要がなく、Post
からCategory
(およびその逆)への直接パスがあるためです。
データベースプロバイダーがテーブルに主キーを持つことを要求する場合は、明示的な構文を使用し、主キーを持つ結合モデルを手動で作成する必要があります。これは、Prisma ORMによって作成されたリレーションテーブル(JOINテーブル)(@relation
を介して表現される)が、暗黙的な構文を使用した多対多関係には主キーがないためです。