Minwook’s portfolio

백엔드 s3 upload 구현 본문

Today I Learned

백엔드 s3 upload 구현

yiminwook 2023. 3. 13. 23:34

 

지난 프론트 img를 서버로 전송한 코드를 이어서

 

서버에서 받은 img를 aws s3 bucket에 올리고자 한다

 

과정은 크게 나누자면

1. 프론트에서 받은 formData를 fomidable로 파싱하고 서버 폴더에 저장

2. 서버에 저장한 이미지를 fs module로 buffer로 읽어온다

3. buffer를 params body에 담아서 s3 bucket으로 보낸다.

4. 이미지 전송에 성공하면 서버에 저장되었던 이미지를 지운다.

5. 파일 이름을 응답으로 내보낸다.  

*추후에는 파일 이름을 응답으로 내보내지 않고 파이어베이스 데이터베이스에 저장할 것 이다.

 

 

이전 프로젝트에서 프론트에서 s3에 업로드 기능을 구현한 코드를 참고하고자 했는데

 

AWS.s3.upload()함수의 params body가 타입을 string 밖에 받지 않아

서버에 저장된 이미지를 읽어서 base64로 변환해서 올리면 업로드 되지않아 좀 많이 헤멧다.

 

처음에 시도한 코드는 aws-sdk 패키지를 이용하였지만 

구현에 성공한 코드는 @aws-sdk/client-3 패키지를 이용하였다.

 

@aws-sdk/client-3를 사용한 코드가 좀 더 최신 버전인듯 하다.

 

사용된 next.js v13.2.3

 

// models/aws_sdk.ts

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import formidable from "formidable";
import fs from "fs/promises"; //next에서 fs모듈을 쓰려면 fs/promise에서 가져와야한다.

const S3_BUCKET = process.env.AWS_S3_BUCKET || "";

const s3 = new S3Client({
  credentials: {
    accessKeyId: process.env.AWS_S3_ACCESSKEY_Id || "",
    secretAccessKey: process.env.AWS_S3_SECRET_ACCESS_KEY || "",
  },
  region: process.env.AWS_S3_REGION || "ap-northeast-2",
});

const uploadFile = async (file: formidable.File) => {
  try {
    const buffer = await fs.readFile(file.filepath); //fomidable로 저정하고 읽어온 파일

    const uploadParams = {
      Bucket: S3_BUCKET,
      Key: file.originalFilename || "",
      Body: buffer,
      ContentType: file.mimetype || "",
    };

    await s3.send(new PutObjectCommand(uploadParams)); //반환값에 url이나 filename이 없다.

    return uploadParams.Key; //따라서 filename을 그대로 리턴
  } catch (err) {
    console.error(err);
    throw new Error("S3 업로드 실패");
  }
};

export default uploadFile;

filename을 그대로 리턴하기 때문에 나중에 이미지 업로드시 파일 이름을 검증하는 과정이 필요할 것 같다.

또한 이미지 url를 반환해주지 않기 때문에 업로드한 이미지에 접근하려면 

 

`https://${process.env.AWS_S3_BUCKET}.s3.{process.env.AWS_S3_REGION}.amazonaws.com/` + encodeURIComponent(파일이름)

위와 같은 방식으로 파일 이름를 통해 url를 직접 조합해야 한다.  

 

 

// models/formidable.ts
import type { NextApiRequest } from "next";
import formidable from "formidable";
import fs from "fs/promises";
import path from "path";

export const imgStoragePath = path.join(
  process.cwd() + "/src" + "/public" + "/images" //이미지 저장경로
);

/** true일시 로컬에 저장 */
export const readFile = async (
  req: NextApiRequest,
  saveLocally: boolean = false
) => {
  try {
    await fs.readdir(imgStoragePath);
  } catch {
    await fs.mkdir(imgStoragePath);
  }

  const options: formidable.Options = {};

  if (saveLocally) {
    options.uploadDir = imgStoragePath;
    options.filename = (_name, _ext, path, _form) => {
      return Date.now().toString() + "_" + path.originalFilename;
    };
  }

  return new Promise<{
    fields: formidable.Fields;
    files: formidable.Files;
  }>((resolve, rejects) => {
    const form = formidable(options);
    form.parse(req, (err, fields, files) => {
      if (err) {
        rejects(err);
      }
      resolve({ fields, files });
    });
  });
};

 

 

// pages/api/upload.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { readFile } from "@/models/formidable";
import uploadFile from "@/models/aws_sdk";
import fs from "fs/promises";
export const config = {
  api: {
    bodyParser: false, //bodyParser를 꺼준다. default = true
  },
};

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    const data = await readFile(req, true); //formidable parse
    const file = Array.isArray(data.files.img)
      ? data.files.img[0]
      : data.files.img;

    const filename = await uploadFile(file); //s3 upload
    await fs.unlink(file.filepath); //파일 삭제
    return res.status(201).json({ message: filename });
  } catch (err) {
    console.log(err);
    return res.status(500).json({ message: "ERR!" });
  }
}

 

 

 

-추가 fs/promise가 아닌 그냥 fs모듈을 사용하려면 next.config.js를 수정해줘야 한다.

/** @type {import('next').NextConfig} */
const path = require("path");

module.exports = {
  reactStrictMode: true,
  publicRuntimeConfig: {},
  sassOptions: {
    includePaths: [path.join(__dirname, "styles")],
  },
  webpack: (config, { isServer }) => {
    // Fixes npm packages that depend on `fs` module
    if (!isServer) {
      config.resolve.fallback = {
        fs: false,
      };
    }

    return config;
  },
};

그러나 이 방식으로 fs모듈을 불러오면 async/await가 적용되지 않아서 fs/promise를 사용하였다.

 

 


 

 

참고

 

브라우저에서 Amazon S3에 사진 업로드 - AWS SDK for JavaScript

인증되지 않은 사용자의 액세스를 활성화하면 버킷과 해당 버킷의 모든 객체, 전 세계 모든 사람에게 쓰기 권한을 부여하게 됩니다. 이러한 보안 태세는 이 사례의 기본 목표에 초점을 맞추는

docs.aws.amazon.com

 

 

S3에 파일을 업로드하는 세 가지 방법

AWS SDK for Javascript v3을 이용해 next.js에서 S3에 파일 업로드를 하는 다양한 방법을 알아보자! 개요 기본 참고 자료 링크 AWS 가이드(S3 Upload) AWS 가이드(Presigned URL) 유튜브(Sam Meech-Ward) 링크로 소개한

songsong.dev

Comments