Skip to content

[BE] @nestjs swagger로 이미지 업로드 form을 어떻게 만들지?

박경미 edited this page Dec 14, 2023 · 5 revisions

목차

  1. 이슈
  2. 삽질 과정
  3. 시도A @UploadedFile() 사용 ❌
  4. 시도B @UploadedFile() 사용 & dto에서 image 제거
  5. 시도C @UploadedFile() 사용 & dto에서 image 사용
  6. 최종 결론

이슈

문제 상황

이상 현실(문제)
image image
  • Swagger에서 왼쪽(이상)처럼 파일을 전송하는 칸 나와야 하지만, 실제론 텍스트 입력 칸이 나온다.
  • 원래 Nest가 알아서 라우터 핸들러의 매개변수를 확인하고 자동으로 Swagger를 구성해준다.
    • 예를 들어, @nestjs/swagger는 create 메서드의 매개변수인 CreateTimelineDto를 자동으로 Swagger 문서에 반영해준다.
    @Post()
    @UseInterceptors(FileInterceptor('image'))
    @ApiOperation({ summary: '타임라인 생성', description: '...'})
    async create(
      @UploadedFile() image: Express.Multer.File,
      @Body() createTimelineDto: CreateTimelineDto
    )
  • 그러나 @UploadedFile() 데코레이터로 받아들이는 image에 대한 입력칸은 Swagger에 자동으로 나오지 않는다.

현재 코드

  • 컨트롤러

    @Post()
    @UseInterceptors(FileInterceptor('image'))
    @ApiOperation({ summary: '타임라인 생성', description: '...'})
    @ApiConsumes('multipart/form-data')
    async create(
      @UploadedFile() image: Express.Multer.File,
      @Body() createTimelineDto: CreateTimelineDto
    ): Promise<Timeline> { }
  • dto

    export class CreateTimelineDto {
      @ApiProperty({ example: '서울역에서 출발~', maxLength: 14, minLength: 1 })
      @IsString()
      @MinLength(1)
      @MaxLength(14)
      title: string;
    
      @ApiProperty({ required: false })
      @IsOptional()
      @IsString()
      image: string;
    }



삽질 과정

구글링

  • @ApiBody() 데코레이터를 사용하고,
  • schema의 properties에서 image: { type: 'string', format: 'binary' }를 사용하면 된다고 함!
@Post()
@UseInterceptors(FileInterceptor('image'))
@ApiOperation({ summary: '타임라인 생성', description: '...'})
@ApiConsumes('multipart/form-data')
@ApiBody({
    description: 'File upload with additional data',
    schema: {
      type: 'object',
      properties: {
        image: {
          type: 'string',
          format: 'binary',
        },
      },
    },
  })
async create(
  @UploadedFile() image: Express.Multer.File,
  @Body() createTimelineDto: CreateTimelineDto
): Promise<Timeline> { }

반영 결과 ➡ createTimelineDto의 프로퍼티는 어디로?

image

  • createTimelineDto에 정의된 다른 프로퍼티는 모두 사라지고,
  • 오직 파일 업로드하는 image 프로퍼티만 덜렁 남겨짐,,

보완하자니, 이게 웬 노가다

  • 사라진 나머지 프로퍼티를 다시 Swagger에 출력되게 하려면,
  • @ApiBody > schema > properties에 dto에 정의된 모든 프로퍼티를 작성하라고 함.
@ApiBody({
    description: 'File upload with additional data',
    schema: {
      type: 'object',
      properties: {
        image: {
          type: 'string',
          format: 'binary',
        },
        createTimelineDto: {
          type: 'object',
          properties: {
            title: { type: 'string', example: '노가다' },
            day: { type: 'number' },
            // Add other properties as needed
          },
        },
      },
    },
  })
  • 노가다이기도 하지만...

image

  • CreateTimelineDto에서 @ApiProperty 데코레이터로 열심히 작성한 모든 내용이 사라진다.
    @ApiProperty({ example: '서울역에서 출발~', maxLength: 14, minLength: 1 })
    @IsString()
    @MinLength(1)
    @MaxLength(14)
    title: string;
    
    @ApiProperty({
     required: false,
     example: 'GS25 서울역점',
     description: '장소 이름',
     maxLength: 50,
    })
    @IsOptional()
    @IsString()
    place: string;
  • 따라서 dto 파일의 @ApiProperty 데코레이터에 적은 내용을 다시 @ApiBody에 작성해야 했다.
  • 내가 example도 넣고, length도 넣고, required 속성도 넣었는데, 이걸 다시 하라고? 이게 무슨 헛짓거리인가.

❗ 이건 아니라는 강한 의심 ⇒ 정말 방법이 없는 것일까?




시도A @UploadedFile() 사용 ❌

이미지 파일 자체가 행방불명

코드

  • 컨트롤러

    • @UploadedFile 데코레이터 제거
    export class TimelinesController {
      constructor(private readonly timelinesService: TimelinesService) {}
    
      @Post()
      @UseInterceptors(FileInterceptor('image')) // 이거 없애면 아래와 같은 일이..
      @ApiOperation({
        summary: '타임라인 생성',
        description: '사용자가 입력한 정보를 토대로 새로운 타임라인을 생성합니다.',
      })
      @ApiConsumes('multipart/form-data')
      async create(
        @Body() createTimelineDto: CreateTimelineDto
      ): Promise<Timeline> { }
    }
    • @UploadedFile 사용 안 한다고 @UserInterceptor까지 제거하면, 모든 입력 데이터가 먹히지 않고 빈 dto가 전달된다.

      image

  • dto

    export class CreateTimelineDto {
      @ApiProperty({
        required: false,
        type: 'file',
        description: '업로드하는 사진',
      })
      @IsOptional()
      image: Express.Multer.File;
    }

Swagger UI

image

Swagger 테스트

image 미전송 image 전송
image image
  • 이미지 전송 시 dto에서 image 필드가 사라진다. 사진을 담은 image 프로퍼티는 dto에 정의된 image로 들어가는 게 아니니까~! (아마 intercept 당한 듯)
  • 그럼 사진은 어디로 갔는가? 모릅니다. 그냥 사라져버렸어요.



시도B @UploadedFile() 사용 & dto에서 image 제거

swagger에서 파일 테스트 불가

코드

  • 컨트롤러

    @Post()
    @UseInterceptors(FileInterceptor('image'))
    @ApiOperation({
      summary: '타임라인 생성',
      description: '사용자가 입력한 정보를 토대로 새로운 타임라인을 생성합니다.',
    })
    @ApiConsumes('multipart/form-data')
    async create(
      @UploadedFile() image: Express.Multer.File,
      @Body() createTimelineDto: CreateTimelineDto
    ): Promise<Timeline> { }
  • dto

    • 아래 코드 제거
    export class CreateTimelineDto {
      @ApiProperty({
        required: false,
        type: 'file',
        description: '업로드하는 사진',
      })
      @IsOptional()
      image: Express.Multer.File;
    }

Swagger UI

  • 사진을 첨부할 수 있는 image 필드 자체가 사라졌다.

image

POSTMAN 테스트

image 첨부 image 미첨부
image image
- dto의 image 필드 없음
- @UploadedFile()로 받은 image 존재
image가 있어야 한다는 에러

Swagger 테스트

Swagger에서 image 프로퍼티가 존재하지 않으므로, 이미지 미첨부 상황만 테스트 가능

image

  • 요청은 보내지지만, @UploadedFile() image에는 아무런 값이 들어 있지 않음



시도C @UploadedFile() 사용 & dto에서 image 사용

코드 (시도1, 2의 합본)

  • 컨트롤러

    @Post()
    @UseInterceptors(FileInterceptor('image'))
    @ApiOperation({
      summary: '타임라인 생성',
      description: '사용자가 입력한 정보를 토대로 새로운 타임라인을 생성합니다.',
    })
    @ApiConsumes('multipart/form-data')
    async create(
      @UploadedFile() image: Express.Multer.File,
      @Body() createTimelineDto: CreateTimelineDto
    ): Promise<Timeline> { }
  • dto

    export class CreateTimelineDto {
      @ApiProperty({
        required: false,
        type: 'file',
        description: '업로드하는 사진',
      })
      @IsOptional()
      image: Express.Multer.File;
    }

Swagger UI

image

POSTMANT 테스트

image 미첨부 image 첨부
image image

Swagger 테스트

image 미첨부 image 첨부
image image



최종 결론

코드

  • controller

    @Put(':id')
    @UseInterceptors(FileInterceptor('image'))
    @ApiOperation({
      summary: 'id에 해당하는 타임라인 수정',
      description: 'id에 해당하는 타임라인을 수정합니다.',
    })
    @ApiConsumes('multipart/form-data')
    @ApiOkResponse({ schema: { example: update_OK } })
    async update(
      @Param('id', ParseUUIDPipe) id: string,
      @UploadedFile() image: Express.Multer.File,
      @Body() updateTimelineDto: UpdateTimelineDto
    ) { }
  • dto

    export class CreateTimelineDto {
      @ApiProperty({ example: '서울역에서 출발~', maxLength: 14, minLength: 1 })
      @IsString()
      @MinLength(1)
      @MaxLength(14)
      title: string;
    
      @ApiProperty({
        required: false,
        type: 'file',
        description: '업로드하는 사진',
      })
      @IsOptional()
      image: Express.Multer.File;
    }

⚠ 주의사항

  • @IsOptional이 빠지면 안 됨! 그러면 이미지를 첨부하지 않았을 때 property image should not exist 에러 발생
  • Express.Multer.File 타입의 프로퍼티 이름(image)는 dto@UploadedFile() 에서 모두 동일해야 한다.

캡처

image 미전송 image 전송
image image
Clone this wiki locally