728x90
백엔드 개발자로서 API 를 만들다 보면 `GET` 요청에 따른 response 에 pagination, count 등과 같이 공통으로 들어가야 하는 필드들을, 각 dto 마다 한땀한땀 추가해주며 고통받았던 기억이 있을 것이다.
만약 이 표준 응답에 새 필드를 추가해줘야 한다면?
또 모든 dto 를 수동으로 업데이트해줘야 할 것이고, 어느덧 샷건 치고 있는 내 자신을 발견할 수 있다.
generic 을 적용해보자!
이 고통을 타개할 방법이 무엇이 있을까 생각해보다가 generic 을 적용해보기로 했다.
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsNumber } from 'class-validator';
class BaseResponseDTO<T> {
@ApiProperty({
description: 'Numbers of returend items',
example: 2,
})
@IsNumber()
count: number;
pagination: {
@ApiProperty({ description: '총 결과수' })
@IsNumber()
readonly totalRow: number;
@ApiProperty({ description: '현 페이지의 결과수' })
@IsNumber()
readonly pageRow: number;
@ApiProperty({ description: '다음 페이지 있는지 여부' })
@IsBoolean()
readonly hasNext: boolean;
@ApiProperty({ description: '총 페이지수' })
@IsNumber()
readonly totalPage: number;
@ApiProperty({ description: '현 페이지' })
@IsNumber()
readonly page: number;
@ApiProperty({ description: '요청한 페이지당 사이즈' })
@IsNumber()
readonly size: number;
};
@ApiProperty({
type: [T],
})
list: T[];
}
완벽한 코드라고 생각했으나, 안타깝게도 이 코드는 동작하지 않았다.
`nestjs/swagger` 는 TypeScript 리플렉션 기능을 사용하는데, 이는 generic 과 함께 동작하지 않는다고 한다.
Mixins 클래스를 사용해보자!
나의 고통은 영원히 지속되는 것인가 하는 생각에 괴로워하고 있던 찰나, 구세주 글을 발견했다.
Mixin 패턴은 특정 기능이나 속성을 여러 클래스에 공통으로 추가할 때 사용하는 디자인 패턴으로, NestJS 에서는 이 Mixin 패턴을 통해 generic 을 사용하는 경우나 반복되는 공통 속성을 쉽게 추가할 수 있다.
▷ response.dto.ts
import { mixin } from '@nestjs/common';
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsNumber } from 'class-validator';
class PaginationResponse {
@ApiProperty({ description: '총 결과수' })
@IsNumber()
readonly totalRow: number;
@ApiProperty({ description: '현 페이지의 결과수' })
@IsNumber()
readonly pageRow: number;
@ApiProperty({ description: '다음 페이지 있는지 여부' })
@IsBoolean()
readonly hasNext: boolean;
@ApiProperty({ description: '총 페이지수' })
@IsNumber()
readonly totalPage: number;
@ApiProperty({ description: '현 페이지' })
@IsNumber()
readonly page: number;
@ApiProperty({ description: '요청한 페이지당 사이즈' })
@IsNumber()
readonly size: number;
}
type Constructor<T = object> = new (...args: any[]) => T;
export function withListResponse<T extends Constructor>(Base: T) {
class BasicPaginationResDto {
@ApiProperty({
nullable: false,
type: [Base],
})
list!: T[];
@ApiProperty({ description: '페이지' })
readonly pagination: PaginationResponse;
}
return mixin(BasicPaginationResDto);
}
▷ post.controller.ts
import { withListResponse } from './libs/dto/response.dto';
import { PostDto } from './dto/post.dto';
import { PostService } from './post.service';
@ApiTags('API')
@Controller('post')
export class PostController {
constructor(private readonly service: PostService) {}
@ApiOperation({ summary: 'post list' })
@ApiOkResponse({
type: () => withListResponse(PostDto),
})
@Get()
async list() {
return this.service.list();
}
}
References
https://www.inextenso.dev/how-to-generate-generic-dtos-with-nestjs-and-swagger
728x90
'TypeScript' 카테고리의 다른 글
[TypeScript] as const (0) | 2024.10.16 |
---|---|
[Nest.js] 카프카(Kafka) 세팅하기 (0) | 2023.12.07 |
[Nest.js] DataDog 를 이용하여 trace id, span id 심기 (0) | 2023.10.17 |
[Nest.js] 버전 별로 스웨거 관리 (0) | 2023.01.11 |
[TypeORM] 데코레이터 - Entity (1) | 2022.09.19 |