我正在创建简单的服务,它将执行简单的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方法?
我试图在这里理解这个概念。
答案 0 :(得分:3)
首先,@ Carlo Corradini的评论是正确的,您应该看一下class-transformer
和class-validator
库,它们在NestJS管道中也很明显地使用,可以很好地使用。结合TypeORM
。
现在,由于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;
}
在UserResponseDto
顶部的@Exclude()处,我们告诉class-transformer
排除DTO文件中没有@Expose()
装饰器的任何字段当我们从任何其他对象实例化UserResponseDto
时。
然后使用@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;
}
}
您还可以使用 @ 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文件,但会丢失数据验证。
让我知道它是否有帮助或不清楚:)
您将声明一个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
。
您可以同时参考Nest和class-validator的文档,以详细了解传递给ValidationPipe
的选项
因此,可以在处理之前验证传入的参数和有效载荷数据:)