NestJS Swagger와 ApiExtraModels
결론
@ApiExtraModels()는 NestJS Swagger(OpenAPI) 스키마를 생성할 때, API의 응답/요청 타입으로 직접 참조되지는 않지만 스키마에 반드시 포함되어야 하는 DTO(Data Transfer Object) 클래스들을 명시적으로 등록하는 데코레이터입니다.
이를 통해 제네릭(Generic) 클래스나 allOf, oneOf 등 복합 스키마에서 동적으로 사용되는 모델들이 OpenAPI 문서의 components/schemas에 정상적으로 정의되어 $ref를 통해 참조될 수 있습니다. NestJS 공식 문서 - OpenAPI (opens in a new tab)
NestJS 공식 문서에서는 "To define an extra model that is not directly referenced in your controllers, use the @ApiExtraModels() decorator." 라고 명시하여, 컨트롤러에서 직접 참조되지 않는 추가 모델을 정의할 때 사용한다고 설명합니다.
사용 목적
NestJS의 Swagger 모듈은 컨트롤러의 메서드 시그니처와 데코레이터(@Body(), @ApiResponse() 등)를 분석하여 API 문서를 자동으로 생성합니다. 하지만 다음과 같은 경우 특정 DTO를 인식하지 못할 수 있습니다.
- 제네릭 응답 DTO:
PaginatedResponseDto<T>와 같이 제네릭을 사용하는 응답 DTO의 경우,T에 어떤 DTO가 올지 미리 알 수 없습니다. - 복합 스키마:
allOf,oneOf,anyOf를 사용하여 여러 DTO를 조합할 때, 조합의 대상이 되는 DTO들이 직접적으로 노출되지 않습니다.
이때 @ApiExtraModels()를 사용해 해당 DTO들을 Swagger 스캐너에 명시적으로 알려주면, 스캐너는 이 모델들을 components/schemas에 등록합니다. 그 결과, getSchemaPath를 통한 $ref 참조가 유효하게 되어 완전한 API 문서를 생성할 수 있습니다.
코드 예시 분석
사용자께서 제공한 ApiOkResponsePaginated 코드는 @ApiExtraModels의 전형적인 사용 사례입니다.
import { Type, applyDecorators } from '@nestjs/common';
import {
ApiExtraModels,
ApiOkResponse,
getSchemaPath,
} from '@nestjs/swagger';
import { PaginatedResponseDto } from './paginated-response.dto';
export const ApiOkResponsePaginated = <DataDto extends Type<unknown>>(
dataDto: DataDto,
) =>
applyDecorators(
// 1. Swagger에 PaginatedResponseDto와 dataDto를 추가 모델로 등록
ApiExtraModels(PaginatedResponseDto, dataDto),
ApiOkResponse({
schema: {
allOf: [
// 2. 등록된 PaginatedResponseDto를 $ref로 참조
{ $ref: getSchemaPath(PaginatedResponseDto) },
{
properties: {
nodes: {
type: 'array',
// 3. 등록된 dataDto를 $ref로 참조
items: { $ref: getSchemaPath(dataDto) },
},
},
},
],
},
}),
);ApiExtraModels(PaginatedResponseDto, dataDto): 이 데코레이터를 통해PaginatedResponseDto와 이 함수로 전달될 특정dataDto(예:UserDto,PostDto등)를 Swagger 스키마에 포함시킵니다.$ref: getSchemaPath(PaginatedResponseDto):allOf를 통해 공통 페이징 응답 형태인PaginatedResponseDto의 스키마를 참조합니다.$ref: getSchemaPath(dataDto): 실제 데이터의 형태인dataDto의 스키마를nodes배열의 아이템으로 참조합니다.
만약 ApiExtraModels 선언이 없다면, Swagger는 PaginatedResponseDto나 dataDto의 존재를 알지 못하므로 $ref 참조가 실패하여 스키마를 올바르게 생성하지 못합니다.