typescriptamazon-s3nestjsmultermulter-s3

NestJS - How to upload image to aws s3?


I'm trying to perform an image upload to aws s3 using multer-s3 on NestJS API. I have also tried aws-sdk. I use FileInterceptor and UploadedFile decorator to capture the file request. So far what I have is:

// Controller
 @Post()
 @UseInterceptors(FileInterceptor('file', multerOptions))
    uploadImage(@UploadedFile() file) {
        console.log(file);
    }

// multerOptions. multer.ts file
const configService = new ConfigService();

export const accessParams = {
    accessKeyId: configService.get('AWS_ACCESS_KEY_ID'),
    secretAccessKey: configService.get('AWS_SECRET_ACCESS_KEY'),
    region: configService.get('AWS_REGION'),
};

const imageMimeTypes = [
    'image/jpg',
    'image/jpeg',
    'image/png',
    'image/bmp',
];

AWS.config.update(accessParams);
export const s3 = new AWS.S3();

export const multerOptions = {
    fileFilter: (req: any, file: any, cb: any) => {
        const mimeType = imageMimeTypes.find(im => im === file.mimetype);

        if (mimeType) {
            cb(null, true);
        } else {
            cb(new HttpException(`Unsupported file type ${extname(file.originalname)}`, HttpStatus.BAD_REQUEST), false);
        }
    },
    storage: multerS3({
        s3: s3,
        bucket: configService.get('S3_BUCKET_NAME'),
        acl: 'read-public',
        metadata: function (req, file, cb) {
            cb(null, { fieldName: file.fieldname })
        },
        key: (req: any, file: any, cb: any) => {
            cb(null, `${Date.now().toString()}/${file.originalname}`);
        },
        contentType: multerS3.AUTO_CONTENT_TYPE
    }),
};

which gives me the following error:

{
  "message": null,
  "code": "InvalidArgument",
  "region": null,
  "time": "2020-04-24T05:34:19.009Z",
  "requestId": "DH224C558HTDF8E3",
  "extendedRequestId": "JKHKJH6877-LKJALDNC765llLKAL=",
  "statusCode": 400,
  "retryable": false,
  "retryDelay": 6.790294010827713,
  "storageErrors": []
}

Any idea? Thank you.


Solution

  • You could create a controller like

    import { Post, UseInterceptors, UploadedFile } from '@nestjs/common';
    
    @Post('upload')
    @UseInterceptors(FileInterceptor('file'))
    async upload(@UploadedFile() file) {
      return await this.service.upload(file);
    }
    

    Your service should look like

    import { S3 } from 'aws-sdk';
    import { Logger, Injectable } from '@nestjs/common';
    
    @Injectable()
    export class FileUploadService {
        async upload(file) {
            const { originalname } = file;
            const bucketS3 = 'my-aws-bucket';
            await this.uploadS3(file.buffer, bucketS3, originalname);
        }
    
        async uploadS3(file, bucket, name) {
            const s3 = this.getS3();
            const params = {
                Bucket: bucket,
                Key: String(name),
                Body: file,
            };
            return new Promise((resolve, reject) => {
                s3.upload(params, (err, data) => {
                if (err) {
                    Logger.error(err);
                    reject(err.message);
                }
                resolve(data);
                });
            });
        }
    
        getS3() {
            return new S3({
                accessKeyId: process.env.AWS_ACCESS_KEY_ID,
                secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
            });
        }
    }