여러장의 이미지 업로드 구현은 아직 해보지 않았다.
프론트에서는 form데이터로 서버에 전송해주면 된다.
서버에서는 할일이 많은데,
먼저 패키지를 여러개 설치해주어야한다.
npm install multer
npm install multer-s3
npm install aws-sdk
npm install @aws-sdk/client-s3
npm install path
그리고
S3는 비디오, 이미지등의 파일을 저장해두는 기능을 제공하는 서비스이다.
AWS콘솔에서 S3를 검색하고, 버킷만들기를 눌러준다.
여기서 버킷이름은 자유롭게 설정하되, 고유한 이름으로 작성해준다.
그리고 객체 소유권만 'ACL 활성화됨'으로 바꿔주고, 나머지는 기본 설정대로 생성한다.
이제 코드에서 사용하기 위해 S3에 접근 가능한 가상계정을 만들어야한다.
AWS에서 IAM을 검색해서 들어간후, 왼쪽 탭에서 사용자를 클릭해준다.
이후 사용자 추가를 클릭하여 새로운 계정을 만들어준다.
사용자 이름을 설정하고, 액세스 유형을 '액세스 키'로 선택해준다.
AWS설명에 적힌대로 프로그래밍 방식으로 AWS에 접근하기 위해 필요한 값이라고 생각하면 된다.
그리고 다음으로 가서 기존 정책 직접 연결을 누르고, S3로 정책을 필터링해서 AmazonS3FullAccess를 선택해준다. 이미지를 업로드하기 위해 S3만 사용하니 이 권한이면 충분하다.
그리고 생성을 완료 한 뒤, 엑세스키, 비밀 엑세스키를 발급받는다. (잃어버리면 안됨)
우선 AWS S3 Client를 생성해준다.
const s3 = new S3Client({
region : 'ap-northeast-2',
credentials : {
accessKeyId : '내가 만든 s3에 접근하는 IAM엑세스키'
secretAccessKey : '내가 만든 s3에 접근하는 IAM비밀 엑세스키'
}
})
위와 같이 AWS SDK v3에서 제공하는 S3Client를 생성하여 S3와 통신할 수 있는 클라이언트를 만든다.
region : S3버킷이 위치한 AWS 리전을 지정한다. (난 서울)
credentials : accessKey와 secretAccessKey는 AWS IAM사용자의 인증정보이다.
const allowedExtensions = ['.png', '.jpg', '.jpeg', '.bmp'];
const imageUploader = multer({
s3 : s3,
bucket : '만든 bucket이름',
acl : 'public-read-write',
key : (req, file, callback) => {
const uploadDirectory = req.query.directory ?? '';
const extension = path.extname(file.originalname);
if(!allowedExtensions.includes(extension)) {
return callback(new Error('Wrong Extension'));
}
callback(null, `${uploadDirectory}/${Date.now()}-${file.originalname}`);
}
});
원리
multerS3 : Multer의 s3저장소를 사용하여 S3에 파일 업로드
bucket : 업로드할 파일이 저장될 S3 버킷 이름을 지정한다.
acl : public-read-write는 업로드된 파일을 퍼블릭으로 읽고 쓸 수 있도록 설정한다.
key : 업로드된 파일의 이름을 설정한다. 여기서는 디렉토리와 타임스탬프를 포함해 고유한 파일 이름을 만든다.
import { S3Client } from '@aws-sdk/client-s3';
import multer from 'multer';
import multerS3 from 'multer-s3';
import path from 'path';
import dotenv from 'dotenv';
dotenv.config();
const s3 = new S3Client({
region: 'ap-northeast-2',
credentials: {
accessKeyId: process.env.AWS_S3_ACCESS_KEY,
secretAccessKey: process.env.AWS_S3_SECRET_ACCESS_KEY,
},
});
const allowedExtensions = ['.png', '.jpg', '.jpeg', '.bmp'];
const imageUploader = multer({
storage: multerS3({
s3: s3,
bucket: 'my-first-image-buck',
acl: 'public-read-write',
key: (req, file, callback) => {
const uploadDirectory = req.query.directory ?? '';
const extension = path.extname(file.originalname);
if (!allowedExtensions.includes(extension)) {
return callback(new Error('Wrong extension'));
}
callback(null, `${uploadDirectory}/${Date.now()}-${file.originalname}`);
},
}),
});
export default imageUploader;
그리고 이제 이걸 라우터에서 이미지 처리를 해주어야 한다.
postListRouter.post('/post', authMiddleware, imageUploader.single('postImg'), async(req, res) => {
...
});
imageUploader.single('postImg') : postImg라는 이름의 파일을 하나만 업로드한다. (single은 하나만 업로드함. postImg는 클라이언트가 보내는 폼데이터의 키이름)
그리고 이 파일에 접근하려면 req.file로 접근할 수 있다.
업로드된 파일의 정보를 포함 파일 경로는 req.file.location에 저장된다.
const filePath = req.file.location;
S3에 업로드된 파일의 URL은 req.file.location에 저장된다. 이 URL을 데이터 베이스의 이미지에 해당하느 필드에 (예 : postImg) 저장하여 나중에 클라이언트에서 불러올 수 있도록 설정한다.
이에 따라, 에러 핸들링 같은 경우에는
postListRouter.post('/post', authMiddleware, imageUploader.single('postImg'), async (req, res) => {
...
const filePath = req.file ? req.file.location : null;
// req.file즉, 파일이 없는데
// filePath = req.file.location;이런식으로 할당하려고 하면 무조건 에러가 남.
// 그래서 삼항 연산자로 req.file이 있다면 경로저장, 아니면 null로 해주어야 한다.
...
}
이렇게 처리해주면 된다.