我正在创建简单的服务,它将做简单的 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-transformer
和 class-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 在我们验证它们时验证给定字段的类型。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/