Node
Nest.js
Swagger

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를 인식하지 못할 수 있습니다.

  1. 제네릭 응답 DTO: PaginatedResponseDto<T>와 같이 제네릭을 사용하는 응답 DTO의 경우, T에 어떤 DTO가 올지 미리 알 수 없습니다.
  2. 복합 스키마: 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) },
              },
            },
          },
        ],
      },
    }),
  );
  1. ApiExtraModels(PaginatedResponseDto, dataDto): 이 데코레이터를 통해 PaginatedResponseDto와 이 함수로 전달될 특정 dataDto(예: UserDto, PostDto 등)를 Swagger 스키마에 포함시킵니다.
  2. $ref: getSchemaPath(PaginatedResponseDto): allOf를 통해 공통 페이징 응답 형태인 PaginatedResponseDto의 스키마를 참조합니다.
  3. $ref: getSchemaPath(dataDto): 실제 데이터의 형태인 dataDto의 스키마를 nodes 배열의 아이템으로 참조합니다.

만약 ApiExtraModels 선언이 없다면, Swagger는 PaginatedResponseDtodataDto의 존재를 알지 못하므로 $ref 참조가 실패하여 스키마를 올바르게 생성하지 못합니다.