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

Drizzle からの移行

このガイドでは、Drizzle から Prisma ORM への移行方法について説明します。このガイドでは、Drizzle Next.js の例 をベースにしたサンプルプロジェクトを、移行手順を示すサンプルプロジェクトとして使用しています。このガイドで使用されているサンプルは、GitHub で確認できます。

注意

この移行ガイドでは、例として 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

このコマンドにより、次のファイルを含む 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 ORMの命名規則に従うようにPrismaモデルの名前を調整する方法を学びます。

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

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

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

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

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

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によって作成されたリレーションテーブル(JOINテーブル)(@relationを介して表現される)が、暗黙的な構文を使用した多対多関係には主キーがないためです。