Skip to main content

如何将 Prisma ORM 和 Prisma Postgres 与 NestJS 结合使用

20 min

介绍

¥Introduction

本指南展示了如何将 Prisma ORM 与 NestJS 结合使用。NestJS 是一个渐进式 Node.js 框架,用于构建高效且可扩展的服务器端应用。你将使用 NestJS 构建一个 REST API,该 API 使用 Prisma ORM 从数据库中存储和检索数据。

¥This guide shows you how to use Prisma ORM with NestJS, a progressive Node.js framework for building efficient and scalable server-side applications. You'll build a REST API with NestJS that uses Prisma ORM to store and retrieve data from a database.

Prisma ORM 是一个适用于 Node.js 和 TypeScript 的开源 ORM。它可以替代编写纯 SQL 语句,或使用其他数据库访问工具,例如 SQL 查询构建器(如 knex.js)或 ORM(如 TypeORMSequelize)。Prisma 目前支持 PostgreSQL、MySQL、SQL Server、SQLite、MongoDB 和 CockroachDB。

¥Prisma ORM is an open-source ORM for Node.js and TypeScript. It is used as an alternative to writing plain SQL, or using another database access tool such as SQL query builders (like knex.js) or ORMs (like TypeORM and Sequelize). Prisma currently supports PostgreSQL, MySQL, SQL Server, SQLite, MongoDB and CockroachDB.

虽然 Prisma 可以与纯 JavaScript 一起使用,但它支持 TypeScript,并提供比 TypeScript 生态系统中其他 ORM 更高的类型安全保障。

¥While Prisma can be used with plain JavaScript, it embraces TypeScript and provides a level of type-safety that goes beyond the guarantees other ORMs in the TypeScript ecosystem offer.

你可以在 此处 中找到一个可直接运行的示例。

¥You can find a ready-to-run example here

先决条件

¥Prerequisites

1. 创建 NestJS 项目

¥ Create your NestJS project

安装 NestJS CLI 并创建一个新项目:

¥Install the NestJS CLI and create a new project:

npm install -g @nestjs/cli
nest new nestjs-prisma

出现提示时,请选择 npm 作为你的包管理器。导航到项目目录:

¥When prompted, select npm as your package manager. Navigate to the project directory:

cd nestjs-prisma

你可以运行 npm start 以在 http://localhost:3000/ 中启动你的应用。在本指南中,你将添加用于存储和检索用户及帖子数据的路由。

¥You can run npm start to start your application at http://localhost:3000/. Over the course of this guide, you'll add routes to store and retrieve data about users and posts.

package.json 文件中,添加 type 字段集,并将其赋值给 "module"

¥In package.json, add the type field set to "module":

package.json
{
"type": "module"
}

2. 设置 Prisma

¥ Set up Prisma

2.1.安装 Prisma 及其依赖

¥2.1. Install Prisma and dependencies

安装必要的 Prisma 软件包和数据库驱动程序:

¥Install the necessary Prisma packages and database drivers:

npm install prisma --save-dev
npm install @prisma/client @prisma/adapter-pg pg
信息

如果你使用其他数据库提供商(PostgreSQL、MySQL、SQL Server),请安装相应的驱动程序适配器包,而不是 @prisma/adapter-pg。欲了解更多信息,请参阅 数据库驱动程序

¥If you are using a different database provider (PostgreSQL, MySQL, SQL Server), install the corresponding driver adapter package instead of @prisma/adapter-pg. For more information, see Database drivers.

2.2.初始化 Prisma

¥2.2. Initialize Prisma

在项目中初始化 Prisma:

¥Initialize Prisma in your project:

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

这将创建一个包含以下内容的 prisma 目录:

¥This creates a new prisma directory with the following contents:

  • schema.prisma:指定你的数据库连接并包含数据库架构

    ¥schema.prisma: Specifies your database connection and contains the database schema

  • prisma.config.ts:项目配置文件

    ¥prisma.config.ts: A configuration file for your projects

  • .env:通常用于将数据库凭据存储在一组环境变量中的 dotenv 文件

    ¥.env: A dotenv file, typically used to store your database credentials in a group of environment variables

2.3.设置生成器输出路径

¥2.3. Set the generator output path

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

¥Specify your output path for the generated Prisma client by either passing --output ../src/generated/prisma during prisma init or directly in your Prisma schema:

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

2.4.配置你的数据库连接

¥2.4. Configure your database connection

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

¥Your database connection is configured in the datasource block in your schema.prisma file. By default it's set to postgresql which is what you need for this guide.

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

datasource db {
provider = "postgresql"
}

现在,打开 .env,你应该会看到已指定的 DATABASE_URL

¥Now, open up .env and you should see a DATABASE_URL already specified:

.env
DATABASE_URL=""
注意

请确保已配置 ConfigModule,否则 DATABASE_URL 变量将无法从 .env 中获取。

¥Make sure you have a ConfigModule configured, otherwise the DATABASE_URL variable will not be picked up from .env.

2.5.定义你的数据模型

¥2.5. Define your data model

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

¥Add the following two models to your schema.prisma file:

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

¥2.6. Create and run your migration

Prisma 模型就绪后,你可以生成 SQL 迁移文件并将其应用于数据库。在终端中运行以下命令:

¥With your Prisma models in place, you can generate your SQL migration files and run them against the database. Run the following commands in your terminal:

npx prisma migrate dev --name init

prisma migrate dev 命令生成 SQL 文件并直接针对数据库运行它们。在本例中,以下迁移文件已在现有的 prisma 目录中创建:

¥This prisma migrate dev command generates SQL files and directly runs them against the database. In this case, the following migration files was created in the existing prisma directory:

$ tree prisma
prisma
├── migrations
│ └── 20201207100915_init
│ └── migration.sql
└── schema.prisma

2.7.生成 Prisma 客户端

¥2.7. Generate Prisma Client

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

¥Once installed, you can run the generate command to generate the types and Client needed for your project. If any changes are made to your schema, you will need to rerun the generate command to keep those types in sync.

npx prisma generate

3. 创建 Prisma 服务

¥ Create a Prisma service

现在你可以使用 Prisma Client 发送数据库查询。设置 NestJS 应用时,你需要将 Prisma Client API 的数据库查询抽象到服务中。要开始使用,你可以创建一个新的 PrismaService 文件,该文件负责实例化 PrismaClient 并连接到你的数据库。

¥You're now able to send database queries with Prisma Client. When setting up your NestJS application, you'll want to abstract away the Prisma Client API for database queries within a service. To get started, you can create a new PrismaService that takes care of instantiating PrismaClient and connecting to your database.

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

¥Inside the src directory, create a new file called prisma.service.ts and add the following code to it:

src/prisma.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaClient } from './generated/prisma/client.js';
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. 创建用户和帖子服务

¥ Create User and Post services

接下来,你可以编写服务,用于从 Prisma schema 中调用 UserPost 模型的数据库。

¥Next, you can write services that you can use to make database calls for the User and Post models from your Prisma schema.

4.1.创建用户服务

¥4.1. Create the User service

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

¥Still inside the src directory, create a new file called user.service.ts and add the following code to it:

src/user.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service.js';
import { User, Prisma } from './generated/prisma/client.js';

@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 文件的繁琐步骤。

¥Notice how you're using Prisma Client's generated types to ensure that the methods that are exposed by your service are properly typed. You therefore save the boilerplate of typing your models and creating additional interface or DTO files.

4.2.创建帖子服务

¥4.2. Create the Post service

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

¥Now do the same for the Post model.

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

¥Still inside the src directory, create a new file called post.service.ts and add the following code to it:

src/post.service.ts

import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service.js';
import { Post, Prisma } from './generated/prisma/client.js';

@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 Client 中可用的 CRUD 查询。在实际应用中,服务通常也是添加业务逻辑的地方。例如,你可以在 UserService 中创建一个名为 updatePassword 的方法,该方法负责更新用户的密码。

¥Your UserService and PostService currently wrap the CRUD queries that are available in Prisma Client. In a real world application, the service would also be the place to add business logic to your application. For example, you could have a method called updatePassword inside the UserService that would be responsible for updating the password of a user.

5. 实现 REST API 路由

¥ Implement REST API routes

5.1.创建控制器

¥5.1. Create the controller

最后,你将使用前面章节中创建的服务来实现应用的不同路由。在本指南中,你需要将所有路由放入已有的 AppController 类中。

¥Finally, you'll use the services you created in the previous sections to implement the different routes of your app. For the purpose of this guide, you'll put all your routes into the already existing AppController class.

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

¥Replace the contents of the app.controller.ts file with the following code:

src/app.controller.ts
import {
Controller,
Get,
Param,
Post,
Body,
Put,
Delete,
} from '@nestjs/common';
import { UserService } from './user.service.js';
import { PostService } from './post.service.js';
import { User as UserModel } from './generated/prisma/client.js';
import { Post as PostModel } from './generated/prisma/client.js';

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

此控制器实现以下路由:

¥This controller implements the following routes:

GET

  • /post/:id:根据 id 获取单个帖子

    ¥/post/:id: Fetch a single post by its id

  • /feed:获取所有已发布的文章

    ¥/feed: Fetch all published posts

  • /filtered-posts/:searchString:按 titlecontent 筛选帖子

    ¥/filtered-posts/:searchString: Filter posts by title or content

POST

  • /post:创建新帖子

    ¥/post: Create a new post

    • 请求体:

      ¥Body:

      • title: String(必需):帖子标题

        ¥title: String (required): The title of the post

      • content: String(可选):帖子内容

        ¥content: String (optional): The content of the post

      • authorEmail: String(必需):创建帖子的用户的电子邮件地址

        ¥authorEmail: String (required): The email of the user that creates the post

  • /user:创建新用户

    ¥/user: Create a new user

    • 请求体:

      ¥Body:

      • email: String(必需):用户的电子邮件地址

        ¥email: String (required): The email address of the user

      • name: String(可选):用户的名称

        ¥name: String (optional): The name of the user

PUT

  • /publish/:id:通过 id 发布帖子

    ¥/publish/:id: Publish a post by its id

DELETE

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

    ¥/post/:id: Delete a post by its id

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

¥5.2. Register services in the app module

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

¥Remember to register the new services in the app module.

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

¥Update src/app.module.ts to register all services:

src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { ConfigModule } from '@nestjs/config';
import { AppService } from './app.service.js';
import { PrismaService } from './prisma.service.js';
import { UserService } from './user.service.js';
import { PostService } from './post.service.js';

@Module({
imports: [ConfigModule.forRoot()],
controllers: [AppController],
providers: [AppService, PrismaService, UserService, PostService],
})
export class AppModule {}

6. 测试 API

¥ Test your API

启动你的应用:

¥Start your application:

npm start

使用 curl、PostmanHTTPie 测试端点。

¥Test your endpoints with curl, Postman, or HTTPie.

创建用户:

¥Create a user:

curl -X POST http://localhost:3000/user \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@prisma.io"}'

创建帖子:

¥Create a post:

curl -X POST http://localhost:3000/post \
-H "Content-Type: application/json" \
-d '{"title": "Hello World", "authorEmail": "alice@prisma.io"}'

获取已发布的文章:

¥Get published posts:

curl http://localhost:3000/feed

发布文章:

¥Publish a post:

curl -X PUT http://localhost:3000/publish/1

搜索文章:

¥Search posts:

curl http://localhost:3000/filtered-posts/hello

概括

¥Summary

在本指南中,你学习了如何将 Prisma ORM 与 NestJS 结合使用来实现 REST API。实现 API 路由的控制器调用 PrismaService,而 PrismaService 又使用 Prisma Client 向数据库发送查询,以满足传入请求的数据需求。

¥In this guide, you learned how to use Prisma ORM with NestJS to implement a REST API. The controller that implements the routes of the API is calling a PrismaService which in turn uses Prisma Client to send queries to a database to fulfill the data needs of incoming requests.

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

¥If you want to learn more about using NestJS with Prisma, be sure to check out the following resources:


Stay connected with Prisma

Continue your Prisma journey by connecting with our active community. Stay informed, get involved, and collaborate with other developers:

We genuinely value your involvement and look forward to having you as part of our community!