일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 개인 프로젝트
- 이력서
- 신입 개발자
- 개발자 이력서
- 구상
- 신입
- react
- 공부
- 대학졸업
- Next.js
- 개인프로젝트
- 신입 프론트엔드
- 회고
- Next.js 13
- 프론트엔드
- 계획
- MONGOOSE
- Javascript
- 삶
- 캐플라이어
- 기획
- 개발
- 백엔드
- TypeScript
- s3 bucket
- aws s3
- CSS
- 신입 이력서
- Today
- Total
개발 마라톤
[개발] Next.js 13에서 Amazon S3만을 이용한 이미지 업로드 본문
[개발] Next.js 13에서 Amazon S3만을 이용한 이미지 업로드
망__고 2023. 10. 11. 18:57
이번에는 어려울 것 같아 미뤄왔던 (...) 이미지 업로드를 구현하려고 한다.
살짝 두렵기도 하지만, 이번에 경험해보면 색다른 성장을 할 수 있을 것 같아 기대된다.

이번 포스팅에서는 Amazon S3 Bucket을 세팅하고, 사용자 IAM을 세팅한 후,
aws-sdk 를 활용하여 이미지를 업로드하는 내용을 정리해보았다.
AWS SDK를 사용하여 Amazon S3 버킷에 객체 업로드 - Amazon Simple Storage Service
AWS SDK를 사용하여 Amazon S3 버킷에 객체 업로드 - Amazon Simple Storage Service
이 설명서는 평가판 버전 SDK에 관한 것입니다. SDK는 변경될 수 있으며 프로덕션에서 사용해서는 안 됩니다.
docs.aws.amazon.com
해당 문서의 JavaScirpt 부분을 확인하여 작성하였다.
! 본 게시글은 S3Client에 PutObjectCommand로 인한 업로드 시에 발생할 수 있는 오류 !
TypeError: Second parameter is not an object.
를 해결하는 방법이 포함되어 있습니다.
해당 이슈를 확인해주세요.
Amazon S3란?
Amazon S3란 무엇인가요? - Amazon Simple Storage Service
Amazon S3란 무엇인가요? - Amazon Simple Storage Service
Amazon S3란 무엇인가요? Amazon Simple Storage Service(Amazon S3)는 업계 최고의 확장성, 데이터 가용성, 보안 및 성능을 제공하는 객체 스토리지 서비스입니다. 모든 규모와 업종의 고객은 Amazon S3를 사용하
docs.aws.amazon.com
Amazon S3는 데이터를 버킷 내의 객체로 저장하는 객체 스토리지 서비스입니다. 객체는 해당 파일을 설명하는 모든 메타데이터입니다. 버킷은 객체에 대한 컨테이너입니다.
...
버킷은 Amazon S3에 저장된 객체에 대한 컨테이너입니다.
객체는 객체 데이터와 메타데이터로 구성됩니다.
객체 키(또는 키 이름)는 버킷 내 객체에 대한 고유한 식별자입니다.
예를 들어, https://DOC-EXAMPLE-BUCKET.s3.us-west-2.amazonaws.com/photos/puppy.jpg URL에서 DOCEXAMPLE-BUCKET은 버킷의 이름이고 photos/puppy.jpg는 키입니다.
요컨데 S3는 어떤 객체를 버킷에 저장하도록 제공되는 서비스이다.
객체는 데이터와 메타데이터로 나눠지며, 우리의 데이터들과 그것의 정보를 함께 저장할 수 있다고 보면 되겠다.
또한 키라는 개념이 존재하는데, 예시를 살펴보면 저장한 데이터를 URL 형태로 접근함을 예측할 수 있다.
Amazon S3 버킷 생성
우선 AWS에 가입한다. 가입하게 되면 서비스를 찾을 수 있는데, 여기서 S3 서비스를 선택한다.

그 후 버킷 생성 버튼을 찾아 시작하면 된다.

버킷 이름을 정해주고 AWS 리전을 서울로 지정해준다.

S3에 접근하며 테스트 해봐야하기 때문에 퍼블릭 액세스 차단을 해제해준다.

버킷을 성공적으로 만들어주었다 !

추가적으로 CORS 보안 에러를 방지하기 위해 모든 접근을 허용해준다.
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"HEAD",
"GET",
"PUT",
"POST",
"DELETE"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": []
}
]
IAM 설정
[매뉴얼][초보자를 위한 AWS 웹구축] 2. IAM 유저 및 MFA 생성하기 | NDS Cloud Tech Blog (nongshim.co.kr)
[매뉴얼][초보자를 위한 AWS 웹구축] 2. IAM 유저 및 MFA 생성하기
IAM 이란? IAM(AWS Identity and Access Management)은 AWS 리소스에 대한 액세스를 안전하게 제어할 수 있는 웹 서비스입니다. IAM을 사용하여 리소스를 사용하도록 인증 및 권한 부여된 대상을 제어합니다.
tech.cloud.nongshim.co.kr
해당 글을 참고해서 작업하였다.
액세스 관리 - AWS Identity and Access Management(IAM) - AWS (amazon.com)
액세스 관리 - AWS Identity and Access Management(IAM) - AWS
aws.amazon.com
AWS Identity and Access Management(IAM)를 사용하면 AWS의 서비스 및 리소스에 액세스할 수 있는 주체를 지정하고 세분화된 권한을 중앙에서 관리하며 액세스 권한을 분석하여 AWS 전체의 권한을 세분화할 수 있습니다.
AWS 계정을 처음 생성할때 얻는 ROOT 계정은 정말 모든 권한을 다 가지고 있기 때문에 보안상 사용을 자제해야 한다.
그러므로 IAM을 통해 Access 권한만을 사용하여 작업해보도록 하자.
우선 IAM을 부여할 수 있는 ROOT 계정의 보안을 높이기 위해 MFA를 설정해준다.
[AWS] AWS MFA 설정 (tistory.com)
[AWS] AWS MFA 설정
AWS에는 ROOT 계정과 IAM계정이 있다. * ROOT 계정 : AWS에 처음 가입할때 생성하는 계정이다. 모든 권한을 가지고 있다. * 보안상 ROOT계정은 최대한 자제해야하고 IAM키로 제한된 기능을 사용해야한다.
doing7.tistory.com

그 후 우측 상단의 계정 이름을 클릭하여 ' 보안 자격 증명 ' 을 선택한다.

좌측 메뉴의 액세스 관리 > 사용자 탭을 클릭한다.


사용자 명을 정해주고, 권한 설정 시 ' 직접 정책 연결 ' 을 클릭한 후 ' AmazonS3FullAccess ' 권한을 부여한다.'
이후 백엔드 코드에서 사용할 IAM 사용자의 '액세스 키' 가 필요한데 이를 발급받아보자.

사용자 목록에 생성한 사용자(IAM)이 존재하는데, 그 사용자를 눌러준다.
그 후 바로 보이는 ' 요약 ' 이라는 메뉴의 우측에 존재하는 ' 액세스 키 만들기 ' 를 선택해주자.

목적에 맞는 사용 사례를 선택해준다.

액세스 키를 설명 할 설명 태그 값을 작성하면
바로 보이는 엑세스 키 와 비공개 키가 나타나게 된다.
바로 보이는 엑세스 키를 ACCESS_ID 로 사용할 것이고, 비공개 키를 ACCESS_KEY로 사용하도록 할 것이다.
백엔드 코드
Multer는 Express.js Request 기반의 미들웨어이고, 오히려 구현이 복잡해질 것 같아 사용 취소하였음.
취소 사유
- middleware.ts에서 사용시 Error: The edge runtime does not support Node.js 'stream' module. 오류
- 의도대로 사용하려면 NextRequest대신 Express.js Request를 이용해야하며,
해당 Request 이용시 Express.js 를 이용하여 미들웨어로 express.json() 등 추가적인 작업이 필요함.
목적에 비해 많은 소요가 필요할 것 같음
결론 - NextRequest를 사용하는 Next.js 13 의 환경에는 빠르게 적용할 수 없고, Next.js 13이 의도하는 설계 방향이 아닌 것 같음.
--- 이전 글
우선 파일 업로드에 사용되는 미들웨어인 multer와 S3를 위한 multer s3를 설치해주자.
기본적인 개념과 내용은 아래의 README에서 확인할 수 있다.
multer/doc/README-ko.md at master · expressjs/multer (github.com)
Multer는 파일 업로드를 위해 사용되는 multipart/form-data를 다루기 위한 node.js의 미들웨어 입니다. 효율성을 최대화 하기 위해 busboy를 기반으로 하고 있습니다.
multer를 이용하여 React - Node.js 파일 업로드 구현하기 (velog.io)
multer를 이용하여 React - Node.js 파일 업로드 구현하기
multer 사용 계기 React, Node.js 로 진행 중인 프로젝트에서, 파일 업로드 기능을 구현하게 되었다. 사용자가 업로드 하고자 하는 파일은 선택하여 업로드 버튼을 누르면 해당 파일이 서버에 저장되
velog.io
multer를 이용하는 이유는 위의 글에도 명시되어있듯 파일을 encType타입을 multipart/form-data로 설정하여
인코딩하지 않고 온전하게 주고 받기 위함이다.
추가적으로 multer는 파일 위치나 파일 용량, 파일 명 규칙 등을 쉽게 정의할 수 있으므로,
파일을 업로드할 때 편의성을 더해주는 종합 유틸이라고 생각하면 되겠다.
- 미들웨어 정의
// multer.ts
import multer from 'multer';
import multerS3 from 'multer-s3';
import aws from 'aws-sdk';
import { S3Client } from '@aws-sdk/client-s3';
// AWS IAM Setting
const AWS_S3_ACCESS_ID = process.env.AWS_S3_ACCESS_ID || '';
const AWS_S3_ACCESS_KEY = process.env.AWS_S3_ACCESS_KEY || '';
const AWS_S3_REGION = process.env.AWS_S3_REGION || '';
aws.config.update({
accessKeyId: AWS_S3_ACCESS_ID,
secretAccessKey: AWS_S3_ACCESS_KEY,
region: AWS_S3_REGION,
});
// s3 버킷에 대한 정보를 설정해줌
const s3 = new S3Client({
credentials: {
accessKeyId: AWS_S3_ACCESS_ID,
secretAccessKey: AWS_S3_ACCESS_KEY,
},
region: AWS_S3_REGION,
});
// multerS3를 사용하여 S3에 접근하는 미들웨어를 multer를 이용하여 만들어줌
const upload = multer({
storage: multerS3({
s3: s3,
bucket: 'charflyerbucket',
key: (req, file, callback) => {
callback(null, `${file.originalname}-${Date.now().toString()}`);
},
}),
});
export default upload;
multer의 single() 메소드에 관해서는 아래 글을 참고하였다.
[EXPRESS] 📚 multer 미들웨어 사용법 💯 정리 (tistory.com)
[EXPRESS] 📚 multer 미들웨어 사용법 💯 정리
multer 모듈 멀터는 사용 방법이 다소 어려운 미들웨어다. 이미지, 동영상 등을 비롯한 여러 가지 파일들을 멀티파트 형식으로 업로드할 때 사용하는 미들웨어이다. 멀티파트 형식이란 enctype이 mul
inpa.tistory.com
single()은 FormData의 input 의 name 파라미터 즉, key를 인자로 가진다.
업로드된 이미지 정보는 request.file을 통해 전달된다.
우선 AWS의 서비스를 이용하기위해 aws-sdk 모듈을 설치해주자.
npm i --save aws-sdk
환경 변수같은 경우 다음과 같이 설정해 두었다.
AWS_S3_ACCESS_ID=발급받은 IAM 엑세스 키
AWS_S3_ACCESS_KEY=발급받은 IA 액세스 비밀키
AWS_S3_REGION=ap-northeast-2
AWS_S3_REGION은 서울 서버는 ap-northeast-2 값을 사용한다.
코드에 앞서 전체적인 매커니즘은 다음과 같다.
- S3Client를 통해 파일을 업로드하고, URL을 반환한다.
- 미들웨어에서 업로드 유틸을 호출하고, 반환 값인 URL을 cookies에 저장한다.
- 라우터 핸들러에서 cookies 값을 통해 URL을 얻어 작업한다.
1) aws-sdk의 S3Client 이용한 업로드 유틸 작성
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { v4 as uuidv4 } from 'uuid';
// AWS IAM Setting
const AWS_S3_ACCESS_ID = process.env.AWS_S3_ACCESS_ID || '';
const AWS_S3_ACCESS_KEY = process.env.AWS_S3_ACCESS_KEY || '';
const AWS_S3_REGION = process.env.AWS_S3_REGION || '';
const AWS_S3_BUCKET = process.env.AWS_S3_BUCKET || '';
// S3를 이용하기위한 IAM 사용자 세팅
const client = new S3Client({
region: AWS_S3_REGION,
credentials: {
accessKeyId: AWS_S3_ACCESS_ID,
secretAccessKey: AWS_S3_ACCESS_KEY,
},
});
/**
* 이미지를 S3에 업로드하는 함수입니다.
* @param file 파일
* @returns
*/
const uploadImageToS3 = async (file: Blob, folder: string): Promise<string | unknown> => {
try {
const arrayBuffer = await file.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
const key = `${folder}/${uuidv4()}_${file.name}`;
const command = new PutObjectCommand({
Bucket: AWS_S3_BUCKET,
Key: key,
Body: buffer,
ACL: 'public-read',
});
const response = await client.send(command);
return response.$metadata.httpStatusCode === 200
? `https://${AWS_S3_BUCKET}.s3.${AWS_S3_REGION}.amazonaws.com/${key}`
: '';
} catch (error : unknown) {
return error;
}
};
export default uploadImageToS3;
이미지 업로드 함수는 다음과 같이 구성된다.
- Blob 값을 Buffer 형으로 변환
const arrayBuffer = await file.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
Blob 값을 그대로 Body에 지정하면 다음과 같은 오류가 뜰 것 이다.
TypeError: Second parameter is not an object.
Node.js 런타임의 Body 타입은 Blob이 아니라 Buffer 형태의 타입임으로 다음과 같은 오류가 발생한다.
따라서 해당 코드를 통해 Buffer 형으로 변환시켜주어야 타입 오류를 방지할 수 있다.
- uuid 를 통해 중복되지 않는 Key 작성
S3 Bucket의 경우 중복 Key를 가지고 있을 때, 업로드되지 않는 현상이 있었다.
이름이 Key인 만큼 완전히 독립적인 값을 가지고 있어야한다.
uuid 모듈을 활용하여 중복되지 않는 Key를 만들 수 있다.
- new PutObjectCommand 로 업로드 데이터 구성
const command = new PutObjectCommand({
Bucket: AWS_S3_BUCKET,
Key: key,
Body: buffer,
ACL: 'public-read',
});
버킷 이름과 중복되지 않는 키(이름), Buffer로 변환시킨 파일, 퍼블릭 접근을 위한 ACL 설정으로 구성되어있다.
참조로 ACL 권한을 지정하기 위해서는 다음과 같은 AWS S3 설정이 필요하다.

S3 Bucket을 클릭하고, 권한 탭을 클릭한다.

ACL(엑세스 제어 목록) 메뉴를 찾아 '편집'을 클릭한다.

다음과 같이 체크 후 '변경 사항 저장'을 클릭한다.
- response 체크 후 URL 반환
return response.$metadata.httpStatusCode === 200
? `https://${AWS_S3_BUCKET}.s3.${AWS_S3_REGION}.amazonaws.com/${key}`
: '';
reponse.$metadata.httpStatusCode 에 상태코드가 반환되어 있으며, 업로드 성공 시에 200 을 반환한다.
URL 은 위 코드처럼 Bucket 명과 region 그리고 설정한 Key를 통해 구성된다.
2) Next.js 13의 middleware.ts에서 업로드 유틸 사용 후 값을 cookies에 설정
import { NextFetchEvent, NextResponse, NextRequest } from 'next/server';
import uploadImageToS3 from './utils/middleware/uploadImageToS3';
export async function middleware(request: NextRequest, event: NextFetchEvent) {
// 기본 response
const response = NextResponse.next();
// POST /api/users
if (
request.nextUrl.pathname.startsWith('/api/users') &&
request.method === 'POST'
) {
// 이미지 파일을 추출합니다.
const formData = await request.formData();
const file = formData.get('profileImage') as Blob;
// 파일이 존재하지 않으면 미들웨어 건너뜀
if (!file) return response;
// 이미지 업로드가 성공하면 URL을 cookies의 imageUrl에 set 할 것
try {
const imageUrl = await uploadImageToS3(file, 'introductionPost');
// unkown 형태의 Error 발생 가능
typeof imageUrl === 'string' &&
response.cookies.set('imageUrl', imageUrl);
return response;
} catch (error) {
return NextResponse.json(
{ message: '서버 이미지 업로드 실패 :', error },
{ status: 500 }
);
}
}
return response;
}
export const config = {
matcher: ['/api/users/:path*'],
};
Next.js 13의 middleware.js 작성 방식은 아래 글을 참고하도록 하자.
10/16 - Next.js 13 미들웨어 (tistory.com)
10/16 - Next.js 13 미들웨어
Next.js 13 미들웨어 사용 Next.js 13 master course - router handler (velog.io) Next.js 13 master course - middleware (velog.io) Next.js 13의 공식문서를 기준으로 번역해주시고 쉽게 풀어주신 윗 글을 통하여 공부해보았다.
lee-ju-0.tistory.com
코드 실행 절차는 다음과 같다.
- NextResponse.next() 를 통한 미들웨어 반환 값을 정의한다.
- NextRequest에서 formData()를 받아온 후, file을 get한다.
- 작성한 uploadImageToS3 유틸을 활용해 file을 업로드한다.
- 반환 값을 response의 cookies에 set 한다. ( NextReponse.next() 값에 설정 가능 )
response를 반환하면, 라우터 핸들러로 response가 전달되게 된다.
3) 라우터 핸들러에서 전달받은 cookies 안의 URL 값을 사용한다.
export async function POST(request: NextRequest) {
const formData = await request.formData();
const data = {
profileImage: request.cookies.get('imageUrl')?.value,
};
...
return NextResponse.json({ status: 201 });
}
미들웨어에서 NextResponse.next() 를 전달하면 라우터 핸들러의 NextRequest 안에 해당 정보가 담겨있게 된다.
따라서 2번에서 지정해준 cookies의 정보를 가지고 있고,
cookies.get(설정 명).value 를 통해 그 값에 접근할 수 있다.
마지막으로 이미지를 불러오는 도메인을 next.config.js에서 허용해주면 된다.
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
domains: [
`${process.env.AWS_S3_BUCKET}.s3.${process.env.AWS_S3_REGION}.amazonaws.com`,
], // 이미지를 호스팅하는 도메인 목록을 지정합니다.
},
};
module.exports = nextConfig;
추가 사항
추가적으로, 백엔드 미들웨어를 거치고 S3 버킷에 파일을 저장하는 방법이 번거롭다면
프론트엔드에서 AWS S3 자원에 URL로 직접 접근할 수 있는 Presigned URL 이라는 기술도 존재한다.
이 부분은 추후에 프로젝트 종료 후 리팩토링 시에 다뤄볼만한 내용같아 참고로 적어두겠다 .. !
'--- Project --- > CharFlyer : 캐플라이어' 카테고리의 다른 글
10/13 - Next.js FormData 다루기 (0) | 2023.10.13 |
---|---|
10/12 - 이미지 프리뷰 (0) | 2023.10.12 |
[회고] 캐플라이어 중간 회고 (2) | 2023.10.10 |
10/1 - 커스텀 훅 (0) | 2023.10.01 |
9/28 - useState와 useRef (0) | 2023.09.28 |