如何從 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
- 內省(Introspect)您的資料庫
- 建立基準遷移(Baseline migration)
- 安裝 Prisma Client
- 逐步將您的 Drizzle 查詢替換為 Prisma Client
無論您是在建立 REST API(例如使用 Express、koa 或 NestJS)、GraphQL API(例如使用 Apollo Server、TypeGraphQL 或 Nexus)還是任何其他使用 Drizzle 存取資料庫的應用程式,這些步驟都適用。
Prisma ORM 非常適合漸進式採用(incremental adoption)。這意味著您不必一次將整個專案從 Drizzle 遷移到 Prisma ORM,而是可以循序漸進地將您的資料庫查詢從 Drizzle 轉移到 Prisma ORM。
步驟 1. 安裝 Prisma CLI
採用 Prisma ORM 的第一步是在您的專案中安裝 Prisma CLI
npm install prisma @types/pg --save-dev
npm install @prisma/client @prisma/adapter-pg pg
如果您使用的是不同的資料庫提供者(MySQL、SQL Server、SQLite),請安裝相應的驅動程式適配器套件,而不是 @prisma/adapter-pg。如需更多資訊,請參閱資料庫驅動程式。
步驟 2. 內省您的資料庫
2.1. 設定 Prisma ORM
在內省資料庫之前,您需要設定 Prisma 結構描述(schema)並將 Prisma 連接到您的資料庫。在專案根目錄執行以下指令以建立基本的 Prisma 結構描述檔案:
npx prisma init --output ../generated/prisma
此指令為您建立了一個名為 prisma 的新目錄,其中包含以下檔案:
schema.prisma:您的 Prisma 結構描述,用於指定資料庫連接和模型.env:一個dotenv檔案,用於將資料庫連接 URL 設定為環境變數
您可能已經有一個 .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"
}
generator client {
provider = "prisma-client"
output = "./generated/prisma"
}
如果您使用 VS Code,請務必安裝 Prisma VS Code 擴充功能,以獲得語法高亮、格式化、自動完成以及更多酷炫功能。
2.2. 連接您的資料庫
如果您不使用 PostgreSQL,則需要將 datasource 區塊中的 provider 欄位調整為您目前使用的資料庫:
- PostgreSQL
- MySQL
- Microsoft SQL Server
- SQLite
datasource db {
provider = "postgresql"
}
datasource db {
provider = "mysql"
}
datasource db {
provider = "sqlserver"
}
datasource db {
provider = "sqlite"
}
完成後,您可以在 .env 檔案中配置您的資料庫連接 URL。Drizzle 和 Prisma ORM 使用相同的連接 URL 格式,因此您現有的連接 URL 應該可以正常運作。
2.3. 設定 Prisma
在專案根目錄建立一個包含以下內容的 prisma.config.ts 檔案:
import 'dotenv/config'
import { defineConfig, env } from 'prisma/config';
export default defineConfig({
schema: 'prisma/schema.prisma',
migrations: {
path: 'prisma/migrations',
},
datasource: {
url: env('DATABASE_URL'),
},
});
您需要安裝 dotenv 套件來載入環境變數。如果您尚未安裝,請使用您的套件管理員安裝:
npm install dotenv
2.4. 使用 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.5. 建立基準遷移(Baseline migration)
要繼續使用 Prisma Migrate 來演進您的資料庫結構,您需要對資料庫進行基準化(baselining)。
首先,建立一個 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.6. 調整 Prisma 結構描述(選用)
透過內省產生的模型目前會精確地對應到您的資料庫資料表。在本節中,您將學習如何調整 Prisma 模型的命名,以符合 Prisma ORM 的命名慣例。
所有這些調整都是完全選用的,如果您現在不想調整任何內容,可以跳過此步驟。您可以在以後的任何時間點回來進行調整。
與 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
既然您已在步驟 1 中安裝了 Prisma Client,您需要執行 generate,以便將您的結構描述反映在 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 並將其從檔案中導出,以便稍後在路由處理程式中使用:
import { PrismaClient } from '../generated/prisma/client'
import { PrismaPg } from '@prisma/adapter-pg'
import 'dotenv/config'
const adapter = new PrismaPg({
connectionString: process.env.DATABASE_URL,
})
export const prisma = new PrismaClient({
adapter,
})
4.1. 替換 getData 查詢
全端 Next.js 應用程式有多個 actions(動作),包括 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 請求期間使用的動作:
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 無法「就地(in place)」編輯布林欄位,因此必須事先獲取該紀錄。
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 允許您隱式地建模多對多關係。也就是說,在一種多對多關係中,您不需要在結構描述中顯式地管理關聯表(relation table)(有時也稱為 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 來修改此關係中的紀錄,因為您可以直接從 Post 到 Category(反之亦然),而不需要先遍歷 PostToCategories 模型。
如果您的資料庫供應商要求資料表必須有主鍵(Primary key),那麼您必須使用顯式語法,並手動建立具有主鍵的合併模型(join model)。這是因為 Prisma ORM 為使用隱式語法的多對多關係建立的關聯表(JOIN 表)(透過 @relation 表示)沒有主鍵。
與 Prisma 保持聯繫
透過以下方式與我們聯繫,繼續您的 Prisma 旅程: 我們的活躍社群。保持資訊靈通、參與其中,並與其他開發者合作
- 在 X 上關注我們 以獲取公告、現場活動和實用技巧。
- 加入我們的 Discord 提出問題、與社群對話,並透過對話獲得積極支援。
- 在 YouTube 上訂閱 查看教學、演示和直播。
- 在 GitHub 上交流 透過為存放庫加星標、報告問題或為 issue 做出貢獻。