【问题标题】:NestJS Do I need DTO's along with entities?NestJS 我需要 DTO 和实体吗?
【发布时间】:2020-11-24 09:20:39
【问题描述】:

我正在创建简单的服务,它将执行简单的 CRUD。 到目前为止,我有 entity 用户:

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  username: string;

  @Column({ name: "first_name" })
  firstName: string;

  @Column({ name: "last_name" })
  lastName: string;

  @Column({ name: "date_of_birth" })
  birthDate: string;
}

控制器:

import { Controller, Get,  Query } from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('api/v1/backoffice')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get(':username')
  findOne(@Query('username') username: string) {
    return this.usersService.findByUsername(username);
  }
}

服务:

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, getRepository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly usersRepository: Repository<User>,
  ) {}


  findByUsername(username: string): Promise<User | undefined> {
    return this.usersRepository.findOne({ username });
  }
}

通过这个基本示例,我从数据库返回值,其中一些列被重命名:first_name --> firstName

它确实符合我的目的,但在很多地方,我看到 DTO 正在被使用。我知道我没有做正确的事情,我也应该开始使用它。 我将如何在我的示例中使用 DTO 方法?

我试图在这里掌握这个概念。

【问题讨论】:

  • 这比您想象的要容易得多。看看这个例子,你可以在 3 行代码中使用 TypeORM + class-transformer + class-validator -> HERE。还有一个库可以将两者组合成一个操作 - HERE

标签: node.js nestjs typeorm


【解决方案1】:

首先,@Carlo Corradini 的评论是正确的,你应该看看 class-transformerclass-validator 库,它们也在 NestJS 管道的底层使用,并且可以很好地与 @987654327 结合使用@。

1:根据@Carlo Corradini 的评论及其相应链接

现在,由于您的 DTO 实例是您希望向消费者公开的数据的表示形式,因此您必须在检索到用户实体后对其进行实例化。

  1. 创建一个新的user-response.dto.ts 文件并在其中声明一个您将导出的UserResponseDto 类。假设您想公开之前检索到的 User 实体中的所有内容,代码如下所示

user-response.dto.ts

import { IsNumber, IsString } from 'class-validator';
import { Exclude, Expose } from 'class-transformer';

@Exclude()
export class UserResponseDto {
  @Expose()
  @IsNumber()
  id: number;

  @Expose()
  @IsString()
  username: string;

  @Expose()
  @IsString()
  firstName: string;

  @Expose()
  @IsString()
  lastName: string;

  @Expose()
  @IsString()
  birthDate: string;
}

这里@Exclude() 在UserResponseDto 的顶部,我们告诉class-transformer 在我们将实例化时排除DTO 文件中没有@Expose() 装饰器的任何字段来自任何其他对象的UserResponseDto。 然后使用@IsString()@IsNumber(),我们告诉类验证器在我们验证给定字段的类型时验证它们。

  1. 将您的 User 实体转换为 UserResponseDto 实例:
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, getRepository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly usersRepository: Repository<User>,
  ) {}


  async findByUsername(username: string): Promise<User | undefined> {
    const retrievedUser = await this.usersRepository.findOne({ username });

    // instantiate our UserResponseDto from retrievedUser
    const userResponseDto = plainToClass(UserResponseDto, retrievedUser);

    // validate our newly instantiated UserResponseDto
    const errors = await validate(userResponseDto);
    if (errors.length) {
        throw new BadRequestException('Invalid user',
this.modelHelper.modelErrorsToReadable(errors));
    }
    return userResponseDto;
  }
}

2:另一种实现方式:

您还可以使用 @nestjs/common 中的 ClassSerializerInterceptor interceptor 自动将您返回的 Entity 实例从服务转换为控制器中定义的正确返回类型方法。这意味着您甚至不必费心在您的服务中使用 plainToClass 并让 Nest 的拦截器自己完成工作,官方文档中说明了一些细节

请注意,我们必须返回该类的实例。如果您返回一个 纯 JavaScript 对象,例如 { user: new UserEntity() }, 对象不会被正确序列化。

代码如下所示:

users.controller.ts

import { ClassSerializerInterceptor, Controller, Get,  Query } from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('api/v1/backoffice')
@UseInterceptors(ClassSerializerInterceptor) // <== diff is here
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get(':username')
  findOne(@Query('username') username: string) {
    return this.usersService.findByUsername(username);
  }
}

users.service.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, getRepository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly usersRepository: Repository<User>,
  ) {}


  async findByUsername(username: string): Promise<User | undefined> {
    return this.usersRepository.findOne({ username }); // <== must be an instance of the class, not a plain object
  }
}

最后的想法: 使用最新的解决方案,您甚至可以在您的用户实体文件中使用class-transformer 的装饰器,而不必声明 DTO 文件,但您会丢失数据验证。

如果有帮助或不清楚的地方,请告诉我:)

使用传入的有效负载验证和转换为适当的 DTO 进行编辑

你可以声明一个带有用户名属性的GetUserByUsernameRequestDto,如下所示: get-user-by-username.request.dto.ts

import { IsString } from 'class-validator';
import { Exclude, Expose } from 'class-transformer';

@Exclude()
export class GetUserByUsernameRequestDto {
  @Expose()
  @IsString()
  @IsNotEmpty()
  username: string;
}

users.controller.ts

import { ClassSerializerInterceptor, Controller, Get,  Query } from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('api/v1/backoffice')
@UseInterceptors(ClassSerializerInterceptor) // <== diff is here
@UsePipes( // <= this is where magic happens :)
    new ValidationPipe({
        forbidUnknownValues: true,
        forbidNonWhitelisted: true,
        transform: true
    })
)
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get(':username')
  findOne(@Param('username') getUserByUsernameReqDto: GetUserByUsernameRequestDto) {  
    return this.usersService.findByUsername(getUserByUsernameReqDto.username);
  }
}

这里我们使用Nest's pipes concept - @UsePipes() - 来完成工作。以及来自 Nest 的内置 ValidationPipe。 您可以参考Nestclass-validator 自己的文档,以了解有关传递给ValidationPipe 的选项的更多信息

因此,这样您的传入参数和有效负载数据可以在处理之前进行验证:)

【讨论】:

  • 感谢您的宝贵时间和精彩的解释。玩了一圈之后,我得出了同样的结论。由于这是一个唯一的搜索控制器,我可以在没有 DTO 的情况下执行此操作,但我需要对用户名属性(来自用户)进行验证,而实现它的唯一方法是使用 DTO 中的验证类。
  • 好吧,那么需要验证的是传入的 DTO,返回的 DTO 可能不需要验证,而只需使用您要公开的正确字段进行实例化。我将使用 Pipes 来编辑我的答案,以验证传入的有效负载并将其转换为 DTO
  • 希望我能多次支持您的回答。再次感谢!
  • 我正在寻找一种解决方案,将实体 - 序列化器和验证器定义在一个地方,如果将字段添加到实体并更新所有 dto,则不会出现问题。
猜你喜欢
  • 2020-07-23
  • 2019-05-01
  • 2021-12-19
  • 1970-01-01
  • 2021-06-16
  • 1970-01-01
  • 2014-07-17
  • 2021-12-15
  • 1970-01-01
相关资源
最近更新 更多