所有文章 > API开发 > 使用NestJS和Prisma构建REST API:处理关系型数据
使用NestJS和Prisma构建REST API:处理关系型数据

使用NestJS和Prisma构建REST API:处理关系型数据

欢迎来到本教程,了解如何使用 NestJS、Prisma 和 PostgreSQL 构建 REST API! 在本教程中,您将学习如何在 NestJS REST API 中处理关系数据。

介绍

在本章中,我们将深入探讨如何在数据层和API层处理关系型数据。

  1. 首先,您将向数据库模式中添加 User 模型,这个模型将包含与Article模型的一对多关系(即一个用户可以拥有多个文章)。
  2. 接下来,您将为User端点实现API路由,以对 User 记录执行CRUD(创建、读取、更新和删除)操作。
  3. 最后,您将学习如何在API层中对 User-Article 关系建模。

在本教程中,我们将基于第二章中构建的REST API进行操作。

开发环境

要学习本教程,您需要:

  • …安装Node.js。
  • …安装 Docker 和 Docker Compose。如果您使用的是 Linux,请确保您的 Docker 版本为 20.10.0 或更高版本。您可以通过在终端中运行 docker version 来检查您的 Docker 版本。
  • …安装 Prisma VS Code 扩展。这个扩展为Prisma提供了强大的IntelliSense功能和语法高亮显示,增强了开发体验。
  • …可以选择访问 Unix shell(如 Linux 和 macOS 中的终端/shell)来运行本系列中提供的命令。

如果您没有 Unix shell(例如,您使用的是 Windows 计算机),您仍然可以继续操作,但可能需要为您的计算机修改 shell 命令。

克隆存储库

本教程的起始点位于GitHub仓库的end-validation分支。首先,您需要克隆该仓库并切换到end-validation分支。以下是操作步骤:

git clone -b end-validation git@github.com:prisma/blog-backend-rest-api-nestjs-prisma.git

现在,执行以下操作以开始使用:

  1. 导航到克隆的目录:
cd blog-backend-rest-api-nestjs-prisma
  1. 安装依赖项:
npm install
  1. 使用 Docker 启动 PostgreSQL 数据库:
docker-compose up -d
  1. 应用数据库迁移:
npx prisma migrate dev
  1. 启动项目:
npm run start:dev

注意:在步骤4中,我们不仅会生成Prisma客户端,还会设置数据库的初始种子数据。

项目结构和文件

您克隆的存储库应具有以下结构:

median
├── node_modules
├── prisma
│ ├── migrations
│ ├── schema.prisma
│ └── seed.ts
├── src
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ ├── main.ts
│ ├── articles
│ └── prisma
├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── README.md
├── .env
├── docker-compose.yml
├── nest-cli.json
├── package-lock.json
├── package.json
├── tsconfig.build.json
└── tsconfig.json

请注意:您可能会发现,此文件夹中还包含了一个名为test的目录。本教程中不会涉及对这部分内容的测试。

此存储库中的关键文件和目录如下:

  • src 目录包含应用程序的源代码。它包含以下三个模块:
    • App 模块:位于 src 目录的根部,是应用程序的入口点。它负责启动 Web 服务器。
    • Prisma 模块:包含 Prisma Client,这是您与数据库的接口。
    • Articles 模块:定义了 /articles 路由的端点以及相关的业务逻辑。
  • prisma 文件夹包含以下内容:
    • schema.prisma 文件:定义了数据库架构。
    • migrations 目录:包含了数据库的迁移历史记录。
    • seed.ts 文件:包含一个脚本,用于使用虚拟数据为您的开发数据库填充初始数据。
  • docker-compose.yml 文件:定义了您的 PostgreSQL 数据库的 Docker 映像。
  • .env 文件:包含了您的 PostgreSQL 数据库的数据库连接字符串。

将User模型添加到数据库

目前,您的数据库架构中仅包含一个模型:Article。文章可以由注册用户撰写。因此,您将在数据库架构中添加一个User模型,以反映这种关系。

首先更新 Prisma 架构:

// prisma/schema.prisma

model Article {
id Int @id @default(autoincrement())
title String @unique
description String?
body String
published Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}

model User {
id Int @id @default(autoincrement())
name String?
email String @unique
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
articles Article[]
}

User模型包含了一些您可能期待的字段,例如id、email、password等,并且与Article模型建立了一对多的关系。这意味着一个用户可以拥有多篇文章,但一篇文章只能有一个作者。为了简化设计,author关系是可选的,所以您也可以创建没有指定作者的文章。

现在,要将这些更改应用到数据库,请执行迁移命令:

npx prisma migrate dev --name "add-user-model"

如果迁移成功运行,您应该会看到以下输出:

...
The following migration(s) have been created and applied from new schema changes:

migrations/
└─ 20230318100533_add_user_model/
└─ migration.sql

Your database is now in sync with your schema
...

更新您的种子脚本

seed 脚本负责使用虚拟数据填充数据库。您将更新seed脚本以在数据库中创建一些用户。

打开 prisma/seed.ts 文件并更新如下:

async function main() {
// create two dummy users
const user1 = await prisma.user.upsert({
where: { email: 'sabin@adams.com' },
update: {},
create: {
email: 'sabin@adams.com',
name: 'Sabin Adams',
password: 'password-sabin',
},
});

const user2 = await prisma.user.upsert({
where: { email: 'alex@ruheni.com' },
update: {},
create: {
email: 'alex@ruheni.com',
name: 'Alex Ruheni',
password: 'password-alex',
},
});

// create three dummy articles
const post1 = await prisma.article.upsert({
where: { title: 'Prisma Adds Support for MongoDB' },
update: {
authorId: user1.id,
},
create: {
title: 'Prisma Adds Support for MongoDB',
body: 'Support for MongoDB has been one of the most requested features since the initial release of...',
description:
"We are excited to share that today's Prisma ORM release adds stable support for MongoDB!",
published: false,
authorId: user1.id,
},
});

const post2 = await prisma.article.upsert({
where: { title: "What's new in Prisma? (Q1/22)" },
update: {
authorId: user2.id,
},
create: {
title: "What's new in Prisma? (Q1/22)",
body: 'Our engineers have been working hard, issuing new releases with many improvements...',
description:
'Learn about everything in the Prisma ecosystem and community from January to March 2022.',
published: true,
authorId: user2.id,
},
});

const post3 = await prisma.article.upsert({
where: { title: 'Prisma Client Just Became a Lot More Flexible' },
update: {},
create: {
title: 'Prisma Client Just Became a Lot More Flexible',
body: 'Prisma Client extensions provide a powerful new way to add functionality to Prisma in a type-safe manner...',
description:
'This article will explore various ways you can use Prisma Client extensions to add custom functionality to Prisma Client..',
published: true,
},
});

console.log({ user1, user2, post1, post2, post3 });
}

种子脚本现在创建了两个用户和三篇文章。第一篇文章的作者是第一个用户,第二篇文章的作者是第二个用户,而第三篇文章则没有指定作者。

注意:目前,密码是以纯文本形式存储的。在实际应用中,您绝不应该这样做。在下一章中,您将了解到如何对密码进行加盐和哈希处理的更多信息。

要执行种子脚本,请运行以下命令:

npx prisma db seed

如果种子脚本成功运行,您应该会看到以下输出:

...🌱  The seed command has been executed.

在ArticleEntity中添加一个新的authorld字段

在迁移之后,您可能已经注意到出现了一个新的 TypeScript 错误。ArticleEntity 类实现了由 Prisma 生成的 Article 类型。Article 类型现在有了一个新的 authorId 字段,但是 ArticleEntity 类中还没有定义这个字段。TypeScript 识别到了类型之间的这种不匹配,并抛出了一个错误。您将通过在 ArticleEntity 类中添加 authorId 字段来解决这个错误。

ArticleEntity 中添加一个新的 authorId 字段:

// src/articles/entities/article.entity.ts

import { Article } from '@prisma/client';
import { ApiProperty } from '@nestjs/swagger';

export class ArticleEntity implements Article {
@ApiProperty()
id: number;

@ApiProperty()
title: string;

@ApiProperty({ required: false, nullable: true })
description: string | null;

@ApiProperty()
body: string;

@ApiProperty()
published: boolean;

@ApiProperty()
createdAt: Date;

@ApiProperty()
updatedAt: Date;

@ApiProperty({ required: false, nullable: true })
authorId: number | null;
}

在像JavaScript这样的弱类型语言中,开发者需要自行识别和修复类型相关的问题。而拥有像TypeScript这样的强类型语言的一大优势在于,它能够迅速帮助开发者发现并解决类型不匹配的问题。

为用户实现 CRUD 端点

在本部分中,我们将为REST API添加 /users 资源,这将赋予您对数据库中用户执行创建(Create)、读取(Read)、更新(Update)和删除(Delete)操作(即CRUD操作)的能力。

生成新的 user REST 资源

要为 users 生成新的REST资源,请运行以下命令:

npx nest generate resource

您将会看到一些命令行提示(CLI prompts)。请根据提示相应回答问题:

  1. 您想为这个资源使用什么名称(复数形式,例如“users”)? users
  2. 您使用什么传输层? REST API
  3. 您是否需要生成CRUD入口点? 是

现在,您应该在src/users目录中找到一个新的用户模块,其中包含您REST端点所需的所有样板代码。

src/users/users.controller.ts文件中,您会看到不同路由(也称为路由处理程序)的定义。处理每个请求的业务逻辑被封装在src/users/users.service.ts文件中。

如果您打开Swagger生成的API页面,您应该会看到类似这样的内容:

自动生成的 “users” 端点

将 PrismaClient 添加到 Users 模块

要在 Users 模块内部访问 PrismaClient,您必须将 PrismaModule 作为导入项。请在 UsersModule 中添加以下导入内容:

// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { PrismaModule } from 'src/prisma/prisma.module';

@Module({
controllers: [UsersController],
providers: [UsersService],
imports: [PrismaModule],
})
export class UsersModule {}

现在,您可以在 UsersService 中注入 PrismaService,并使用它来访问数据库。为此,请在 users.service.ts 文件中添加一个构造函数,如下所示:

// src/users/users.service.ts

import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { PrismaService } from 'src/prisma/prisma.service';

@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}

// CRUD operations
}

定义User 实体和 DTO 类

ArticleEntity 类似,您将定义一个 UserEntity 类,用于在API层表示User实体。请在 user.entity.ts 文件中定义 UserEntity 类,如下所示:

// src/users/entities/user.entity.ts
import { ApiProperty } from '@nestjs/swagger';
import { User } from '@prisma/client';

export class UserEntity implements User {
@ApiProperty()
id: number;

@ApiProperty()
createdAt: Date;

@ApiProperty()
updatedAt: Date;

@ApiProperty()
name: string;

@ApiProperty()
email: string;

password: string;
}

@ApiProperty 装饰器用于使属性在Swagger中可见。请注意,您没有为password字段添加@ApiProperty装饰器。这是因为该字段是敏感的,您不希望它在API中暴露。

注意:省略 @ApiProperty 装饰器只会从Swagger文档中隐藏 password 属性。该属性仍然会在响应体中可见。您将在后面的部分处理这个问题。

DTO(数据传输对象)是一个定义数据如何通过网络发送的对象。您需要实现CreateUserDtoUpdateUserDto类,以分别定义在创建和更新用户时发送到API的数据。请在create-user.dto.ts文件中定义CreateUserDto 类,如下所示:

// src/users/dto/create-user.dto.ts

import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString, MinLength } from 'class-validator';

export class CreateUserDto {
@IsString()
@IsNotEmpty()
@ApiProperty()
name: string;

@IsString()
@IsNotEmpty()
@ApiProperty()
email: string;

@IsString()
@IsNotEmpty()
@MinLength(6)
@ApiProperty()
password: string;
}

@IsString@MinLength 和 @IsNotEmpty 是用于验证发送到API的数据的验证装饰器

UpdateUserDto 的定义会自动从 CreateUserDto 的定义中推断出来,因此无需显式定义。

定义 UsersService 类

UsersService 负责使用 Prisma Client 从数据库中修改和获取数据,并将其提供给 UsersController。您将在该类中实现 create()findAll()findOne()update() 和 remove() 方法。

// src/users/users.service.ts

import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { PrismaService } from 'src/prisma/prisma.service';

@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}

create(createUserDto: CreateUserDto) {
return this.prisma.user.create({ data: createUserDto });
}

findAll() {
return this.prisma.user.findMany();
}

findOne(id: number) {
return this.prisma.user.findUnique({ where: { id } });
}

update(id: number, updateUserDto: UpdateUserDto) {
return this.prisma.user.update({ where: { id }, data: updateUserDto });
}

remove(id: number) {
return this.prisma.user.delete({ where: { id } });
}
}

定义 UsersController 类

UsersController 负责处理对 users 端点的请求和响应。它将利用 UsersService 来访问数据库,使用 UserEntity 来定义响应体,以及使用 CreateUserDto 和 UpdateUserDto 来定义请求体。

控制器由不同的路由处理程序组成。您将在该类中实现五个路由处理程序,它们分别对应于五个端点:

  • create() – POST /users
  • findAll() – GET /users
  • findOne() – GET /users/:id
  • update() – PATCH /users/:id
  • remove() – DELETE /users/:id

在 users.controller.ts 文件中更新这些路由处理程序的实现,如下所示:

// src/users/users.controller.ts

import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
ParseIntPipe,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { UserEntity } from './entities/user.entity';

@Controller('users')
@ApiTags('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}

@Post()
@ApiCreatedResponse({ type: UserEntity })
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}

@Get()
@ApiOkResponse({ type: UserEntity, isArray: true })
findAll() {
return this.usersService.findAll();
}

@Get(':id')
@ApiOkResponse({ type: UserEntity })
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}

@Patch(':id')
@ApiCreatedResponse({ type: UserEntity })
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateUserDto: UpdateUserDto,
) {
return this.usersService.update(id, updateUserDto);
}

@Delete(':id')
@ApiOkResponse({ type: UserEntity })
remove(@Param('id', ParseIntPipe) id: number) {
return this.usersService.remove(id);
}
}

更新后的控制器使用 @ApiTags 装饰器将端点分组到 users 标签下。它还使用 @ApiCreatedResponse@ApiOkResponse 装饰器来定义每个端点的响应体。

更新后的Swagger API页面应该如下所示:

更新了 Swagger 页面

请随意测试不同的终端节点,以验证它们是否按预期运行。

从响应正文中排除 password字段

虽然 user API 按预期工作,但它存在一个重大的安全漏洞。不同端点的响应体中返回了password字段。

GET /users/:id 显示密码

您有两种方法可以解决此问题:

  1. 从控制器的路由处理程序中手动从响应体中删除密码
  2. 使用拦截器自动从响应体中删除密码

第一种方法容易出错,并且会导致不必要的代码重复。因此,您将使用第二种方法。

使用ClassSerializerInterceptor从响应中移除字段

NestJS中的拦截器允许您挂钩到请求-响应周期,并在路由处理程序执行之前和之后执行额外的逻辑。在这种情况下,您将使用它来从响应体中删除 password 字段。

NestJS有一个内置的ClassSerializerInterceptor,可以用于转换对象。您将使用这个拦截器从响应对象中删除 password 字段。

首先,通过更新 main.ts 文件来全局启用ClassSerializerInterceptor:

// src/main.ts

import { NestFactory, Reflector } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ClassSerializerInterceptor, ValidationPipe } from '@nestjs/common';

async function bootstrap() {
const app = await NestFactory.create(AppModule);

app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));

const config = new DocumentBuilder()
.setTitle('Median')
.setDescription('The Median API description')
.setVersion('0.1')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);

await app.listen(3000);
}
bootstrap();

注意:您也可以将拦截器绑定到方法或控制器上,而不是全局绑定。

ClassSerializerInterceptor 使用 class-transformer 包来定义如何转换对象。在 UserEntity 类中使用 @Exclude() 装饰器来排除密码字段:

// src/users/entities/user.entity.ts

import { ApiProperty } from '@nestjs/swagger';
import { User } from '@prisma/client';
import { Exclude } from 'class-transformer';

export class UserEntity implements User {
@ApiProperty()
id: number;

@ApiProperty()
createdAt: Date;

@ApiProperty()
updatedAt: Date;

@ApiProperty()
name: string;

@ApiProperty()
email: string;

@Exclude()
password: string;
}

如果您再次尝试使用GET /users/:id端点,您会发现密码字段仍然被公开🤔。这是因为目前控制器中的路由处理程序返回的是由Prisma Client生成的User类型。ClassSerializerInterceptor 仅适用于使用 @Exclude() 装饰器装饰的类。在这种情况下,它是 UserEntity 类。因此,您需要更新路由处理程序以返回 UserEntity 类型。

首先,您需要创建一个构造函数来实例化 UserEntity 对象。

// src/users/entities/user.entity.ts

import { ApiProperty } from '@nestjs/swagger';
import { User } from '@prisma/client';
import { Exclude } from 'class-transformer';

export class UserEntity implements User {
constructor(partial: Partial<UserEntity>) {
Object.assign(this, partial);
}

@ApiProperty()
id: number;

@ApiProperty()
createdAt: Date;

@ApiProperty()
updatedAt: Date;

@ApiProperty()
name: string;

@ApiProperty()
email: string;

@Exclude()
password: string;
}

构造函数接收一个对象,并使用 Object.assign()方法将部分对象的属性复制到 UserEntity 实例中。partial 的类型是 Partial<UserEntity> ,这意味着部分对象可以包含UserEntity类中定义的任何属性子集。

接下来,更新 UsersController 的路由处理程序,使其返回 UserEntity 对象,而不是 Prisma.User 对象:

// src/users/users.controller.ts

@Controller('users')
@ApiTags('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}

@Post()
@ApiCreatedResponse({ type: UserEntity })
async create(@Body() createUserDto: CreateUserDto) {
return new UserEntity(await this.usersService.create(createUserDto));
}

@Get()
@ApiOkResponse({ type: UserEntity, isArray: true })
async findAll() {
const users = await this.usersService.findAll();
return users.map((user) => new UserEntity(user));
}

@Get(':id')
@ApiOkResponse({ type: UserEntity })
async findOne(@Param('id', ParseIntPipe) id: number) {
return new UserEntity(await this.usersService.findOne(id));
}

@Patch(':id')
@ApiCreatedResponse({ type: UserEntity })
async update(
@Param('id', ParseIntPipe) id: number,
@Body() updateUserDto: UpdateUserDto,
) {
return new UserEntity(await this.usersService.update(id, updateUserDto));
}

@Delete(':id')
@ApiOkResponse({ type: UserEntity })
async remove(@Param('id', ParseIntPipe) id: number) {
return new UserEntity(await this.usersService.remove(id));
}
}

现在,应该从 response 对象中省略 password。

GET /users/:id 不显示密码

返回文章及其作者

在第一章中,您实现了用于检索单篇文章的GET /articles/:id端点。目前,该端点仅返回文章的authorId,而不返回作者信息。为了获取作者信息,您需要向GET /users/:id端点发出额外的请求。如果您需要同时获取文章及其作者信息,这样做并不理想,因为您需要发出两个API请求。您可以通过在返回Article对象的同时返回作者信息来改进这一点。

数据访问逻辑是在ArticlesService中实现的。更新findOne()方法,以在返回Article对象的同时返回作者信息:

// src/articles/articles.service.ts

findOne(id: number) {
return this.prisma.article.findUnique({
where: { id },
include: {
author: true,
},
});
}

如果您测试 GET /articles/:id 端点,您会发现如果文章有作者,那么作者信息会被包含在响应对象中。但是,有一个问题,password 字段将再次被暴露🤦。

GET /articles/:id 显示密码

这个问题的原因与上次非常相似。目前,ArticlesController 返回的是由Prisma生成的类型实例,而 ClassSerializerInterceptor 是与 UserEntity 类一起工作的。为了解决这个问题,您将更新 ArticleEntity 类的实现,并确保它使用 UserEntity 的实例来初始化 author 属性。

// src/articles/entities/article.entity.ts

import { Article } from '@prisma/client';
import { ApiProperty } from '@nestjs/swagger';
import { UserEntity } from 'src/users/entities/user.entity';

export class ArticleEntity implements Article {
@ApiProperty()
id: number;

@ApiProperty()
title: string;

@ApiProperty({ required: false, nullable: true })
description: string | null;

@ApiProperty()
body: string;

@ApiProperty()
published: boolean;

@ApiProperty()
createdAt: Date;

@ApiProperty()
updatedAt: Date;

@ApiProperty({ required: false, nullable: true })
authorId: number | null;

@ApiProperty({ required: false, type: UserEntity })
author?: UserEntity;

constructor({ author, ...data }: Partial<ArticleEntity>) {
Object.assign(this, data);

if (author) {
this.author = new UserEntity(author);
}
}
}

您再次使用Object.assign()方法将数据对象的属性复制到ArticleEntity实例中。如果author属性存在,它将被初始化为UserEntity的实例。

现在,更新ArticlesController以返回ArticleEntity对象的实例:

// src/articles/articles.controller.ts import { Controller, Get, Post, Body, Patch, Param, Delete, ParseIntPipe, } from '@nestjs/common'; import { ArticlesService } from './articles.service'; import { CreateArticleDto } from './dto/create-article.dto'; import { UpdateArticleDto } from './dto/update-article.dto'; import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; import { ArticleEntity } from './entities/article.entity'; @Controller('articles') @ApiTags('articles') export class ArticlesController { constructor(private readonly articlesService: ArticlesService) {} @Post() @ApiCreatedResponse({ type: ArticleEntity }) async create(@Body() createArticleDto: CreateArticleDto) { return new ArticleEntity( await this.articlesService.create(createArticleDto), ); } @Get() @ApiOkResponse({ type: ArticleEntity, isArray: true }) async findAll() { const articles = await this.articlesService.findAll(); return articles.map((article) => new ArticleEntity(article)); } @Get('drafts') @ApiOkResponse({ type: ArticleEntity, isArray: true }) async findDrafts() { const drafts = await this.articlesService.findDrafts(); return drafts.map((draft) => new ArticleEntity(draft)); } @Get(':id') @ApiOkResponse({ type: ArticleEntity }) async findOne(@Param('id', ParseIntPipe) id: number) { return new ArticleEntity(await this.articlesService.findOne(id)); } @Patch(':id') @ApiCreatedResponse({ type: ArticleEntity }) async update( @Param('id', ParseIntPipe) id: number, @Body() updateArticleDto: UpdateArticleDto, ) { return new ArticleEntity( await this.articlesService.update(id, updateArticleDto), ); } @Delete(':id') @ApiOkResponse({ type: ArticleEntity }) async remove(@Param('id', ParseIntPipe) id: number) { return new ArticleEntity(await this.articlesService.remove(id)); } }

现在,GET /articles/:id返回没有author字段的password对象:

GET /articles/:id 不显示密码

总结和结束语

在本章中,您学习了如何在NestJS应用程序中使用Prisma对关系型数据进行建模。您还了解了 ClassSerializerInterceptor 以及如何使用实体类来控制返回给客户端的数据。

您可以在GitHub仓库的 end-relational-data 分支中找到本教程的完整代码。如果您发现任何问题,请随时在仓库中提出问题或提交拉取请求(PR)。

原文链接:https://www.prisma.io/blog/nestjs-prisma-relational-data-7D056s1kOabc

#你可能也喜欢这些API文章!