Q. nest로 s3에 이미지 업로드, s3에 저장된 이미지 url 가져오기를 모두 진행하려는 경우 모듈을 어떤 방식으로 구성해야 할까?

A.

이러한 작업을 수행하기 위해서는 이미지 업로드와 이미지 URL 가져오기에 대한 두 가지 주요 기능을 제공하는 서비스를 만들어야 합니다. 각각의 기능을 담당하는 두 개의 서비스를 만들고 이를 NestJS 모듈로 구성하는 것이 일반적인 접근 방식입니다.

  1. 이미지 업로드 서비스 (ImageUploadService):

    typescriptCopy code
    // image-upload.service.ts
    
    import { Injectable } from '@nestjs/common';
    import * as AWS from 'aws-sdk';
    import * as multerS3 from 'multer-s3';
    import { v4 as uuidv4 } from 'uuid';
    import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface';
    import { S3 } from 'aws-sdk';
    
    @Injectable()
    export class ImageUploadService {
      private readonly s3: S3;
    
      constructor() {
        this.s3 = new AWS.S3({
          accessKeyId: process.env.AWS_ACCESS_KEY_ID,
          secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
          region: process.env.AWS_REGION,
        });
      }
    
      private readonly bucketName = 'your-s3-bucket-name';
    
      private readonly multerOptions: MulterOptions = {
        storage: multerS3({
          s3: this.s3,
          bucket: this.bucketName,
          acl: 'public-read', // Set ACL to 'public-read' for public access
          key: (req, file, cb) => {
            const extension = file.originalname.split('.').pop();
            const uniqueId = uuidv4();
            const filename = `${uniqueId}.${extension}`;
            cb(null, filename);
          },
        }),
      };
    
      getMulterOptions(): MulterOptions {
        return this.multerOptions;
      }
    }
    
    
  2. 이미지 URL 가져오기 서비스 (ImageRetrieveService):

    typescriptCopy code
    // image-retrieve.service.ts
    
    import { Injectable } from '@nestjs/common';
    import * as AWS from 'aws-sdk';
    
    @Injectable()
    export class ImageRetrieveService {
      private readonly s3: AWS.S3;
    
      constructor() {
        this.s3 = new AWS.S3({
          accessKeyId: process.env.AWS_ACCESS_KEY_ID,
          secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
          region: process.env.AWS_REGION,
        });
      }
    
      async getImageUrl(key: string): Promise<string> {
        const params: AWS.S3.GetObjectRequest = {
          Bucket: 'your-s3-bucket-name',
          Key: key,
        };
    
        const signedUrl = await this.s3.getSignedUrlPromise('getObject', params);
        return signedUrl;
      }
    }
    
    
  3. 모듈 구성 (S3Module):

    typescriptCopy code
    // s3.module.ts
    
    import { Module } from '@nestjs/common';
    import { ImageUploadService } from './image-upload.service';
    import { ImageRetrieveService } from './image-retrieve.service';
    
    @Module({
      providers: [ImageUploadService, ImageRetrieveService],
      exports: [ImageUploadService, ImageRetrieveService],
    })
    export class S3Module {}
    
    
  4. 사용 예시 (AppModule):

    typescriptCopy code
    // app.module.ts
    
    import { Module } from '@nestjs/common';
    import { MulterModule } from '@nestjs/platform-express/multer/multer.module';
    import { S3Module } from './s3/s3.module';
    import { AppController } from './app.controller';
    import { AppService } from './app.service';
    
    @Module({
      imports: [
        S3Module,
        MulterModule.registerAsync({
          useFactory: (imageUploadService: ImageUploadService) => imageUploadService.getMulterOptions(),
          inject: [ImageUploadService],
        }),
      ],
      controllers: [AppController],
      providers: [AppService],
    })
    export class AppModule {}
    
    

위 코드에서 **ImageUploadService**는 이미지를 S3에 업로드하고, **ImageRetrieveService**는 S3에서 이미지 URL을 가져오는 역할을 수행합니다. 이 두 서비스는 **S3Module**에서 제공되며, 다른 모듈에서 주입하여 사용할 수 있습니다. 또한 **MulterModule**를 사용하여 이미지 업로드를 처리합니다. 이렇게 모듈을 구성하면 이미지 업로드 및 이미지 URL 가져오기에 대한 로직이 모듈화되어 코드를 깔끔하게 관리할 수 있습니다.

Q. 그래서 이미지 업로드는 어떻게 하는거니?

A.

이 코드에서는 **multer**를 사용하여 이미지 업로드를 설정하고 있습니다. 실제 이미지 업로드를 위해서는 NestJS 컨트롤러에서 해당 서비스를 사용하고, **multer**를 미들웨어로 추가하여 이미지를 업로드해야 합니다. 아래는 이미지 업로드를 위한 기본적인 NestJS 컨트롤러 코드의 예시입니다.

typescriptCopy code
import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { ImageUploadService } from './image-upload.service';

@Controller('images')
export class ImageController {
  constructor(private readonly imageUploadService: ImageUploadService) {}

  @Post('upload')
  @UseInterceptors(FileInterceptor('image'))
  async uploadImage(@UploadedFile() file) {
    // 여기에서 업로드된 이미지에 대한 추가 로직을 수행합니다.
    // file.filename을 통해 S3에 저장된 파일의 이름을 얻을 수 있습니다.

    return { filename: file.filename, originalname: file.originalname };
  }
}

위의 코드에서 주요한 부분은 다음과 같습니다:

  1. @UseInterceptors(FileInterceptor('image')): **FileInterceptor**를 사용하여 이미지를 업로드합니다. **'image'**는 업로드된 파일의 필드명을 나타냅니다. 이 부분은 요청(form)에서 파일을 업로드할 때의 필드명과 일치해야 합니다.
  2. async uploadImage(@UploadedFile() file): 업로드된 파일은 @UploadedFile() 데코레이터를 통해 받아옵니다. 이 예시에서는 업로드된 이미지 파일에 대한 추가 로직을 수행하고, 결과로 업로드된 파일의 이름과 원본 파일명을 반환하고 있습니다.

이제 위의 코드를 사용하여 이미지를 업로드하려면, 해당 엔드포인트에 POST 요청을 보내고 이미지를 함께 첨부하면 됩니다. 이때 요청의 **Content-Type**은 **multipart/form-data**여야 합니다.

[Nest] 42208 - 2023. 11. 16. 오후 2:51:46 ERROR [ExceptionHandler] Nest can't resolve dependencies of the MULTER_MODULE_OPTIONS (?). Please make sure that the argument ImageUploadService at index [0] is available in the MulterModule context.

Potential solutions: