NestJS是否需要DTO和实体?

时间:2020-08-04 09:43:30

标签: node.js nestjs typeorm

我正在创建简单的服务,它将执行简单的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方法?

我试图在这里理解这个概念。

1 个答案:

答案 0 :(得分:3)

首先,@ 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;
}

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

  1. 将您的用户实体投射到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的选项

因此,可以在处理之前验证传入的参数和有效载荷数据:)