NestJS 我需要 DTO 和实体吗?

Ved*_*ic. 6 node.js typeorm nestjs

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

@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;
}
Run Code Online (Sandbox Code Playgroud)

控制器:

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

服务:

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 });
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

我试图理解这里的概念。

A. *_*tre 14

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

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;
}
Run Code Online (Sandbox Code Playgroud)

这里,通过 顶部的 @Exclude() UserResponseDto,我们告诉当我们从任何其他对象实例化 a 时,排除 DTO 文件中class-transformer没有装饰器的任何字段。然后使用and ,我们告诉类验证器在验证给定字段的类型时验证它们。@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;
  }
}
Run Code Online (Sandbox Code Playgroud)

2:另一种实现方式:

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

请注意,我们必须返回该类的一个实例。如果您返回纯 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);
  }
}
Run Code Online (Sandbox Code Playgroud)

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
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

编辑传入的有效负载验证并转换为正确的 DTO

您可以GetUserByUsernameRequestDto在其中声明一个包含用户名属性的 a,如下所示: 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;
}
Run Code Online (Sandbox Code Playgroud)

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

在这里,我们使用Nest 的管道概念- @UsePipes() - 来完成工作。以及 Nest 的内置功能ValidationPipe。您可以参考Nest类验证器本身的文档,以了解有关传递给类验证器的选项的更多信息。ValidationPipe

这样,您的传入参数和有效负载数据就可以在处理之前得到验证:)