본문 바로가기

Computer Programming/React

리액트 AWS S3 버킷으로 파일 업로드 구현하기

S3를 이용해서 파일을 업로드하는 프로세스이다. 클라이언트에서 S3에 파일을 업로드하고, S3에 저장된 파일을 클라이언트에서 사용할때는 생성된 URL 객체로 접근해서 사용할 수 있다.

 

 

우선 리액트 앱을 S3로 빌드한 뒤 생성되는 객체 중 이미지를 저장하는 객체를 하나 만든다.

 

 

 

- 참고 자료

https://www.youtube.com/watch?v=-BmF30R_erk 

이 자료로 초기 설정한 뒤에 권한 관련된 부분은 S3 배포 설명 사이트를 참고했다. (티스토리 글 쓰다가 캐시 날아가서 트러블슈팅 정리한거 다 날아가버림)

ㅠㅠ

 

 

우선 aws-sdk 라이브러리를 설치해주고 S3 upload 생성자를 만든 뒤 만든 파일 배열을 보내면 업로드되어 url로 배열에 담긴다.

 

 

./utils/awsS3

import AWS from "aws-sdk";

AWS.config.update({
  accessKeyId: process.env.REACT_APP_ACCESS_KEY,
  secretAccessKey: process.env.REACT_APP_SECREAT_ACCESS_KEY,
});

const myBucket = new AWS.S3({
  params: { Bucket: process.env.REACT_APP_S3_BUCKET },
  region: process.env.REACT_APP_RESION,
});

const uploadToS3 = (img, _) => {
  const params = {
    ACL: "public-read",
    Body: img,
    Bucket: process.env.REACT_APP_S3_BUCKET,
    Key: `upload/${img.name}`,
  };

  return new Promise((resolve, reject) => {
    myBucket
      .putObject(params)
      .on("httpUploadProgress", (evt) => {})
      .send((err, _) => {
        if (err) {
          console.log(err);
          reject(err);
          return;
        }
        const imageUrl = `https://${process.env.REACT_APP_S3_BUCKET}.s3.${process.env.REACT_APP_RESION}.amazonaws.com/${params.Key}`;
        resolve(imageUrl);
      });
  });
};

export default uploadToS3;

IAM을 통해 S3 권한을 열어주고 

 

퍼블릭 엑세스도 잊지않고 열어준다.

 

버킷 정책은 원하는 resorce 페이지로 설정하고 꼭 /*를 붙여준다. Action 필드는 s3의 GetObject를 선택한다. 

 

 

CORS 핸들링도 해준다.

 

 

이제 사진을 올려보자

 

유틸리티 폴더에 파일을 따로 생성해서 s3에 업로드하는 로직을 모듈화해준다.

 

import AWS from "aws-sdk";

AWS.config.update({
  accessKeyId: process.env.REACT_APP_ACCESS_KEY,
  secretAccessKey: process.env.REACT_APP_SECREAT_ACCESS_KEY,
});

const myBucket = new AWS.S3({
  params: { Bucket: process.env.REACT_APP_S3_BUCKET },
  region: process.env.REACT_APP_RESION,
});

const uploadToS3 = (img, _) => {
  const params = {
    ACL: "public-read",
    Body: img,
    Bucket: process.env.REACT_APP_S3_BUCKET,
    Key: `upload/${img.name}`,
  };

  return new Promise((resolve, reject) => {
    myBucket
      .putObject(params)
      .on("httpUploadProgress", (evt) => {})
      .send((err, _) => {
        if (err) {
          console.log(err);
          reject(err);
          return;
        }
        const imageUrl = `https://${process.env.REACT_APP_S3_BUCKET}.s3.${process.env.REACT_APP_RESION}.amazonaws.com/${params.Key}`;
        resolve(imageUrl);
      });
  });
};

export default uploadToS3;

 

access key와 id를 제대로 넣어주고 params 설정도 알맞게 넣은 뒤 image를 url로 변경해주는 로직을 비동기로 처리해준다.

 

 

그리고 이번 프로젝트같은 경우는 이미지가 1개가 아닌 최대 3장까지 넣을 수 있게 구현했기 때문에 map을 돌려서 모든 파일을 이 awsS3 모듈을 통과하게 해야한다.

 

그래서 이 함수를 호출하는 WriteContainer (글 작성 컨테이너)에서 map을 돌려서 file들을 모두 처리해준다

 

const onSubmit = async (e) => {
    e.preventDefault();

    if (Number(formData.price) > 10000) {
      toast.error("만원 이하의 가격만 입력해주세요.");
      return;
    }

    if (!Object.values(formData).every((item) => item !== "")) {
      toast.error("정보를 모두 입력해주세요.");
      return;
    }

    const uploadPromises = formData.images.map(uploadToS3);

    try {
      const imageUrls = await Promise.all(uploadPromises);
      handlePost(imageUrls);
    } catch (err) {
      console.error(err);
    }
  };

uploadToS3 가 비동기 함수라서 onSubmit도 async를 적용했고, uploadPromises에 담긴 프로미스 객체를 try문에서 한번에 처리한다. 즉, 아직 uploadPromises에는 promise 객체가 최대 3개가 담긴 배열이지만, try 문의 await Promise.all 메소드를 통해 url로 변환되어 url이 최대 3개가 담긴 배열이 리턴된다.

 

 

그리고 이 배열을 handlePost에 넘기게 되는데

 

const handlePost = async (imageUrls) => {
    const updatedFormData = { ...formData, images: imageUrls };
    try {
      if (editPost) {
        const response = await editmyPost(editPost.postId, formData);
        Swal.fire({
          position: "top",
          icon: "success",
          title: "수정 완료 되었습니다 :)",
          showConfirmButton: false,
          timer: 1500,
        });
        navigate(`/${response.data.postId}/detail`);
      } else {
        const response = await writePost(updatedFormData);
        Swal.fire({
          position: "top",
          icon: "success",
          title: "작성 완료 되었습니다 :)",
          showConfirmButton: false,
          timer: 1500,
        });
        navigate(`/${response.data.postId}/detail`);
      }

      setFormData({
        ...formData,
        title: "",
        content: "",
        price: "",
        category: "",
        location: "",
        images: [],
      });
    } catch (e) {
      console.log(e);
    }
  };

 

url이 담긴 배열을 받으면 우선 formData를 업데이트한다. 그리고 editmyPost라는 api로 서버를 통해 데이터가 업데이트된다. 그리고 해당하는 아이디를 가진 포스트의 디테일 페이지로 넘어가면 성공

 

 

 

 

권한 설정에서 살짝 헤멨지만 더 힘들었던건 이미지 여러개 받아서 처리하는것이었다...... 지피티야 고마워!