node.js - NestJS 我需要 DTO 和实体吗?

标签 node.js nestjs typeorm

我正在创建简单的服务,它将做简单的 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;
}
Controller :
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 方法?
我试图在这里掌握这个概念。

最佳答案

首先,@Carlo Corradini 的评论是对的,你应该看看 class-transformerclass-validator libs,在 NestJS 管道中也明显使用,并且可以很好地与 TypeORM 结合使用。 .
1:根据@Carlo Corradini 的评论及其相应链接
现在,由于您的 DTO 实例是您要向消费者公开的数据的表示,因此您必须在检索用户实体后实例化它。

  • 新建 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排除任何没有 @Expose() 的字段当我们将实例化 UserResponseDto 时,DTO 文件中的装饰器从任何其他对象。
    然后用 @IsString()@IsNumber() ,我们告诉 class-validator 在我们验证它们时验证给定字段的类型。
  • 将您的 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:另一种实现方式:
    您也可以使用 ClassSerializerInterceptor interceptor来自 @nestjs/common 自动转换您返回的 Entity将服务实例转换为 Controller 方法中定义的正确返回类型。这意味着你甚至不必费心在你的服务中使用 plainToClass 并让工作由 Nest 的拦截器本身完成,如官方文档所述

    Note that we must return an instance of the class. If you return a plain JavaScript object, for example, { user: new UserEntity() }, the object won't be properly serialized.


    代码如下所示: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的装饰器在您的 User 实体文件中,不必声明 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() - 完成工作。以及内置 ValidationPipe也来自 Nest。
    您可以引用 Nest 中的文档和 class-validator自己了解有关传递给 ValidationPipe 的选项的更多信息
    因此,通过这种方式,您可以在处理之前验证传入的参数和有效载荷数据:)

    关于node.js - NestJS 我需要 DTO 和实体吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63244163/

    相关文章:

    javascript - 如何在 nodejs 中使用请求 npm 发送原始放置数据

    node.js - 如何使用node js将一般日期转换为cron作业时间?

    sql - typeorm:如何正确使用IsNotNull/IsNull?

    javascript - 将一个提供程序注入(inject)另一个提供程序,相同的模块 #1250

    sql - TypeORM:所有权方面如何运作?

    javascript - 如何从另一个js文件发出事件?

    node.js - npm 更新破坏了 npm

    nestjs - 如何在一个 DTO 中编写@Param 和@Body

    node.js - 即使在带有 PG 的 MacOS 上等待,typeorm createConnection 也返回 Pending

    javascript - 如何在 TS 和 TypeORM 中创建泛型函数?