본문 바로가기

프론트엔드

AWS S3 이미지 업로드하기 (with NextJS)

반응형

출처 : 빵빵이의 일상(빵빵이 최고!)

 

안녕하세요. 오랜만에 쓰는 포스팅이네요.

코드드림 어드민 프로젝트가 거의 마무리되어 가고 있습니다. 

 

저희는 AWS S3를 사용해 이미지를 저장하게 되었는데,

이때 배우게 된 내용을 복기하고자 이번 포스팅을 기획하게 되었습니다. 

생각보다 별건 없습니다. 레고 


1. AWS 계정 생성하기

우선 계정을 생성해 볼까요? 아래 링크로 들어가서 무료 계정을 생성해 봅시다.

 

Amazon S3

동영상 Amazon S3 시작하기 - 데모(7:37) Amazon S3 데이터 보호 개요 - 버전 관리, 객체 잠금 및 복제(7:41) Amazon S3 Intelligent Tiering 스토리지 클래스 소개(1:18) Amazon S3 Glacier 스토리지 클래스 소개(1:30) 자습

aws.amazon.com

 

기존에 계정이 없다는 가정하에 진행 하도록 하겠습니다.

루트 사용자 이메일 주소, AWS 계정 이름을 입력해 주신 후, 이메일 주소를 확인해주세요.

 

입력한 이메일로 온 확인 코드를 입력해 주시고,

암호 생성, 연락처 정보입력, 결제 정보, 자격증명 확인까지 해주시고

무료 플랜을 선택해 주시면 AWS 계정 가입이 완료됩니다. 

 

2. AWS Management Console 이동

가입이 완료되었으면 AWS Management Console로 이동합니다.

상단 검색을 이용해 S3 서비스를 클릭 후,버킷을 생성합니다.

 

자, 이제부터 버킷을 만들 것입니다.

버킷은 저장공간입니다. 원하는 버킷이름을 입력해 줍니다. 

(AWS 리전은 아래 사진과 동일하게 설정하시면 됩니다.)

 

개발의 편의성을 높이기 위해 퍼블릭 엑세스 또한 아래 사진과 동일하게 해제해 줍니다.

이외의 설정은 기본 설정을 따라갑니다. 

객체 소유권 : ACL 비활성화됨 (권장)
버킷 버전 관리 : 비활성화
태그 - 선택사항 : 미지정
기본 암호화 : Amazon S3 관리형 키 (SSE-S3)를 사용한 서버 측 암호화 

 

저희가 설정한 구성과 이름으로 새 버킷이 생성되었습니다.

그럼 이 버킷의 정책을 수정해 봅시다.

 

3. 버킷 정책 변경

해당 버킷의 권한 탭을 클릭 후, 버킷 정책 편집을 클릭해 줍니다.

 

버킷 정책은 누가, 버킷을 읽고 수정, 삭제할 수 있는지 권한을 정의하는 것입니다. 

아래 설정으로 정책을 정의하는 법을 알아볼까요?

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::설정하신 버킷명/*"
        },
        {
            "Sid": "2",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::AWS계정ID:root"
            },
            "Action": [
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Resource": "arn:aws:s3:::설정하신 버킷명/*"
        }
    ]
}

 

해당 권한을 설명하자면, 아래 사진과 같습니다.

 

AWS계정 ID:root는 화면 오른쪽 상단을 누르면 확인하실 수 있습니다. 

 

버킷 정책을 입력하셨다면 하단 [ 변경사항 저장 ] 버튼을 눌러 버킷 정책을 저장해 주세요.

다음으로 CORS(Cross-origin 리소스 공유) 설정을 추가해 줍시다.

 

어떤 사이트에서 버킷 안의 파일들을 읽고, 쓰고, 삭제할 수 있는지 CORS를 설정하는 부분입니다.

개발 중이니 AllowedOrigins 안에 *를 넣어주고

개발 후에는 다른 사이트에서 접근하지 못하게 사이트 주소를 넣어주도록 합시다.

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST",
            "GET"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": [
            "ETag"
        ]
    }
]

 

마찬가지로 입력 후 하단의 [ 변경 사항 저장 ] 버튼을 클릭해 CORS 저장을 마무리 짓습니다.

 

4. Access 키 발급

오른쪽 상단 계정이름을 클릭 후 [ 보안 자격 증명 ]을 클릭합니다.

 

[ 엑세스 키 만들기 ]를 누르면 엑세스 키가 발급됩니다.

엑세스키와 비밀 엑세스 키는 분실 시 다시 만들 수 있지만,

다른 사용자가 사용 시 요금 폭탄을 맞을 수도 있으니 주의하도록 합시다.


5. 개발

NextJS로 파일 업로드가 가능한 페이지를 하나 생성해 봅시다.

 

// app > form > page.tsx

"use client";

import Image from "next/image";
import { ChangeEvent, useState } from "react";

import styles from "./form.module.css";

export default function Form() {
  const [previewImg, setPreviewImg] = useState<FileList>();

  // 이미지 저장
  const saveHandler = async () => {
    if (!previewImg) {
      return;
    }

    const formData = new FormData();
    formData.append("img", previewImg[0]);

    const result = await fetch("/api", {
      method: "POST",
      body: formData,
    }).then((res) => res.json());

    if (result.message == "OK") {
      alert("이미지가 저장되었습니다.");
    }
  };

  // 이미지 미리보기
  const fileHandler = (e: ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files;

    if (file && file.length > 0) {
      setPreviewImg(file);
    }
  };

  return (
    <main>
      <div className={styles.container}>
        <form className={styles.form}>
          {/* 파일 업로드  */}
          <input type="file" onChange={(e) => fileHandler(e)} />

          {/* 이미지 미리보기  */}
          {previewImg && (
            <Image
              src={URL.createObjectURL(previewImg[0])}
              alt="이미지 미리보기"
              width={100}
              height={100}
            />
          )}

          <button
            type="button"
            className={styles.saveBtn}
            onClick={() => saveHandler()}
          >
            저장하기
          </button>
        </form>
      </div>
    </main>
  );
}

 

// app > form > form.module.css

@import url("https://webfontworld.github.io/sunn/SUIT.css");

.container {
    display: flex;
    align-items: center;
    flex-direction: column;
    margin: auto;
    padding: 0 40px;
    padding: 80px 0 20px;
    position: relative;
    width: 100%;
    max-width: 1280px;
    max-width: 860px;
    font-family: "SUIT";
}

.form {
    display: flex;
    flex-direction: column;
    gap: 50px;
    align-items: center;
}

.saveBtn {
    display: table;
    margin: 30px auto 0;
    padding: 10px;
    border: none;
    outline: none;
    background-color: darkslateblue;
    font-weight: 700;
    color: #fff;
    text-decoration: none;
    cursor: pointer;
    border-radius: 4px;
}

 

 

위의 코드를 붙여 넣으시면 아래 이미지와 같은 화면이 생성될 것입니다.

 

 

정상적으로 화면이 나왔다면, 이미지를 저장하는 api를 만들어봅시다.

api를 만들기 전. env 파일을 생성해 AWS를 가입하면서 얻은 비밀의 키를 입력해 둡니다. 

AWS_REGION=ap-northeast-2
AMPLIFY_BUCKET= 여러분이 입력하신 버킷의 이름
AWS_ACCESS_KEY_ID= 여러분이 얻으신 ACCESS 키 아이디
AWS_SECRET_ACCESS_KEY= 여러분이 얻으신 ACCESS 비밀 키

 

다음으로 해당 패키지를 설치해 줍니다. 

 

@aws-sdk/client-s3

AWS SDK for JavaScript S3 Client for Node.js, Browser and React Native. Latest version: 3.504.0, last published: 10 hours ago. Start using @aws-sdk/client-s3 in your project by running `npm i @aws-sdk/client-s3`. There are 2499 other projects in the npm re

www.npmjs.com

 

설치가 정상적으로 되었다면, 아래 코드를 해당 경로에 붙여 넣습니다.

s3 인스턴스를 생성해 주고, 생성한 인스턴스에 파일의 정보를 보내주면 정상적으로 파일이 저장된 것을 확인할 수 있을 것입니다. 

CREATE (저장)

// app > api > route.ts

import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";

const Bucket = process.env.AMPLIFY_BUCKET;
const s3 = new S3Client({
  region: process.env.AWS_REGION,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID as string,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string,
  },
});

// 이미지 저장
export async function POST(req: Request, res: Response) {
  try {
    const formData = await req.formData();
    const files = formData.getAll("img") as File[];
    const Body = (await files[0].arrayBuffer()) as Buffer;

    s3.send(
      new PutObjectCommand({
        Bucket,
        Key: files[0].name, // 저장시 넣고 싶은 파일 이름
        Body,
        ContentType: "image/jpg",
      })
    );

    return Response.json({ message: "OK" });
  } catch (error) {
    return Response.error();
  }
}

 

DELETE (삭제)

다음으로 저장된 이미지를 삭제해 볼까요?

이미지를 삭제하는 api는 DeleteObjectCommand를 사용했습니다. 

await s3.send(
    new DeleteObjectCommand({
        Bucket: Bucket,
        Key: '영상 포스터.png' // 삭제하고 싶은 파일의 이름 (AWS 저장 기준)
    })
);

 

READ (읽기)

마지막으로 버킷에 저장된 이미지를 그려볼까요?

이미지 저장에 사용했던 api에서 저장된 이미지의 주소를 담아봅시다.

 

아래 패키지를 설치해 줍니다.

 

@aws-sdk/s3-request-presigner

[![NPM version](https://img.shields.io/npm/v/@aws-sdk/s3-request-presigner/latest.svg)](https://www.npmjs.com/package/@aws-sdk/s3-request-presigner) [![NPM downloads](https://img.shields.io/npm/dm/@aws-sdk/s3-request-presigner.svg)](https://www.npmjs.com/.

www.npmjs.com

 

PutObjectCommand 이후에

GetObjectCommand를 사용해 저장된 파일의 주소를 가져올 수 있습니다. 

import {GetObjectCommand, PutObjectCommand, S3Client} from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

// 이미지 저장 후
const imgUrl = await getSignedUrl(
    s3,
    new GetObjectCommand({
        Bucket,
        Key: files[0].name,
    }),
    { expiresIn: 3600 } //만료시간
);

console.log(imgUrl);

 

콘솔을 찍어보면 아래와 같이 터미널에 이미지 주소가 찍힌 모습을 볼 수 있습니다. 

 

또한, 이런 방식으로도 저장된 이미지를 호출할 수 있습니다. 

https://s3.ap-northeast-2.amazonaws.com/여러분의 버킷 이름/저장한 이미지의 이름

이렇게 NextJS로 AWS S3에 이미지를 업로드해보았습니다.

처음 막무가내로 AWS를 접했을 때는 막막하기만 했었는데, 결과적으로 좋은 경험이 된 것 같습니다.

 

제가 작성한 코드가 정답은 아니겠지만, 그래도 저장은... 잘 되는 것 같아요..ㅎㅎ

먼 훗날 제가 이 코드를 보면 얼마나 재미있을까요? 헤헤

미래의 나한테 혼나는 오늘의 나 (출처: 빵빵이의 일상)

 

저희는 스터디를 통해 글을 기록하고 있습니다. 피드백은 언제나 환영입니다 :)

반응형