Next.js + TS | AWS S3 이미지 업로드 API 구현하기 (+ AWS S3 버킷 생성, route 작성하기)

dayannne·2024년 9월 26일
0
post-thumbnail
post-custom-banner

인턴하면서 AWS S3을 통해 이미지를 저장하고 프로젝트에서 이미지를 읽어들일 때 pre-signed URL로 사용했던 경험을 계기로,
Next.js 개인 프로젝트에서 직접 AWS S3 Bucket을 생성해서 이미지를 저장해 사용해 보기로 했다.


AWS S3 버킷 만들기

1. AWS S3 버킷 생성하기

AWS 계정을 이미 생성했다는 가정 하에,

로그인 후 상단 검색을 이용해 S3 서비스를 클릭한다.

버킷 만들기를 클릭해 버킷을 만들 것이다.
(버킷은 저장공간을 말한다.)

먼저 AWS 리전이 올바르게 설정되어 있는지 확인한다.

AWS 리전(Region)
AWS 인프라를 지리적으로 나누어 배포한 것을 의미한다. 지리적 영역이며 사용자와 리전이 가까울수록 네트워크 지연을 최소화할 수 있다. 따라서 사용자는 AWS 리전 선택 시 글로벌하게 분포되어 있는 리전 중에 실제 서비스를 사용할 사용자층과 가장 가까운 리전을 선택하여 그 리전 내 클라우드 인프라를 사용하는 것이 일반적인 방법이다.

올바르게 설정되어 있지 않다면, 오른쪽 상단의 지역을 변경 후 다시 버킷 만들기를 진행한다.

원하는 버킷이름을 입력한 후, 그 다음은 아래 기본 설정을 따라간다.

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

그 후 이 버킷의 퍼블릭 액세스 차단 설정에서 모든 퍼블릭 액세스 차단을 해제한 후 노란 박스에 체크한다.

최종 버킷 만들기를 클릭하고 대시보드로 돌아가면 버킷이 생성된 것을 확인할 수 잇다.

2. AWS S3 버킷 정책 변경, CORS 설정하기

1) 버킷 정책 변경

누가 버킷을 읽고, 수정, 삭제할 수 있는지에 대한 권한을 정의해야 한다.
해당 버킷의 권한 탭을 클릭 후, 버킷 정책 - 편집버튼을 클릭해 준다.

그 다음 아래와 같이 작성한다.

{
    "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::나의계정아이디:root"
            },
            "Action": [
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Resource": "arn:aws:s3:::버킷명/*"
        }
    ]
}


다른 양식은 변경하지 않고, 한글로 되어 있는 부분만 수정해 그대로 붙여 넣어준다.

계정 ID는 오른쪽 상단 username 클릭 시 확인할 수 있다.

2) CORS 설정

다음으로 어떤 사이트에서 버킷 안의 파일들을 읽고, 쓰고, 삭제할 수 있는지 권한을 주기 위한 CORS를 설정한다.
권한 탭의 맨 하단에 CORS(Cross-origin 리소스 공유) 로 이동해 편집을 클릭한다.

그 다음 아래와 같이 작성한다. (모든 사이트에 대한 권한이 허용된다.)

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

3. Access 키 발급

오른쪽 상단의 username을 클릭 후 보안 자격 증명으로 이동한다.

그 다음 액세스 키 만들기를 통 해 액세스 키를 발급한다.

만들기를 완료하기 전 꼭 비밀 액세스 키를 다른 곳에 저장해 두거나 CSV 파일을 받아둔다.

비밀 액세스 키를 받아둔 후 완료를 클릭한다.

버킷에서 직접 이미지 업로드 + 불러와 보기

테스트 삼아 이 이미지를 업로드 한 후 불러와 보기로 했다.
버킷 - 객체에서 업로드를 클릭한다.
이미지 파일을 추가한 후 업로드를 클릭해 완료한다.
(참고로 여러 파일의 이미지를 한꺼번에 추가해 업로드할 수도 있다.)

다시 돌아와 업로드 된 이미지를 선택 후 URL 복사를 클릭하면 아래와 같은 방식의 pre-signed URL이 복사된다.

https://{버킷명}.s3.{AWS리전}.amazonaws.com/{파일명}

브라우저에 해당 URL을 입력해 이동하면?
이미지가 불러와졌다!

이 원리에 따라 Next.ts 프로젝트에서 이미지 업로드 API 로직을 구현하면,
API 요청을 통해 AWS S3에 이미지를 업로드한 후 저장된 이미지의 pre-signed URL로 이미지를 불러오는 방식으로 프로젝트에서 이미지를 사용할 수 있게 된다.


AWS S3 이미지 업로드 API 구현하기

1. 프로젝트 환경변수 설정

이미지 업로드를 구현할 프로젝트의 env파일에
AWS 리전 / 버킷 이름 / ACCESS 키 아이디 / ACCESS 비밀 키를 저장한다.

2. 이미지 업로드 API 구현

먼저 아래 패키지를 설치해 준다.

NPM - @aws-sdk/client-s3 패키지

app > api > image > route.ts에 이미지를 업로드 CREATE 하기 위한 로직을 작성한다.

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

const Bucket = process.env.AMPLIFY_BUCKET;
// S3 클라이언트 설정
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,
  },
});

// POST 함수 
export async function POST(req: Request, res: Response) {
  try {
    // 폼 데이터 처리
    const formData = await req.formData();
    const files = formData.getAll('img') as File[];
    
	// 업로드할 이미지 파일 수 제한
    if (files.length > 3) {
      return new Response(
        JSON.stringify({
          message: '업로드할 수 있는 이미지 파일의 수는 최대 3장입니다.',
        }),
        { status: 400 },
      );
    }

    // 이미지 파일을 S3에 업로드
    const uploadPromises = files.map(async (file) => {
      const Body = Buffer.from(await file.arrayBuffer());
      const Key = file.name;
      const ContentType = file.type || 'image/jpg';

      await s3.send(
        new PutObjectCommand({
          Bucket,
          Key,
          Body,
          ContentType,
        }),
      );

      return [
        `https://${Bucket}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`,
      ];
    });

    const imgUrls = await Promise.all(uploadPromises);

    // 파일의 개수가 1이면 배열 대신 단일 URL 반환
    if (imgUrls.length === 1) {
      return new Response(JSON.stringify({ data: imgUrls[0], message: 'OK' }), {
        status: 200,
      });
    }

    // 파일 개수가 여러개면 URL 배열 반환
    return new Response(JSON.stringify({ data: [...imgUrls], message: 'OK' }), {
      status: 200,
    });
    
  } catch (error) {
    console.error('Error uploading files:', error);
    return new Response(
      JSON.stringify({ message: '파일 업로드 중 오류가 발생했습니다.' }),
      { status: 500 },
    );
  }
}

req로 부터 파일 데이터를 받으면,

  • 업로드 가능할 이미지 파일 수를 제한하고,
  • 업로드한 이미지는
    https://${Bucket}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key} 와 같이 이미지를 pre-signed URL로 사용할 수 있도록 변환했다.
  • 변환한 이미지 파일 개수가 여러개면 배열 형식으로 반환하고, 단일 이미지라면 URL 문자열만을 반환했다.

이를 바탕으로 어떠한 폼 제출 시 이미지 업로드 API 요청 후, 반환한 pre-signed 이미지 URL를 폼 제출 API Request로 넣는 방식으로 구현할 수 있게 되었다.


에러

The bucket you are attempting to access must be addressed using the specified endpoint.
Please send all future requests to this endpoint.

코드상의 리전과 버킷이 위치한 리전이 다른 경우 만난 에러이다.
버킷의 지역이 ap-northeast-2로 설정되어 있는데, pre-signed URL에서
https://{버킷명}.s3.ap-southeast-2.amazonaws.com/{파일명}와 같이 엉뚱한 리전으로 요청될 때이다.
버킷을 만들 때 AWS 리전을 잘 확인하고, ENV 설정에서 올바르게 AWS 리전이 설정되어 있는지 확인한다면 만나지 않을 것이다. 나는 만났지만..^^


참고

profile
☁️
post-custom-banner

0개의 댓글