Во-первых, комментарий @Carlo Corradini правильный, вам следует взглянуть на библиотеки class-transformer
и class-validator
, которые также заметно используются под капотом в каналах Nest JS и могут быть красиво объединены с 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()
в файле DTO, когда мы будем создавать экземпляр UserResponseDto
из любого другого объекта. Затем с помощью @IsString()
и @IsNumber()
мы говорим классу-валидатору проверять типы данных полей при их проверке.
Преобразуйте свой объект 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
перехватчик из @ nestjs / common , чтобы автоматически преобразовывать ваши возвращенные 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 - @UsePipes () - для выполнения работы. вместе со встроенным ValidationPipe
от Nest. Вы можете обратиться к документации как из Nest , так и из class-validator , чтобы узнать больше о параметрах передачи ValidationPipe
Таким образом, ваш входящий параметры и данные полезной нагрузки могут быть проверены перед обработкой :)