찔끔찔끔씩😎

[Sopt] 7차 세미나(1) - Nodjs AWS S3 파일 업로드 본문

Server/Nodejs

[Sopt] 7차 세미나(1) - Nodjs AWS S3 파일 업로드

댕경 2022. 6. 16. 17:17
728x90

 


IMAGE UPLOAD

🔎 Content type

HTTP Reqeust / HTTP  Response

HTTP Body 에 들어가는 Message의 Type 을 명시해주어야 한다.

json 형식을 사용하여 HTTP Body를 전송하는 경우의 Content Type으로 지금까지 applicaion/json 을 사용하였다.

 

그렇다면 파일은  어떤 형식으로 전송해야 할까?

 

바로 multipart/form-data 를 사용하면 된다. Content-Type 필드에 MIME Type 을 명시하기 위한 Content-type file 전송을 위해 사용한다.

🔎 Upload Flow

Client-Server 사이의 업로드 과정에 대해 알아보자.

1. 클라이언트는 Form 을 통하여 파일을 서버로 전송한다.

2. 이때 Content-Type은 multipart/form-data로 지정되어 전송된다.

3. 서버는 해당 multipart 메세지를 part 별로 분리하여 처리한다.

🔎 사용할 모듈

- multer: multipart/form-data로 전송된 파일을 처리하는 미들웨어

- multer-s3: 이미지 업로드 시 S3를 사용할 경우 이용한다.

- aws-sdk: Node.js 용 AWS SDK를 사용하기 위한 모듈

 

설치해주자!

yarn add multer multer-s3 aws-sdk
yarn add @types/multer-s3 @types/multer --dev

추가적으로 서버에서 AWS에 접근하기 위해서는 Access key가 필요하다.

발급 받은 키와 본인의 S3 버킷 이름을  .env에 추가해주도록 한다.

 

.env

변수로 사용할 수 있도록 config/index.ts에 정의해준다.

 

 

config/s3.Config.ts

aws-sdk 모듈을 이용하여 access key, secret access key, region 을 파라메터로 하는 AWS.S3를 만들어 준다.

이는 AWS와의 연결을 위한 것!

import AWS from "aws-sdk";
import config from ".";

/**
 * AWS와 연결을 위해
 */
const s3: AWS.S3 = new AWS.S3({
    accessKeyId: config.s3AccessKey,
    secretAccessKey: config.s3SecretKey,
    region: 'ap-northeast-2'
});

export default s3;

 

config/multer.ts

import multer from "multer";
import multerS3 from "multer-s3";
import config from ".";
import s3 from "./s3Config";

const upload = multer({ //미들웨어로 사용할 multer 생성
    storage: multerS3({
        // 실질적인 storage는 multer s3를 사용한다.
        // 따라서 aws s3로 설정하고, s3 bucket name을 지정해준다.
        s3: s3, 
        bucket: config.bucketName,
        contentType: multerS3.AUTO_CONTENT_TYPE, // multer가 알아서 지정할것
        acl: "public-read", // access control for the file
        key: function (req: Express.Request, file: Express.MulterS3.File, cb) {
            // bucket 내 이름이 겹치면, 동일 파일로 인식하기 때문에
            // 시간도 붙여줘서 이름이 같더라도 다른 파일로 저장하게 한다. 
            cb(null, `${Date.now()}_${file.originalname}`);
        },
    }),
});

export default upload;

이미지 업로드 (차례대로 단일 이미지, 여러 이미지)

🔎 1. Model,  Collection 만들기

File 모델 (src/models/File.ts)

import mongoose from "mongoose";
import { FileInfo } from "../interfaces/file/FileInfo";

const FileSchema = new mongoose.Schema({
    link: {
        type: String,
        required: true
    },
    fileName: {
        type: String,
        required: true
    }
}, {
    timestamps: true 
});

export default mongoose.model<FileInfo & mongoose.Document>("File", FileSchema);

 

FileInfo.ts (src/interfaces/file/FileInfo.ts)

export interface FileInfo {
    link: string;
    fileName: string;
}

 

FileResponseDto.ts (src/interfaces/file/FileResponseDto.ts)

import mongoose from "mongoose";

export interface FileResponseDto {
    _id: mongoose.Schema.Types.ObjectId;
    link: string;
}

🔎 2. Controller 만들기

multer upload 미들웨어에서 s3에 저장된 파일 주소는 req.file.location에 위치한다.

// 단일 파일
// multer upload 미들웨어에서 s3에 저장된 파일 주소는 req.file.location에 위치함
// 미들웨어 만든거 넣어주고, 파일 주소를 저장만 해주면 된다.
const uploadFileToS3 = async (req: Request, res: Response) => {
    if (!req.file) return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, message.NULL_VALUE));

    const image: Express.MulterS3.File = req.file as Express.MulterS3.File;
    // image에서 name,location 받아오기
    const { originalname, location } = image;

    try {
        const data = await FileService.createFile(location, originalname);
        res.status(statusCode.CREATED).send(util.success(statusCode.CREATED, message.CREATE_FILE_SUCCESS, data));
    } catch (error) {
        console.log(error);
        res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, message.INTERNAL_SERVER_ERROR));
    }
}

// 다중 파일
const uploadFilesToS3 = async (req: Request, res: Response) => {
    if (!req.files) return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, message.NULL_VALUE));

    // 배열로 들어온다.
    const images: Express.MulterS3.File[] = req.files as Express.MulterS3.File[];

    try {
        // 이미지 위치, 이름로 된 배열로 만들어주기. 
        const imageList: {
            location: string;
            originalname: string;
        }[] = await Promise.all(images.map((image: Express.MulterS3.File) => {
            return {
                location: image.location,
                originalname: image.originalname
            }
        }));

        const data = await FileService.createFiles(imageList);
        res.status(statusCode.CREATED).send(util.success(statusCode.CREATED, message.CREATE_FILE_SUCCESS, data));

     } catch (error) {
        console.log(error);
        res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, message.INTERNAL_SERVER_ERROR));
    }
}

export default{
    uploadFileToS3,
    uploadFilesToS3,
}

🔎3. Service 만들기

// 단일 파일
const createFile = async (link: string, fileName: string): Promise<FileResponseDto> => {
    try {
        const file = new File({
            link,
            fileName
        });

        await file.save();

        const data = {
            _id: file._id,
            link
        };

        return data;
    } catch (error) {
        console.log(error);
        throw error;
    }
}

// 다중 파일
const createFiles = async (imageList: { location: string, originalname: string }[]): Promise<FileResponseDto[]> => {
    try {
        const data = await Promise.all(imageList.map(async image => {
            const file = new File({
                link: image.location,
                fileName: image.originalname
            });

            await file.save();

            // file의 id, link를 배열로 반환하기
            return {
                _id: file._id,
                link: file.link
            };
        }));

        return data;
    } catch (error) {
        console.log(error);
        throw error;
    }
};

export default {
    createFile,
    createFiles
}

🔎 4. Router 만들기

upload.single('file'): multipart/form-data에 들어온 데이터 중 'file' 필드로 받아 온다.single: 단일array: 다중

const router: Router = Router();

// router.post('/upload', upload.single('file'), FileController.uploadFileToS3);

// 여러개를 위해 single대신 array로 사용
router.post('/upload', upload.array('file'), FileController.uploadFilesToS3);

export default router;

🔎 multipart/form-data 보내기

이전에는 Json 형식으로만 보냈다면 Form 으로 전송해주어야한다.

🔎 s3 acl 권한 부여

s3 bucket -> 권한 -> ACL -> 편집

Comments