Node & AWS S3
Install
1
npm i aws-sdk
AWS S3 버킷 생성 및 권한 설정에서 이미지 저장 및 삭제는 권한(IAM 정책
)을 부여했다.
IAM 정책 생성
시 받은액세스 키(AWS_ACCESS_KEY)
,시크릿 키(AWS_SECRET_KEY)
와S3 리전(AWS_REGION)
을.env
에서 관리한다.ex)
1 2 3 4 5 6 7 8 9
import aws from 'aws-sdk'; const { AWS_SECRET_KEY, AWS_ACCESS_KEY, AWS_REGION } = process.env; export const s3 = new aws.S3({ secretAccessKey: AWS_SECRET_KEY, accessKeyId: AWS_ACCESS_KEY, region: AWS_REGION, });
이미지 저장
이미지를 저장하는 방법 중에 아래와 두 가지 방법이 있다.
Client
에서 ->Backend
로 이미지 파일을 전송 후multer-s3
를 이용해 s3에 이미지를 저장하는 방법.- 위의 방법은 이미지 용량이 크거나 여러 장 저장할 때 서버에 부하를 줄 수도 있고 이미지를 모두 저장 후 url을 받아 사용자에게 보여준다면 시간이 너무 오래 걸려 사용자 경험이 좋지 못하다.
Client
orBackend
에서PresignedUrl
을 이용해 S3에 이미지를 업로드하는 방법.- S3에서 특정 파일(Object)에 PresignedUrl(제한된 시간 동안 접근할 수 있도록 권한이 부여된 서명된 url(업로드 url))로 S3에 이미지를 저장하는 방법.
- 클라이언트에서 바로 S3로 업로드가 가능하기 때문에, 보안을 위해 백엔드를 걸쳐 이미지를 업로드 시켰다.
Node에서 PresignedUrl 생성
createPresignedPost
를 이용해PresignedUrl
을 생성한다.key
부분은 이미지 저장 경로 및 파일 이름- ex)
image/<파일 이름>.<파일 확장자>
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
import aws from 'aws-sdk';
const { AWS_SECRET_KEY, AWS_ACCESS_KEY, AWS_REGION } = process.env;
export const s3 = new aws.S3({
secretAccessKey: AWS_SECRET_KEY,
accessKeyId: AWS_ACCESS_KEY,
region: AWS_REGION,
});
export const getSignedUrl = ({ key }: { key: string }) => {
return new Promise((resolve, reject) => {
s3.createPresignedPost(
{
Bucket: '<your bucket name>',
Fields: {
key, /* ex) image/<파일 이름>.<jpeg or png> */
},
Conditions: [
['content-length-range', 0, 50 * 1000 * 1000],
['starts-with', '$Content-Type', 'image/'],
],
},
(err, data) => {
if (err) reject(err);
resolve(data);
}
);
});
};
getSignedUrl
이용해 생성된PresignedUrl
하고imageKey(image/<파일 이름>.<jpeg or png>)
를 응답한다.
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
import express, { Request, Response, NextFunction } from 'express';
import { s3, getSignedUrl } from '../aws';
const router = express.Router();
/* ... */
router.post('/presigned', async (req, res, next) => {
try {
const { contentTypes } = req.body;
if (!Array.isArray(contentTypes)) throw new Error('error');
const presignedData = await Promise.all(
contentTypes.map(async (conteType) => {
const imageKey = `${uuid()}.${mime.extension(conteType)}`;
const key = `image/${imageKey}`;
const presigned = await getSignedUrl({ key });
return { imageKey, presigned };
})
);
res.json(presignedData);
} catch (err) {
next(err);
}
});
/* ... */
export default router;
Client(React)에서 PresignedUrl로 이미지 업로드
PresignedUrl
생성하고 응답받은presignedData(imageKey(이미지 파일 이름), PresignedUrl(업로드 url))
을 이용해Client
에서 S3에 이미지를 저장한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const imgContentTypes = imgFile.map((el) => el?.type);
/* presignedUrl 생성하기 */
const res = await request.post('/upload/presigned', {
contentTypes: imgContentTypes,
});
/* s3 이미지 저장 */
await Promise.all(
imgFile.map((file, index) => {
const { presigned } = res.data[index];
const formData = new FormData();
for (const key in presigned.fields) {
formData.append(key, presigned.fields[key]);
}
file && formData.append('Content-Type', file.type);
file && formData.append('file', file);
return axios.post(presigned.url, formData);
})
);
이미지 삭제
Bucket
부분에 해당 S3 버킷 이름을 적어준다.Key
부분에 버킷에서 삭재할 이미지 경로와 파일 이름을 적어준다.- 버킷에서
image
폴더에 이미지 저장했기 때문에Key
부분에<폴더>/<삭제할 이미지 파일 이름>.<파일 확장자>
를 입력했다. - ex)
image/<img 파일 이름>.jpeg
- 버킷에서
- 콜백으로 에러와 데이터를 확인할 수 있다.
ex)
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
31
import express, { Request, Response, NextFunction } from 'express';
import { s3, getSignedUrl } from '../aws';
const router = express.Router();
/* ... */
router.delete(
'/image/:id',
async (req: Request, res: Response, next: NextFunction) => {
try {
await s3.deleteObject(
{
Bucket: '<your bucket name>',
Key: `image/${req.params.id}`,
},
(err, data) => {
if (err) throw err;
}
);
res.json({
message: 'success',
});
} catch (err) {
next(err);
}
}
);
/* ... */
export default router;