如何在 NestJS 中使用 Prisma ORM
簡介
本指南將展示如何將 Prisma ORM 與 NestJS 搭配使用。NestJS 是一個用於構建高效且可擴展的伺服器端應用程式的漸進式 Node.js 框架。您將使用 NestJS 構建一個 REST API,並利用 Prisma ORM 來存儲與檢索資料庫中的資料。
Prisma ORM 是一個適用於 Node.js 與 TypeScript 的開源 ORM。它作為編寫原始 SQL,或使用其他資料庫存取工具(如 SQL 查詢構建器,例如 knex.js)或 ORM(如 TypeORM 和 Sequelize)的替代方案。Prisma 目前支援 PostgreSQL、MySQL、SQL Server、SQLite、MongoDB 與 CockroachDB。
雖然 Prisma 可以與原始 JavaScript 一起使用,但它深度整合了 TypeScript,並提供了超越 TypeScript 生態系統中其他 ORM 所能保證的型別安全級別。
您可以在此處找到一個可直接執行的範例。
先決條件
1. 建立您的 NestJS 專案
安裝 NestJS CLI 並建立一個新專案
npm install -g @nestjs/cli
nest new nestjs-prisma
出現提示時,請選擇 npm 作為您的套件管理工具。接著導航至專案目錄
cd nestjs-prisma
您可以執行 npm start 以在 https://:3000/ 啟動您的應用程式。在本指南的過程中,您將新增路由來儲存和檢索有關 使用者 (users) 和 文章 (posts) 的資料。
2. 設定 Prisma
2.1. 安裝 Prisma 與相依套件
安裝必要的 Prisma 套件與資料庫驅動程式
npm install prisma --save-dev
npm install @prisma/client @prisma/adapter-pg pg
如果您使用的是其他資料庫提供者(PostgreSQL、MySQL、SQL Server),請安裝對應的驅動程式適配器套件,而非 @prisma/adapter-pg。如需更多資訊,請參閱 資料庫驅動程式 (Database drivers)。
2.2. 初始化 Prisma
在您的專案中初始化 Prisma
npx prisma init --db --output ../src/generated/prisma
這會建立一個包含以下內容的 prisma 目錄
schema.prisma:指定您的資料庫連線並包含資料庫架構 (schema)prisma.config.ts:您專案的設定檔.env:一個 dotenv 檔案,通常用於在一組環境變數中儲存您的資料庫憑證
2.3. 設定產生器輸出路徑
您可以透過在 prisma init 時傳遞 --output ../src/generated/prisma,或直接在您的 Prisma schema 中,指定產生之 Prisma 客戶端的輸出 path
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
2.4. 設定您的資料庫連線
您的資料庫連線是在 schema.prisma 檔案中的 datasource 區塊設定的。預設情況下它被設定為 postgresql,這正是本指南所需的設定。
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
datasource db {
provider = "postgresql"
}
現在,開啟 .env,您應該會看到已經指定了 DATABASE_URL
DATABASE_URL=""
請確保您已設定 ConfigModule,否則 DATABASE_URL 變數將無法從 .env 中讀取。
2.5. 定義您的資料模型
將以下兩個模型加入到您的 schema.prisma 檔案中
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean? @default(false)
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}
2.6. 建立並執行遷移 (Migration)
設定好 Prisma 模型後,您可以產生 SQL 遷移檔案並對資料庫執行它們。在您的終端機中執行以下指令
npx prisma migrate dev --name init
此 prisma migrate dev 指令會產生 SQL 檔案並直接對資料庫執行。在這種情況下,現有的 prisma 目錄中將建立下列遷移檔案
$ tree prisma
prisma
├── migrations
│ └── 20201207100915_init
│ └── migration.sql
└── schema.prisma
2.7. 產生 Prisma Client
安裝完成後,您可以執行產生指令來建立專案所需的型別與客戶端。如果您的 schema 有任何變更,您需要重新執行 generate 指令以保持這些型別同步。
npx prisma generate
3. 建立一個 Prisma 服務
現在您可以使用 Prisma Client 發送資料庫查詢了。在設定 NestJS 應用程式時,您會希望將 Prisma Client API 的資料庫查詢抽象化到一個服務中。首先,您可以建立一個新的 PrismaService,負責實例化 PrismaClient 並連線至您的資料庫。
在 src 目錄中,建立一個名為 prisma.service.ts 的新檔案,並加入以下程式碼
import { Injectable } from '@nestjs/common';
import { PrismaClient } from './generated/prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
@Injectable()
export class PrismaService extends PrismaClient {
constructor() {
const adapter = new PrismaPg({
connectionString: process.env.DATABASE_URL as string,
});
super({ adapter });
}
}
4. 建立 User 與 Post 服務
接下來,您可以編寫服務來針對 Prisma schema 中的 User 與 Post 模型進行資料庫呼叫。
4.1. 建立 User 服務
同樣在 src 目錄中,建立一個名為 user.service.ts 的新檔案,並加入以下程式碼
import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
import { User, Prisma } from './generated/prisma/client';
@Injectable()
export class UserService {
constructor(private prisma: PrismaService) {}
async user(
userWhereUniqueInput: Prisma.UserWhereUniqueInput,
): Promise<User | null> {
return this.prisma.user.findUnique({
where: userWhereUniqueInput,
});
}
async users(params: {
skip?: number;
take?: number;
cursor?: Prisma.UserWhereUniqueInput;
where?: Prisma.UserWhereInput;
orderBy?: Prisma.UserOrderByWithRelationInput;
}): Promise<User[]> {
const { skip, take, cursor, where, orderBy } = params;
return this.prisma.user.findMany({
skip,
take,
cursor,
where,
orderBy,
});
}
async createUser(data: Prisma.UserCreateInput): Promise<User> {
return this.prisma.user.create({
data,
});
}
async updateUser(params: {
where: Prisma.UserWhereUniqueInput;
data: Prisma.UserUpdateInput;
}): Promise<User> {
const { where, data } = params;
return this.prisma.user.update({
data,
where,
});
}
async deleteUser(where: Prisma.UserWhereUniqueInput): Promise<User> {
return this.prisma.user.delete({
where,
});
}
}
請注意,您是如何使用 Prisma Client 產生的型別來確保服務公開的方法具有正確的型別定義。因此,您節省了為模型定義型別以及建立額外介面或 DTO 檔案的重複性工作 (boilerplate)。
4.2. 建立 Post 服務
現在對 Post 模型執行相同的操作。
同樣在 src 目錄中,建立一個名為 post.service.ts 的新檔案,並加入以下程式碼
import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
import { Post, Prisma } from './generated/prisma/client';
@Injectable()
export class PostService {
constructor(private prisma: PrismaService) {}
async post(
postWhereUniqueInput: Prisma.PostWhereUniqueInput,
): Promise<Post | null> {
return this.prisma.post.findUnique({
where: postWhereUniqueInput,
});
}
async posts(params: {
skip?: number;
take?: number;
cursor?: Prisma.PostWhereUniqueInput;
where?: Prisma.PostWhereInput;
orderBy?: Prisma.PostOrderByWithRelationInput;
}): Promise<Post[]> {
const { skip, take, cursor, where, orderBy } = params;
return this.prisma.post.findMany({
skip,
take,
cursor,
where,
orderBy,
});
}
async createPost(data: Prisma.PostCreateInput): Promise<Post> {
return this.prisma.post.create({
data,
});
}
async updatePost(params: {
where: Prisma.PostWhereUniqueInput;
data: Prisma.PostUpdateInput;
}): Promise<Post> {
const { data, where } = params;
return this.prisma.post.update({
data,
where,
});
}
async deletePost(where: Prisma.PostWhereUniqueInput): Promise<Post> {
return this.prisma.post.delete({
where,
});
}
}
您的 UserService 與 PostService 目前封裝了 Prisma Client 中可用的 CRUD 查詢。在實際的應用程式中,服務也是新增商業邏輯的地方。例如,您可以在 UserService 中擁有一個名為 updatePassword 的方法,負責更新使用者的密碼。
5. 實作 REST API 路由
5.1. 建立控制器 (Controller)
最後,您將使用在前幾節中建立的服務來實作應用程式的不同路由。在本指南中,您將把所有路由放入現有的 AppController 類別中。
使用以下程式碼替換 app.controller.ts 檔案的內容
import {
Controller,
Get,
Param,
Post,
Body,
Put,
Delete,
} from '@nestjs/common';
import { UserService } from './user.service';
import { PostService } from './post.service';
import { User as UserModel } from './generated/prisma/client';
import { Post as PostModel } from './generated/prisma/client';
@Controller()
export class AppController {
constructor(
private readonly UserService: UserService,
private readonly postService: PostService,
) {}
@Get('post/:id')
async getPostById(@Param('id') id: string): Promise<PostModel | null> {
return this.postService.post({ id: Number(id) });
}
@Get('feed')
async getPublishedPosts(): Promise<PostModel[]> {
return this.postService.posts({
where: { published: true },
});
}
@Get('filtered-posts/:searchString')
async getFilteredPosts(
@Param('searchString') searchString: string,
): Promise<PostModel[]> {
return this.postService.posts({
where: {
OR: [
{
title: { contains: searchString },
},
{
content: { contains: searchString },
},
],
},
});
}
@Post('post')
async createDraft(
@Body() postData: { title: string; content?: string; authorEmail: string },
): Promise<PostModel> {
const { title, content, authorEmail } = postData;
return this.postService.createPost({
title,
content,
author: {
connect: { email: authorEmail },
},
});
}
@Post('user')
async signupUser(
@Body() userData: { name?: string; email: string },
): Promise<UserModel> {
return this.UserService.createUser(userData);
}
@Put('publish/:id')
async publishPost(@Param('id') id: string): Promise<PostModel> {
return this.postService.updatePost({
where: { id: Number(id) },
data: { published: true },
});
}
@Delete('post/:id')
async deletePost(@Param('id') id: string): Promise<PostModel> {
return this.postService.deletePost({ id: Number(id) });
}
}
此控制器實作了以下路由
GET
/post/:id:依id獲取單一文章/feed:獲取所有已發佈的文章/filtered-posts/:searchString:依title或content篩選文章
POST
/post:建立新文章- 主體 (Body)
title: String(必填):文章標題content: String(選填):文章內容authorEmail: String(必填):建立文章的使用者電子郵件
- 主體 (Body)
/user:建立新使用者- 主體 (Body)
email: String(必填):使用者電子郵件地址name: String(選填):使用者名稱
- 主體 (Body)
PUT
/publish/:id:依id發佈文章
DELETE
/post/:id:依id刪除文章
5.2. 在 App Module 中註冊服務
記得在 App Module 中註冊新服務。
更新 src/app.module.ts 以註冊所有服務
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { ConfigModule } from '@nestjs/config';
import { AppService } from './app.service';
import { PrismaService } from './prisma.service';
import { UserService } from './user.service';
import { PostService } from './post.service';
@Module({
imports: [ConfigModule.forRoot()],
controllers: [AppController],
providers: [AppService, PrismaService, UserService, PostService],
})
export class AppModule {}
6. 測試您的 API
啟動您的應用程式
npm start
使用 curl、Postman 或 HTTPie 測試您的端點。
建立使用者
curl -X POST https://:3000/user \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@prisma.io"}'
建立文章
curl -X POST https://:3000/post \
-H "Content-Type: application/json" \
-d '{"title": "Hello World", "authorEmail": "alice@prisma.io"}'
獲取已發佈文章
curl https://:3000/feed
發佈文章
curl -X PUT https://:3000/publish/1
搜尋文章
curl https://:3000/filtered-posts/hello
總結
在本指南中,您學習了如何將 Prisma ORM 與 NestJS 搭配使用來實作 REST API。實作 API 路由的控制器會呼叫 PrismaService,後者則利用 Prisma Client 發送查詢至資料庫,以滿足傳入請求的資料需求。
如果您想了解更多關於在 NestJS 中使用 Prisma 的資訊,請務必查看以下資源
- NestJS & Prisma
- REST & GraphQL 的可直接執行範例專案
- 生產環境就緒的啟動套件 (Starter kit)
- 影片:使用 NestJS 與 Prisma 存取資料庫 (5分鐘) 作者:Marc Stammerjohann
與 Prisma 保持聯繫
透過以下方式與我們聯繫,繼續您的 Prisma 旅程: 我們的活躍社群。保持資訊靈通、參與其中,並與其他開發者合作
- 在 X 上關注我們 以獲取公告、現場活動和實用技巧。
- 加入我們的 Discord 提出問題、與社群對話,並透過對話獲得積極支援。
- 在 YouTube 上訂閱 查看教學、演示和直播。
- 在 GitHub 上交流 透過為存放庫加星標、報告問題或為 issue 做出貢獻。