Node.js

node> 이미지 업로드 기능 만들기 (AWS S3 셋팅)

연습노트 2024. 7. 27. 16:44

유저가 글작성시 이미지를 업로드할 수 있게 만들어봅시다.

업로드한 이미지를 하드디스크에 저장해놓고 필요할 때 보여주면 될텐데 

클라우드에 올리다보면 하드디스크 사용이 어려울 수 있을 때가 있어서 

AWS S3같은 파일저장용 클라우드 서비스 빌려서 거기 업로드해놓는게 좋습니다. 

첫가입시 카드등록까지 마치면 S3는 무료로 5GB까지 1년 이용가능하니까

AWS S3 사용해서 이미지 파일 올리는 법을 좀 알아봅시다. 

실은 구글 클라우드가 약간 더 싼데 사람들이 AWS를 좋아해서 그거나 씁시다. 

 

 

 

 

 

Access 키 발급

 

일단 AWS 사이트 들어가서 가입하고 카드등록까지 마칩시다. 

이제 서버에서 AWS기능을 이용하고 싶으면

서버파일에서 코드짤 때 Access key가 하나 필요합니다. 

그거 발급하려면 AWS 사이트 상단에서 IAM 검색해서 들어갑시다. 

 

 

 

 

 

▲ 아니면 우측 위에 님들 이름눌러도 아마 들어갈 수 있음

 

 

 

 

▲ 1. 좌측에서 사용자를 하나 만들어줍니다.

이게 뭐냐면 님의 AWS 계정에 여러 사람이 접속해서 일할 수도 있기 때문에 

님 AWS 권한을 대신 사용가능한 아이디 하나 새로 만드는 것임 

 

 

 

 

▲ 사용자 이름은 아무렇게나 하고 권한 설정 메뉴에선 

AmazonS3FullAccess 권한을 체크하고 하나 만들어주면 됩니다. 

방금 뭐한거냐면 S3 액세스 권한이 있는 사용자를 하나 만들어준 것임 

 

 

 

 

 

▲ 2. 방금 만든 사용자 눌러보면 

액세스키 만들기가 어딘가 있는데 그거 눌러서 액세스 키 하나 만들어 줍시다.

그럼 액세스키 비밀키 2개를 발급해주는데 코딩할 때 사용하게 안전한 곳에 보관해둡시다. 

이거 털리면 님들 AWS 요금 월 1억 나옵니다. 

그리고 위 사진에 보면 ARN도 있는데 이것도 필요하니까 복사해둡시다. 

 

 

 

 

 

 

 

 

AWS S3 셋팅

 

S3를 이용하고 싶으면

AWS 사이트 상단에서 S3 검색해서 들어가봅시다.

그리고 어딘가에 있는 버킷만들기 버튼 누르면 됩니다. 

 

 

 

▲ 버킷은 여러분만의 저장공간입니다. 

버킷 이름 작명은 유니크하게 해줍시다.

 

 

 

 

 

 

▲ 퍼블릭 액세스 설정을 이렇게 바꿔줍시다. 그래야 개발시 편합니다. 

대충 이것만 채워서 버킷만들면 됩니다. 

 

 

 

 

 

 

버킷정책 수정하기

 

보안을 신경써야한다면 일반 사람들은 읽기만 가능하게,

관리자는 수정, 삭제가 가능하게 바꾸는게 좋은데 

 

 

 

▲ 방금 만든 버킷 들어가서 권한 눌러서 버킷 정책 부분을 편집해봅시다.

누가 버킷을 읽기, 수정, 삭제 할 수 있는지 정의하는 부분입니다.

 

 

 

 

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::님들버킷명/*"
        },
        {
            "Sid": "2",
            "Effect": "Allow",
            "Principal": {
                "AWS": "님들ARN"
            },
            "Action": [
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Resource": "arn:aws:s3:::님들버킷명/*"
        }
    ]
} 

 

그래서 이렇게만 써놔도 일단은 안전한데 Sid : 1번은 모든 유저는 GET이 가능하고

2번은 관리자만 PUT, DELETE가 가능하다고 적어놓은 것입니다. 

 

- Actions 항목에서 

자료 읽기는 s3:GetObject

쓰기는 s3:PutObject

삭제는 s3:DeleteObject

를 의미합니다. 

 

- Principal 항목은 어떤 유저인지 명시하는 부분인데 * 쓰면 모든유저, 특정 AWS 계정을 넣고 싶으면 ARN 주소 넣으면 됩니다.

둘째 Principal 항목엔 님들이 액세스 키 발급받았던 그 사용자 계정의 ARN을 넣어줍시다. 

ARN은 까먹었으면 IAM - 사용자 메뉴 들어가서 다시 복사해오면 됩니다. 

 

- Resource는 어떤 버킷인지 버킷명 잘 적어주면 됩니다. 

제 경우는 arn:aws:s3:::codingappleimage1/* 이렇게 적어봄 

 

참고로 버킷 사용권한을 ACL 어쩌구로 설정하는 방법도 있는데 

실은 옛날에 쓰던 방법입니다. 지금은 이런 방식을 권장합니다. 

 

 

 

 

 

 

 

그 밑에 CORS 설정은

 

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

어떤 사이트에서 버킷안의 파일들을 읽기, 쓰기, 삭제할 수 있는지 CORS 설정하는 부분입니다.

 

저번시간에 AWS 셋팅 완료했으면

이제 글과 함께 이미지를 업로드할 수 있는 기능을 만들어봅시다. 

 

1. 유저가 글과함께 이미지를 서버로 전송하면

2. 서버는 그걸 S3에 저장시켜주면 끝이겠죠

근데 저장하면 끝이아니라

나중에 그 이미지를 html페이지 이런데서 보여주고 싶으면 어떻게 하죠? 

어떻게 하냐면 S3에 파일이나 이미지를 업로드하면 

URL을 하나 알려주는데 그 URL로 접속하면 이미지가 나옵니다. 

그래서 이미지 URL을 DB에 글과 함께 저장해뒀다가 필요할 때 꺼내쓰면 됩니다. 

 

 

그래서 글과함께 이미지 업로드할 수 있는 기능을 쭉 한글로 정리부터 하면 

1. 글작성페이지에 이미지 업로드할 수 있는 인풋하나 만들고 

2. 글과 이미지를 서버로 전송시 서버는 전송된 이미지를 S3에 저장시키고 

3. 그럼 URL을 하나 뱉어주는데 그걸 DB에 글과함께 저장

4. 나중에 이미지 필요할 때 DB에 있던 URL 꺼내쓰기 

 

 

 

 

1. 글작성 페이지에 input 하나 만들기

 

write.ejs로 들어가봅시다.

 

<form class="form-box" action="/add" method="POST" enctype="multipart/form-data">
    <input type="file" name="img1" accept="image/*">
    (생략)
</form> 

이미지나 파일을 서버로 전송하고 싶으면 

- <form>태그엔 enctype을 저렇게 설정해주고

- <input>에 type="file"과 name 속성도 잘 달아주면 됩니다.

 

그리고 accept 속성으로 이미지파일만 선택할 수 있게 제한할 수 있는데 제한이 아니라 권장일 뿐이라

파일타입을 아예 제한하고 싶으면 서버에서 확장자 검사해보는게 좋습니다.

 

 

 

 

 

2. 서버는 전송된 이미지를 S3에 저장시킴 

 

서버에서 multer나 formidable이라는 라이브러리를 쓰면

업로드한 파일같은걸 좀 쉽게 다룰 수 있기 때문에 multer 이거 설치해서 사용해보도록 합시다.

 

 

npm install multer multer-s3 @aws-sdk/client-s3 

터미널 열어서 설치합시다. 

multer가 기본 라이브러리고 뒤에 2개는 S3 이용을 위해 필요한 라이브러리들입니다. 

 

 

 

const { S3Client } = require('@aws-sdk/client-s3')
const multer = require('multer')
const multerS3 = require('multer-s3')
const s3 = new S3Client({
  region : 'ap-northeast-2',
  credentials : {
      accessKeyId : 'IAM에서 발급받은 액세스키',
      secretAccessKey : 'IAM에서 발급받은 시크릿키'
  }
})

const upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: '님들버킷이름',
    key: function (요청, file, cb) {
      cb(null, Date.now().toString()) //업로드시 파일명 변경가능
    }
  })
})

server.js 상단에 복붙해두면 라이브러리 셋팅 끝입니다. 

이제 중간중간에 설정 잘 기입하면 되는데

 

- region : 부분은 S3 리전 설정하는 부분인데 서울로 s3 셋팅해놨으면 ap-northeast-2 기입합시다.   

- AWS의 IAM 메뉴에서 키랑 시크릿키 발급받아온 것도 집어넣읍시다. 털리면 안되니까 .env 파일 쓰는게 어떨까요 

- 버킷이름 설정하고 

- 저장할 파일명도 맘대로 바꿀 수 있습니다. 

파일명을 안겹치게하려면 랜덤문자를 해싱해서 집어넣던가 아니면 현재시간을 섞거나 그래도 됩니다.

원래 파일명은 요청.file 하면 나옵니다.

 

 

 

 

app.post('/add', upload.single('img1'), (요청, 응답) => {
  console.log(요청.file)
  (생략)
}) 

셋팅 잘 해놨으면 원하는 곳에서 upload.single() 라고 미들웨어를 추가해주면 

이제 img1 이런 name속성을 가진 이미지가 들어올 때 마다 S3에 자동으로 업로드해줍니다.

그래서 글발행해주는 /add API 에다가 추가해봤습니다.

S3 업로드 기능 구현 끝 

 

그리고 업로드후엔 URL이 하나 생성되는데 그건 요청.file 아니면 요청.files 안에 들어있기 때문에 그걸 맘대로 가져다가 쓰면 되겠습니다.

남이 하는 말 호구처럼 그대로 믿지 말고

업로드 진짜 되나 직접 테스트해보고 S3도 들어가봅시다.

 

 

 

 

(참고) 여러장의 이미지를 동시에 업로드 하고 싶으면

<input multiple> 이런거 폼에 넣어두면 여러개 이미지 선택이 가능하고

서버에선 upload.single() 대신 upload.array(‘input의 name속성 이름’, 3) 를 씁시다. 

3이라는 숫자는 한 번에 업로드할 이미지 최대갯수설정인데 맘대로 바꿉시다. 

 

 

 

 

 

 

3. 이미지 URL을 글과 함께 DB에 저장

 

왜 저장하냐구요? 

그래야 나중에 글 보여줄 때 업로드한 이미지도 보여주고 그럴거 아닙니까 

그래서 글 document에 이미지 URL도 함께 저장해보도록 합시다. 

참고로 이미지가 많으면 document 안에 array자료 같은것도 집어넣을 수 있기 때문에 그렇게 저장해도 상관없습니다.

 

 

 

app.post('/add', upload.single('img1'), (요청, 응답) => {
  console.log(요청.file)
  await db.collection('post').insertOne({
    title : 요청.body.title,
    content : 요청.body.content,
    img : 요청.file.location
  })
  (생략)
}) 

DB에 글발행할 때 img : 항목도 집어넣으라고 했습니다.

요청.file 안에 있던 이미지 URL을 넣어봤습니다.

 

참고로 <img src=" "> 안에 이미지 URL을 집어넣으면 이미지를 html 파일안에서 보여줄 수 있습니다.

 

 

 

 

 

 

에러처리 하고싶으면

 

S3가 이상하거나 그러면 에러가 날 수 있습니다.

이상한 에러가 나면 서버도 멈출 수 있기 때문에 그러기 싫으면 

에러상황 처리하는 코드도 잘 작성해둡시다.

 

app.post('/add', (요청, 응답) => {
    upload.single('img1')(요청, 응답, (err)=>{
      if (err) return 응답.send('에러남')
      이미지 업로드성공시 실행할 코드~~
    })
}) 

upload.single()을 여기 작성하면 에러처리도 가능해서 이렇게 쓰는게 나을 수 있습니다.

이미지 업로드 성공시 글발행하라고 코드 수정하면 될듯요