Код Ревью
Общее
- Папки по конвенции nest именуются с маленькой буквы.
- Обычно
DTO
кладут внутрь модуля, т.е. к примеру, а не делают для этого shared
папку в корне src
:
.
└── src/
├── invitations/
│ ├── dto/
│ │ ├── create-invitation.dto.ts
│ │ └── update-invitation.dto.ts
│ ├── invitations.module.ts
│ ├── invitations.service.ts
│ └── invitations.controller.ts
└── ...
-
В Nest
, если имя файла состоит из более чем одного слова, то они разделяются дефисом, а не camelCase
. Например, create-news-body.dto.ts
-
Не нужно писать public
перед методами, это bad practice
во многих конвенциях по TS
.
Основные моменты
- Мощь
DTO
используется лишь на 10%. В твоём коде DTO
— всего лишь тип данных для автокомплита и сваггера. Главная фишка нестовских DTO
— это ультрадекларативная валидация входящих параметров при помощи class-validator
и их трансформация при помощи class-transformer
.
Для начало надо их установить: yarn add class-transformer@latest class-validator@latest
.
Затем глобально зарегистрировать ValidationPipe
(чтобы не писать везде @UsePipes()
):
/* main.ts */
...
app.useGlobalPipes(
new ValidationPipe({
transform: true, // Стараться приводить всё к указанным типам данным
whitelist: true, // Поля, которых нет в DTO будут удалены
forbidNonWhitelisted: true, // Клиенту будет запрещено отправлять поля, которых нет в DTO
always: true, // Проверка независимо от группы (почитай документацию class-validator)
forbidUnknownValues: true // Название говорит само за себя
})
);
Теперь возьмём какой-нибудь DTO
, например create-collection-body.dto
, и навесим на него декораторы валидации:
/* create-collection-body.dto.ts */
import { ApiProperty } from '@nestjs/swagger'
import { CollectionType } from '@enums/collectionType.enum'
import { IsString, IsEnum, IsNotEmpty } from 'class-validator'
export class CreateCollectionBodyDto {
@ApiProperty({ name: 'name' })
@IsNotEmpty()
@IsString()
name: string
@ApiProperty({ name: 'type', enum: CollectionType })
@IsEnum(CollectionType)
type: CollectionType
@ApiProperty({ name: 'anime_list', default: 'id,id,id,id' })
@IsString()
anime_list: string
}
Далее, необходимо поменять edit-collection-body.dto
. Во-первых, все edit
меняй на update
, во-вторых, нет смысла дублировать все эти поля, когда ты используешь данный подход.
import { PartialType, OmitType } from '@nestjs/swagger' // если не используешь swagger, импортируй из @nestjs/common
import { CollectionType } from '@enums/collectionType.enum'
export class UpdateCollectionBodyDto extends PartialType(
OmitType(CreateCollectionBodyDto, ['anime_list'] as const),
) {}
Если бы тебе просто нужно было сделать DTO
с идентичными полями, но при этом сделать их необязательными, то тогда тебе нужно было бы просто наследоваться от PartialType()
, но так как в этом DTO
отсутствует поле anime_list
, то нужен ещё один mapped type
— OmitType()
.
Теперь тебе остаётся только попробовать отправить какие-либо некорректные данные и ты сам всё увидишь.
- Пагинацию можно также сделать при помощи
DTO
. Сделай pagination.dto
, определи там поля limit
иpage
с типом number
, добавь нужную валидацию и используй его как ты обычно используешь DTO
. Таким образом ты ещё и избавляешься от нужды приводить это к типу number
, так как этим занимается class-transformer и опция transform: true
, которую мы включили ранее.
export class PaginationDto {
@ApiProperty()
@IsOptional()
@IsNumber()
@IsPositive()
@Max(100)
limit: number;
@ApiProperty()
@IsOptional()
@IsNumber()
@IsPositive()
@Min(1)
page: number;
}
...
async getAllUsers(
@Query() { limit, page }: PaginationDto
): Promise<GetAllUsersType[]> {
return this.userService.getAll(limit ?? 15, page ?? 1)
}
- Странное название «
AccessJwt
», назвал бы Auth
хотя бы...
Мелочи
- НИКОГДА НЕ ИСПОЛЬЗУЙ ТАБЫ. ТОЛЬКО ПРОБЕЛЫ. КОД НА ГИТХАБЕ НАЧИНАЕТ ЕХАТЬ В ЕБЕНЯ.
- Я никогда не видел, чтобы использовались такие user-frendly ошибки, как тут, обычно, в
REST
отвечают статусом. Т.е. если на GET ничего не найдено, то просто выбрасывай NotFound
безо всяких "Пользователь не найден". Однако подход с class-validator
позволяет выбрасывать информативные BadRequest
.