跳至主要内容

如何将 Prisma ORM 与 NestJS 结合使用

20 分钟

介绍

本指南向您展示如何在 NestJS(一个用于构建高效可扩展服务器端应用程序的渐进式 Node.js 框架)中使用 Prisma ORM。您将使用 NestJS 构建一个 REST API,该 API 使用 Prisma ORM 从数据库存储和检索数据。

Prisma ORM 是一个用于 Node.js 和 TypeScript 的开源 ORM。它被用作编写纯 SQL 或使用其他数据库访问工具(例如 SQL 查询构建器(如 knex.js)或 ORM(如 TypeORMSequelize))的替代方案。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 starthttps://:3000/ 启动您的应用程序。在本指南中,您将添加路由来存储和检索有关用户帖子的数据。

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。有关更多信息,请参阅数据库驱动程序

2.2. 初始化 Prisma

在您的项目中初始化 Prisma

npx prisma init --db --output ../src/generated/prisma

这将创建一个新的 prisma 目录,其中包含以下内容

  • schema.prisma:指定您的数据库连接并包含数据库架构
  • prisma.config.ts:您的项目的配置文件
  • .env:一个 dotenv 文件,通常用于将您的数据库凭据存储在一组环境变量中

2.3. 设置生成器输出路径

通过在 prisma init 期间传递 --output ../src/generated/prisma 或直接在您的 Prisma 架构中指定生成的 Prisma 客户端的输出 path

prisma/schema.prisma
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}

2.4. 配置您的数据库连接

您的数据库连接在 schema.prisma 文件中的 datasource 块中配置。默认情况下,它设置为 postgresql,这是本指南所需的。

prisma/schema.prisma
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}

datasource db {
provider = "postgresql"
}

现在,打开 .env,您应该会看到已经指定了 DATABASE_URL

.env
DATABASE_URL=""
注意

确保您已配置 ConfigModule,否则将不会从 .env 中获取 DATABASE_URL 变量。

2.5. 定义您的数据模型

将以下两个模型添加到您的 schema.prisma 文件中

prisma/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. 创建并运行您的迁移

有了 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 客户端

安装后,您可以运行生成命令以生成项目所需的类型和客户端。如果对您的架构进行了任何更改,您将需要重新运行 generate 命令以保持这些类型同步。

npx prisma generate

3. 创建 Prisma 服务

您现在可以使用 Prisma 客户端发送数据库查询。设置 NestJS 应用程序时,您需要将 Prisma 客户端 API 用于数据库查询抽象到服务中。首先,您可以创建一个新的 PrismaService,负责实例化 PrismaClient 并连接到您的数据库。

src 目录中,创建一个名为 prisma.service.ts 的新文件并向其中添加以下代码

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. 创建用户和帖子服务

接下来,您可以编写服务,用于从 Prisma 架构中的 UserPost 模型进行数据库调用。

4.1. 创建用户服务

仍在 src 目录中,创建一个名为 user.service.ts 的新文件并向其中添加以下代码

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 客户端生成的类型来确保您的服务公开的方法具有正确的类型。因此,您省去了为模型类型化和创建额外的接口或 DTO 文件的样板代码。

4.2. 创建帖子服务

现在对 Post 模型执行相同的操作。

仍在 src 目录中,创建一个名为 post.service.ts 的新文件并向其中添加以下代码

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,
});
}
}

您的 UserServicePostService 目前封装了 Prisma 客户端中可用的 CRUD 查询。在实际应用程序中,服务也将是向应用程序添加业务逻辑的地方。例如,您可以在 UserService 中有一个名为 updatePassword 的方法,负责更新用户的密码。

5. 实现 REST API 路由

5.1. 创建控制器

最后,您将使用在前面部分中创建的服务来实现应用程序的不同路由。为了本指南的目的,您将把所有路由都放入已存在的 AppController 类中。

app.controller.ts 文件的内容替换为以下代码

src/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:按 titlecontent 筛选帖子

POST

  • /post:创建新帖子
    • 正文
      • title: String (必需):帖子的标题
      • content: String (可选):帖子的内容
      • authorEmail: String (必需):创建帖子的用户的电子邮件
  • /user:创建新用户
    • 正文
      • email: String (必需):用户的电子邮件地址
      • name: String (可选):用户的姓名

PUT

  • /publish/:id:按其 id 发布帖子

DELETE

  • /post/:id:按其 id 删除帖子

5.2. 在应用模块中注册服务

请记住在应用模块中注册新服务。

更新 src/app.module.ts 以注册所有服务

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、PostmanHTTPie 测试您的端点。

创建用户

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 客户端向数据库发送查询以满足传入请求的数据需求。

如果您想了解更多关于将 NestJS 与 Prisma 结合使用,请务必查看以下资源


与 Prisma 保持联系

通过以下方式与我们保持联系,继续你的 Prisma 之旅: 我们的活跃社区。保持信息灵通,参与其中,并与其他开发人员协作。

我们真诚地感谢你的参与,并期待你成为我们社区的一部分!

© . This site is unofficial and not affiliated with Prisma Data, Inc.