Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

File uploading along with other data in Swagger NestJs

I want to send file along with JSON

{
    "comment" : "string",
    "outletId" : 1
}

The help I got from Documentation is

requestBody:
    content:
      multipart/form-data:
        schema:
          type: object
          properties:
            orderId:
              type: integer
            userId:
              type: integer
            fileName:
              type: string
              format: binary

I don't know where to put this schema. I have tried putting it inside @ApiProperty() in DTO as well as in @ApiOperations but could not resolve the issue.

Below is the function I want to capture file content in.

@Post('/punchin')
@ApiConsumes('multipart/form-data')
@ApiOperation({ summary: 'Attendance Punch In' })
@UseInterceptors(CrudRequestInterceptor, ClassSerializerInterceptor, FileInterceptor('file'))
@ApiImplicitFile({ name: 'file' })
async punchInAttendance( @Body() body: PunchInDto, @UploadedFile() file: Express.Multer.File ): Promise<Attendance> {
    const imageUrl = await this.s3FileUploadService.upload(file)
    console.log(body, imageUrl)
    return await this.service.punchInAttendance({
      comment: body.punchInComment,
      outletId: body.outletId,
      imgUrl: imageUrl,
    })
  }
like image 650
Jamshaid Tariq Avatar asked Sep 10 '25 14:09

Jamshaid Tariq


2 Answers

use @ApiBody because body keeps your data.

  @Post('upload')
  @ApiConsumes('multipart/form-data')
  @ApiBody({
    schema: {
      type: 'object',
      properties: {
        comment: { type: 'string' },
        outletId: { type: 'integer' },
        file: {
          type: 'string',
          format: 'binary',
        },
      },
    },
  })
  @UseInterceptors(FileExtender)
  @UseInterceptors(FileInterceptor('file'))
  uploadFile2(@UploadedFile('file') file) {
    console.log(file);
  }

screenshot

I get in console:

{
  fieldname: 'file',
  originalname: 'dart.txt',
  encoding: '7bit',
  mimetype: 'text/plain',
  buffer: <Buffer 20 0a 69 6d  ... 401 more bytes>,
  size: 451,
  comment: 'some comment',
  outletId: 123456
}

Because FileInterceptor removes body params, I used FileExtender interceptor, to pack comment and outletId in file properties.

@Injectable()
export class FileExtender implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const req = context.switchToHttp().getRequest();
    req.file['comment'] = req.body.comment;
    req.file['outletId'] = Number(req.body.outletId);
    return next.handle();
  }
}
like image 148
Daniil Loban Avatar answered Sep 12 '25 04:09

Daniil Loban


The solution that works for me was to create a class containing the API references I will be using and to set one of those fields as the File.

storage-object.dto.ts

export class StorageObjectDto {
    @ApiProperty({ required: false })
    @IsString()
    comment?: string

    @ApiProperty({ type: 'string', format: 'number', required: false })
    @IsNumber()
    outletId?: number

    @ApiProperty({ type: 'string', format: 'binary', required: true })
    file: Express.Multer.File
}

Using the implementation suggested on the nestJs docs, I can extract the file based on the associated key within the object. In this case, the key is file

object.controller.ts

@Version('1')
@Post('upload')
@ApiConsumes('multipart/form-data')
@UseInterceptors(FileInterceptor('file'))
uploadFile(@Body() data: StorageObjectDto, @UploadedFile() file: Express.Multer.File): void {
    console.log({ data, file })
}

Once you call the endpoint you should see the following output in your console log

{
  data: FileDataDto {
    comment: 'This is a test comment',
    outletID: 123
  },
  file: {
    fieldname: 'file',
    originalname: 'placeholder.png',
    encoding: '7bit',
    mimetype: 'image/png',
    buffer: <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 01 f4 00 00 01 f4 04 03 00 00 00 39 f8 c2 b9 00 00 00 1b 50 4c 54 45 cc cc cc 96 96 96 9c 9c 9c ... 1069 more bytes>,
    size: 1119
  }
}
like image 34
Marc van Leeuwen Avatar answered Sep 12 '25 03:09

Marc van Leeuwen