🐈

NestJS + OpenAPI でのファイルアップロード

こんにちは。

株式会社アルダグラムの安政です。

NestJS 内の OpenAPI の機構を用いて、日々開発しています。

ファイルをアップロードする処理を実装する機会がありましたので、実装方法などを紹介したいと思います。

この記事では、

  • ファイルアップロードの基本的な処理
  • 受け取るファイルの MimeType を用いてのバリデーション処理
  • OpenAPI (Swagger)上から利用するためのデコレーター定義

の順でご紹介します。

また実装にあたってはこちらのサイトを参考にさせていただきました。

https://notiz.dev/blog/type-safe-file-uploads

ファイルアップロードの基本処理

ファイルアップロードに関する公式の記事としてはこちらです。

https://docs.nestjs.com/techniques/file-upload

Basic example に import を加えたソースコードがこちらになります。

import {
  Controller,
  Post,
  UploadedFile,
  UseInterceptors
} from '@nestjs/common'

import { FileInterceptor } from '@nestjs/platform-express'

@Post('upload')
@UseInterceptors(FileInterceptor('file'))
uploadFile(@UploadedFile() file: Express.Multer.File) {
  console.log(file);
}

NestJS を Express ベースで動かしているので、Multer を用いてリクエストを受け取ることになります。

Interceptor の機構( UseInterceptors )を用いることで、リクエストを受け取る前にロジックをバインドすることができるので、ファイルの取得処理を挟むことができています。

ファイルを受け取るロジックは FileInterceptor 内にて書かれています。

これだけの記載でファイルを受け取る処理が完結するのはとてもありがたいです。

ファイルの MimeType によるバリデーション処理

FileInterceptor に対して、 fileFilter を指定することでバリデーション処理を加えることができます。

以下が filter を指定したコードです。

import { fileMimetypeFilter } from './file-mimetype-filter'

@Post('upload')
@UseInterceptors(
  FileInterceptor('file', { fileFilter: fileMimetypeFilter('image') })
)
uploadFile(@UploadedFile() file: Express.Multer.File) {
  console.log(file);
}

fileMimetypeFilter を作成・指定しています。

今回の場合は image という文字列が mimeType に含まれるファイルのみを通す処理となっています。

# fileMimetypeFilter

import { UnsupportedMediaTypeException } from '@nestjs/common'
import Request from 'express-http-context'

// 指定された MimeType のみ通す
export function fileMimetypeFilter(...mimetypes: string[]) {
  return (
    request: Request,
    file: Express.Multer.File,
    callback: (error: Error | null, acceptFile: boolean) => void
  ) => {
    if (mimetypes.some(m => file.mimetype.includes(m))) {
      callback(null, true)
    } else {
      // 例外処理を返す
      callback(
        new UnsupportedMediaTypeException(
          `File type is not matching: ${mimetypes.join(', ')}`
        ),
        false
      )
    }
  }
}

こちらを応用することで、アップロードできないファイルを指定することや、特定の拡張子を含んでいたら処理を行うなどができるかと思います。

OpenAPI 用のデコレーター定義

最後に、Swagger 上でファイルのアップロード処理を追加する定義の紹介です。

# image.controller.ts 

import {
  Controller,
  Post,
  Body,
  UploadedFile,
  UseInterceptors
} from '@nestjs/common'

import { ApiConsumes } from '@nestjs/swagger'

import { FileInterceptor } from '@nestjs/platform-express'
import { fileMimetypeFilter } from './file-mimetype-filter'

import { CreateImageRequest } from './dto/create-image.dto'

export class ImagesController {
  @Post()
  @ApiConsumes('multipart/form-data')
  @UseInterceptors(
    FileInterceptor('file', { fileFilter: fileMimetypeFilter('image') })
  )
  async create(
    @Body() createImageRequest: CreateImageRequest,
    @UploadedFile() file: Express.Multer.File
  ) {
    console.log(file)
  }
}

ApiConsumes を指定することで、multipart のフォームを有効にしています。

また、 CreateImageRequest 内にて、 ApiProperty をこのようにすることで、 Request Body

上にフォームが出てくることになります。


# dto/create-image.dto.ts

import { ApiProperty } from '@nestjs/swagger'

export class CreateImageRequest {
  @ApiProperty({
    description: 'アップロードするファイル',
    type: 'file',
    properties: {
      file: {
        type: 'string',
        format: 'binary'
      }
    }
  })
  file!: Express.Multer.File
}

最後に

今回はソースコードの紹介がメインになってしまいましたが、いかがでしたでしょうか。

NestJS はライブラリが充実しているので、テクニックを組み合わせることで簡単に実装を行うことが可能です。

しかし、ドキュメントが最低限な記載である傾向もあるので、ライブラリの中まで読み込まないと、拡張ができないこともあります。

みなさまの開発時の参考になりましたら、幸いです。

アルダグラム Tech Blog

Discussion