跳到主要內容

如何從 Drizzle 遷移到 Prisma ORM

15 分鐘

簡介

本指南將示範如何將您的應用程式從 Drizzle 遷移到 Prisma ORM。我們將使用基於 Drizzle Next.js 範例 的範例專案來示範遷移步驟。您可以在 GitHub 上找到本指南使用的範例。

您可以在 Prisma ORM 與 Drizzle 的比較頁面上了解 Prisma ORM 與 Drizzle 的比較。

先決條件

開始本指南之前,請確保您已具備

  • 您想要遷移的 Drizzle 專案
  • 已安裝 Node.js (版本 16 或更高版本)
  • PostgreSQL 或另一個支援的資料庫
  • 基本熟悉 Drizzle 和 Next.js
注意

本遷移指南使用 Neon PostgreSQL 作為範例資料庫,但同樣適用於 Prisma ORM 支援的任何其他關聯式資料庫。

您可以在 Prisma ORM 與 Drizzle 的比較頁面上了解 Prisma ORM 與 Drizzle 的比較。

遷移流程概述

請注意,無論您建置何種應用程式或 API 層,從 Drizzle 遷移到 Prisma ORM 的步驟始終相同

  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 schema 並將 Prisma 連接到您的資料庫。在專案的根目錄中執行以下命令以建立基本的 Prisma schema 檔案

npx prisma init

此命令建立了一個名為 prisma 的新目錄,其中包含以下檔案供您使用

  • schema.prisma:您的 Prisma schema,指定您的資料庫連線和模型
  • .env:一個 dotenv,用於將您的資料庫連線 URL 設定為環境變數
注意

您可能已經有一個 .env 檔案。如果是這樣,prisma init 命令會將行附加到其中,而不是建立新檔案。

Prisma schema 目前看起來如下

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 來發展您的資料庫 schema,您需要建立資料庫基準

首先,建立一個 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 表格。

您現在擁有目前資料庫 schema 的基準。若要對您的資料庫 schema 進行進一步的變更,您可以更新您的 Prisma schema 並使用 prisma migrate dev 將變更套用至您的資料庫。

2.5. 調整 Prisma schema (選用)

透過內省產生的模型目前完全對應到您的資料庫表格。在本節中,您將學習如何調整 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

安裝後,您需要執行 generate 以使您的 schema 反映在 TypeScript 類型和自動完成中。

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 應用程式有幾個 actions,包括 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 請求期間使用的動作

  • 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 允許您隱含地建立多對多關係模型。也就是說,在多對多關係中,您不必在您的 schema 中明確地管理關係表格 (有時也稱為 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),
});

此 schema 等同於以下 Prisma schema

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 schema 中,多對多關係是透過關係表格 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 來修改此關係中的記錄,因為您有從 PostCategory (以及反向) 的直接路徑,而不是需要先遍歷 PostToCategories 模型。

警告

如果您的資料庫供應商要求表格具有主索引鍵,則您必須使用明確的語法,並手動建立具有主索引鍵的聯結模型。這是因為 Prisma ORM 針對使用隱含語法的多對多關係建立的關係表格 (JOIN 表格) (透過 @relation 表示) 沒有主索引鍵。