보통 초심자가 백엔드 개발을 하는 과정을 보면, 두 부분에서 가장 어려움을 겪는 것 같습니다. 바로 회원가입/로그인, 그리고 이미지 업로드 기능인데요. 오늘은 AWS s3를 이용해서 이미지를 업로드하는 방법에 대해 살펴보겠습니다.
서비스를 만들 때 얼마나 많은 이미지가 업로드 될 지 모릅니다. 그래서 우리는 보통 클라우드 서비스를 사용하는데요, 아마 개발자 분들이 가장 많이 사용하는 클라우드 서비스는 AWS일 것입니다.
그 중에서 S3는 비디오, 이미지 등의 파일을 저장해두는 기능을 제공하는 서비스이죠.
(참고자료. 클라우드와 AWS의 기본 개념)
AWS 계정을 생성하고 S3를 검색하면, 버킷이라는 페이지를 볼 수 있을 것입니다.
그곳에서 '버킷 만들기'를 눌러서 버킷을 생성해 봅시다.
버킷 이름은 자유롭게 설정하되, 고유한 이름으로 적어주시면 됩니다.
그리고 객체 소유권만 'ACL 활성화됨'으로 바꿔주시고, 나머지는 기본 설정대로 생성해주세요.
(ACL 활성화는 이렇게 웹사이트를 통해 직접 설정하지 않고, 코드를 통해서 S3에 접근하기 위해 필요합니다)
이제는 코드에서 사용하기 위해 S3에 접근 가능한 가상의 계정을 만들어보겠습니다.
AWS에서 IAM을 검색해서 들어간 후, 왼쪽 탭에서 '사용자'를 클릭해주세요.
이후 '사용자 추가'를 클릭하여, 새로운 계정을 만들어봅시다.
사용자 이름을 설정하고, 액세스 유형을 '액세스 키'로 선택해 주세요.
AWS 설명에 적힌 대로 프로그래밍 방식으로 AWS에 접근하기 위해 필요한 값이라고 생각하시면 되어요.
그리고 다음으로 가서 '기존 정책 직접 연결'을 누르고, s3로 정책을 필터하여 AmazonS3FullAccess를 선택해주세요.
우리는 이미지를 업로드하기 위해 S3만 사용할테니, 해당 권한만 있으면 충분합니다.
그리고 쭉쭉 넘겨서 생성을 완료하면 (다음과 같은 페이지 밑에) 사용자 이름과 액세스 키, 비밀 액세스 키가 뜨는 것을 확인하실 수 있습니다.
이 때 액세스 키 ID와 비밀 액세스 키 값은 바로 사용할 것이니 복사해주세요!
이제 코드로 돌아와봅시다. 우리는 NodeJS를 쓰고 있으니 aws를 위한 npm 패키지를 하나 받아야 합니다.
npm install --save aws-sdk
그리고 파일을 하나 만들어서 다음과 같이 코드를 작성하면 됩니다.
저는 image 업로드 기능을 담당한다는 의미로 ImageUploader.js라고 이름을 짓겠습니다.
import AWS from 'aws-sdk'
AWS.config.update({
region: 'ap-northeast-2',
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
});
const s3 = new AWS.S3()
여기서 region은 AWS의 region 값을 넣고, accessKeyId와 secretAccessKey에는 각각 윗 단계에서 복사해두었던 액세스 키 ID와 비밀 액세스 키 값을 넣어주시면 됩니다.
보통 보안을 위해서 중요한 값은 env 파일에 설정해두어서 저렇게 process.env.{값}의 방식으로 사용하는데, 만약 테스트를 위해서라면 우선 string 값으로 넣으시면 됩니다.
이제 우리는 S3에 접근이 가능합니다! 남은 것은 기존의 json 형식이 아니라 이미지 파일 (멀티파트 폼데이터)을 다룰 수 있도록 설정해야 하는 것인데요. multer 라는 패키지를 이용하면 쉽게 설정할 수 있습니다.
multer는 파일 업로드를 위해 사용되는 multipart/form-data를 다루기 위한 node.js의 미들웨어라고 생각하시면 됩니다. multer를 거치면 req.file 혹은 req.files 로 내용을 넘겨주는 방식이죠.
aws s3에 올리기 위해서는 multer외에, multer-s3 패키지도 필요합니다.
npm install multer multer-s3 --save
그리고 아까 생성했던 imageUploader.js 파일에 코드를 추가해봅시다.
import AWS from 'aws-sdk'
import multer from 'multer'
import multerS3 from 'multer-s3'
import path from 'path'
AWS.config.update({
region: 'ap-northeast-2',
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
});
const s3 = new AWS.S3()
const allowedExtensions = ['.png', '.jpg', '.jpeg', '.bmp']
const imageUploader = multer({
storage: multerS3({
s3: s3,
bucket: 'bucket-name', // 생성한 버킷 이름을 적어주세요.
key: (req, file, callback) => {
const uploadDirectory = req.query.directory ?? '' // 업로드할 디렉토리를 설정하기 위해 넣어둔 코드로, 없어도 무관합니다.
const extension = path.extname(file.originalname)
if (!allowedExtensions.includes(extension)) { // extension 확인을 위한 코드로, 없어도 무관합니다.
return callback(new Error('wrong extension'))
}
callback(null, `${uploadDirectory}/${Date.now()}_${file.originalname}`)
},
acl: 'public-read-write'
}),
})
export default imageUploader
우선 생성했던 AWS s3 버킷의 이름을 'bucket-name' 대신에 적어주세요.
그리고 중간부분은 다 빼고 cb(콜백) 함수만 있으면 업로드 기능은 끝입니다!
(acl은 우리가 위에서 s3 생성할 때 이야기했던 권한 관련 설정이라고 생각하시면 됩니다)
나머지는 제가 추가한 코드인데요. 필수는 아니지만 궁금하신 분들이 있을 수도 있어서 간단히 설명드리겠습니다.
우선, cb 함수의 두 번쨰 인자로 들어가는 것이 업로드 경로인데요, 경로에 '/'를 포함하면 폴더도 자동으로 생성해줍니다. 그래서 저는 업로드 할 때 경로를 request에서 설정할 수 있게 req.query의 directory 값으로 받고, 없으면 디폴트 경로에 추가되게 한 것입니다.
그리고 extension은 원하지 않는 포맷을 받지 않기 위해서 추가한 코드입니다. path라는 기본 패키지를 활용하여 extname으로 확장자를 추출하고, 허용하지 않는 확장자이면 에러를 내도록 한 것이죠.
여기까지 했다면 이제 벌써 기능을 완성한 것입니다!
한 번 확인해볼까요? 다음과 같은 test/image 라는 임시 api 주소를 만들어서 요청을 날려보겠습니다.
(저는 Express를 사용하고 있는데, 다른 프레임워크를 사용해도 무방합니다.)
router.post('/test/image', imageUploader.single('image'), (req, res) => {
res.send('good!')
})
여기에서 single은 단일 이미지를 받을 때 사용하는 것인데요. 만약 여러 이미지를 받고싶다면 imageUploader.array('images')와 같이 single대신 array를 사용하면 됩니다. 그리고 파라미터 안의 문자열은 어떤 key 값으로 받을지 설정하는 것으로, 자유롭게 이름을 설정하면 됩니다.
그리고 postman과 같은 서비스를 이용해서 api를 호출해보면...!
우선 다음과 같이 설정한 response가 잘 나타나는 것을 확인할 수 있습니다.
그리고 s3에 들어가보면 원하는 directory에 원하는 이름의 파일이 등록된 것도 확인할 수 있죠!
사실 이대로도 충분하지만, 한 번 제대로 된 api까지 이어서 만들어보겠습니다!
만약 프로필 이미지 변경 기능을 만들고 있었다고 생각하면, 다음과 같은 rest api를 만들 수 있을 것입니다.
router.post('/profiles/:id/image', imageUploader.single('image'), ProfileController.editProfileImage)
그리고 업로드 된 경로 값을 api에 활용하기 위해서는 아래와 같이 req.file.location 값을 사용하시면 됩니다!
(나머지 로직이나 파일 이름/함수 등은 서비스에 따라 달라질 수 있으니 자유롭게 생각해 주세요!)
export default class ProfileController {
static editProfileImage = async (req, res) => {
const { id } = req.params
const filePath = req.file.location // 업로드 된 이미지 경로
if (!filePath) {
throw new CustomError({
status: 401,
response: {
message: "Invalid file path"
}
})
}
const profile = await ProfileService.updateProfile(
new Types.ObjectId(profileId),
{ photoUrl: filePath }
)
res.status(200).send(profile)
}
}
이렇게 오늘은 이미지 업로드 기능에 대해 다루어보았는데요. 이제 프로필 이미지 변경 등의 기능을 자유롭게 사용하실 수 있으실 것입니다! 그럼 새로 배운 지식을 기반으로 삼아서 모두 해피코딩하세요:)
react-query 캐싱 이슈 해결하기! 변경, 삭제할 때 쿼리 업데이트가 안된다? (0) | 2022.01.31 |
---|---|
react native 이미지 업로드 기능 만들기! (expo) (2) | 2022.01.30 |
REST API 컨벤션 Top5! 단수, 복수, 네이밍 등의 url 설계를 위한 best practice 알아보기 (0) | 2022.01.13 |
Express에서 JWT 토큰 인증 미들웨어 만들기 (Typescript 적용까지) (1) | 2021.12.22 |
Express에서 Error handler 추가하여 try, catch 생략하기! (1) | 2021.12.22 |
댓글 영역