3
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

NestJS でローカルに画像を保存する API を作成する

事前説明

  • 画像の保存処理はデコレータで行われる
  • 画像保存等の処理には multer というモジュールが使われる
  • デコレータでは保存するファイル名の指定、保存先の指定などが行える
  • デコレータでは、形式、サイズ等によって受け取るファイルのフィルタリングが行える
  • 既存のデコレータの実装では、関数の中の処理は画像の保存後に行われる

方針

  • 保存処理はデコレータに任せ、オプションを渡してファイル名の指定、フィルタリングなどを行う
  • デコレータでは実現できない部分はファイルの保存後に追加で実施する

ひながた

これだけ書けば、POST HOST/somathing/images の file に画像を乗せることで保存自体はできるはず。

@Controller('images')
class SampleController {
    @Post()
    @UseInterceptors(FilesInterceptor('files'))
    async uploadFile(
        @UploadedFile() file: Express.Multer.File,
    ) {}
}

細かい実装

追加で必要になったモジュール

multer, @nestjs/platform-express

$ yarn install multer @nestjs/platform-express

Intercepter

@UseInterceptors() に渡すことで、保存するファイル名の指定や、フィルタリングを行える。

import { FileInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { BadRequestException } from '@nestjs/common';

export const customFileIntercept = (options: {
    fieldname: string;        // file が乗っている body の field
    dest: string;             // 保存したい場所
    maxFileSize: number;      // 最大画像サイズ
    fileCount: number;        // 最大画像枚数
    allowFileTypes: string[]; // 許可する画像形式
}) =>
    FileInterceptor(options.fieldname, {
        storage: diskStorage({
            destination: options.dest,
        }),
        limits: {
            fileSize: options.maxFileSize,
            files: options.fileCount,
        },
        fileFilter(
            req: any,
            file: {
                fieldname: string;
                originalname: string;
                encoding: string;
                mimetype: string;
                size: number;
                destination: string;
                filename: string;
                path: string;
                buffer: Buffer;
            },
            callback: (error: Error | null, acceptFile: boolean) => void,
        ) {
            if (!options.allowFileTypes.includes(file.mimetype))
                return callback(
                    new BadRequestException('invalid file type.'),
                    false,
                );
            return callback(null, true);
        },
    });

Controller

import {
    Post,
    UseInterceptors,
    Controller,
    UploadedFile,
    BadRequestException,
    Body,
} from '@nestjs/common';
import { PostImagesDto } from '../dto/post-images.dto';
import { PostImagesClass } from '../class/post-images.class';
import { Image } from 'src/domain/image/image.domain';
// 上で作ったもの
import { customFileIntercept } from './fileIntercepter';

@Controller('images')
export class ImagesController {
    @Post()
    @UseInterceptors(customFileIntercept({
        fieldname: 'file',
        dest: './uploads',
        maxFileSize: 200000,
        fileCount: 1,
        allowFileTypes: ['image/png'],
    }))
    async uploadFile(
        @UploadedFile() file: Express.Multer.File | undefined,
        @Body() dto: PostImagesDto,
    ): Promise<PostImagesClass> {
        if (!file) throw new BadRequestException('file field must be set.');

        // body で受け取った値を使った処理
        // db に問い合わせて、その結果ファイル名を変えるとか、ファイル削除するとか
        something(file, body)

        return new PostImagesClass(file);
    }
}
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
3
Help us understand the problem. What are the problem?