Node.js 웹서버에서 프로그래밍적으로 S3 버킷 데이터를 사용하기 위한 전처리 작업입니다. S3 버킷에서 데이터를 추가하는 작업을 할 예정입니다. 실제 데이터를 브라우저를 통해 POST요청을 보낼떄 버킷에 업로드함과 동시에 업로드된 이미지 url을 데이터베이스에 추가하기 위해 필요합니다.

작업환경

  • koa
  • koa-router
  • aws-sdk
  • multer
  • multer-s3

작업순서

S3 버킷을 만들었다면 accessKeyIdsecretAccessKey를 다운받기 위해 우측상단에 있는 내 계정을 클릭해서 내 보안 자격 증명(Credential) 로 들어갑니다.

새 액세스 키 만들기 를 눌러 액세스키를 새로 만듭니다.

액세스 키 표시 를 누릅니다.

다음과 같이 액세스 키 ID보안 액세스 키가 나타납니다.

아래와 같이 패키지를 설치합니다.

yarn add koa koa-router aws-sdk koa-multer multer-s3

라우팅을 할 수 있게 기본적인 웹서버 세팅을 합니다.

// app.js
const Koa = require('koa');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();

app.use(router.routes());

app.listen(3000);

라우팅까지 끝났으면 S3를 node.js에서 연결시킵니다. 필요한 패키지들을 불러온 다음 아까 발급받은 accessKeyIdsecretAccessKey를 사용하고 지역은 버킷을 만들때 아시아 태평양(서울) 로 설정했다면 ap-northeast-2로 작성하면 됩니다.

// s3.js
const AWS = require('aws-sdk');
const multer = require('koa-multer');
const multerS3 = require('multer-s3');
const path = require('path');

aws-sdk에 새로운 s3객체를 만들어 키를 입력합니다. 방금 발급받은 정보들을 입력하고 다른 s3 api를 이용하기 위해서 파라미터 객체를 만듭니다.

// s3.js
const s3 = new AWS.S3({
  accessKeyId: AWS_ACCESS_KEY_ID,
  secretAccessKey: AWS_SECRET_ACCESS_KEY,
  region: AWS_REGION
});

let params = {
  Bucket: 'BUCKET_NAME',
  ACL: 'public-read-write'
};

이제 파일을 웹에서 업로드하는 일만 남았는데요. multer-s3는 koa-multer를 이용해서 사용할 수 있습니다. koa에서 multer를 사용하면 다음과 같이 에러가 발생하기 때문에 사용하는 프레임워크에 맞는 패키지를 설치해야합니다.

TypeError: req.pipe is not a function

일단 먼저 multer의 기본적인 코드는 다음과 같습니다.

// s3.js
let storage = 'upload/';
exports.upload = multer({ storage: storage });

// app.js
const s3 = require('./s3');

app.post('/upload/singe', s3.upload.single('file_name'), ctx => {
  body = ctx.req.file;
});
app.post('/upload/array', s3.upload.array('file_name'), ctx => {
  body = ctx.req.files;
});
app.post('/upload/any', s3.upload.array('file_name'), ctx => {
  body = ctx.req.files;
});

multer()의 옵션에 해당하는 인자에 객체를 입력합니다. 객체의 키로는 파일을 저장할 위치를 가리키는 deststorage를 사용할 수 있습니다.

  • dest: 저장되는 디렉토리만 설정할 수 있습니다. storage에서 사용하는 값은 올 수 없습니다.
  • storage: 저장되는 파일명이나 인코딩 등을 조작할 수 있습니다. dest의 값으로 올 수 있는 디렉토리 이름도 사용할 수 있습니다.

storage키와 같이 사용할 수 있는 옵션이 바로 multer에서 말하는 스토리지 엔진 입니다. 디스크 스토리지는 파일은 디스크에 저장하기 위한 모든 제어기능을 제공합니다. destinationfilename 두가지의 옵션을 사용할 수 있습니다. multer 공식문서에서 다른 옵션을 볼 수 있습니다.

koa 라우터에 미들웨어로 들어가는 옵션은 다음과 같습니다.

  • single(): 인자에 명시된 이름의 단수 파일을 받습니다. body.req.file에 저장됩니다.
  • array(): 인자에 명시된 이름의 파일 전부를 배열 형태로 받습니다. body.req.files에 저장됩니다.
  • any(): 전달된 모든 파일을 허용합니다. body.req.files에 저장됩니다.

multer 공식문서에 들어가시면 더 많은 옵션을 볼 수 있습니다.

이대로 파일을 업로드하면 파일명도 확장자도 알아서 바껴버리기 때문에 아까 언급한 스토리지 엔진을 아래와 같이 사용합니다.

// s3.js
let diskStorage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'upload');
  },
  filename: (req, file, cb) => {
    let extension = path.extname(file.originalname);
    let basename = path.basename(file.originalname, extension);
    cb(null, `images/${basename}-${Date.now()}${extension}`);
  }
});

exports.upload = multer({ storage: diskStorage });

upload 디렉토리에 파일을 올리고 파일명은 파일 중복을 막기 위해 Number타입으로 리턴된 날짜를 파일명 앞에 접착합니다.

이제 포스트맨에서 파일이 업로드가 제대로 되는지 테스트를 해보겠습니다. 요청을 보내는 순서는 아래와 같습니다.

  • HTTP 메서드를 POST로 설정합니다.
  • Body에서 form-data를 선택합니다.
  • KEY를 app.js에 post 라우터에 들어간 s3.upload.array('file_name')에서 file_name과 같이 동일한 이름으로 설정한다.
  • VALUE에 파일을 업로드합니다.
  • SEND를 누릅니다.

3개의 파일을 로컬로 업로드했습니다. 응답에 데이터가 뜨는 것과 프로젝트 디렉토리에 있는 upload디렉토리에 파일이 생성됐음을 확인합니다.

[
  {
    "fieldname": "file",
    "originalname": "1.txt",
    "encoding": "7bit",
    "mimetype": "text/plain",
    "destination": "upload",
    "filename": "1-1553254524481.txt",
    "path": "upload\\1-1553254524481.txt",
    "size": 1
  },
  {
    "fieldname": "file",
    "originalname": "2.txt",
    "encoding": "7bit",
    "mimetype": "text/plain",
    "destination": "upload",
    "filename": "2-1553254524482.txt",
    "path": "upload\\2-1553254524482.txt",
    "size": 1
  },
  {
    "fieldname": "file",
    "originalname": "3.txt",
    "encoding": "7bit",
    "mimetype": "text/plain",
    "destination": "upload",
    "filename": "3-1553254524482.txt",
    "path": "upload\\3-1553254524482.txt",
    "size": 1
  }
]

다음은 multer-s3를 적용해서 s3버킷에 보내고 바디를 받아오겠습니다. multer-s3 사용법은 multer 스토리지 엔진 부분에 모듈을 집어넣어주는 것이 전부입니다.

// s3.js
let s3Storage = multerS3({
  s3: s3,
  bucket: params.Bucket,
  key: function(req, file, cb) {
    let extension = path.extname(file.originalname);
    let basename = path.basename(file.originalname, extension);
    cb(null, `images/${basename}-${Date.now()}${extension}`);
  },
  acl: 'public-read-write',
  contentDisposition: 'attachment',
  serverSideEncryption: 'AES256'
});

exports.upload = multer({ storage: s3Storage });

key 설정에서 파일명 앞에 디렉토리명을 써서 올리면 해당 디렉토리 안으로 파일이 업로드됩니다. 자세한 옵션들은 multer-s3 npm 문서를 참고하시기 바랍니다.

s3에 업로드하면 바디에 다음과 같이 찍힙니다.

[
  {
    "fieldname": "file",
    "originalname": "1.txt",
    "encoding": "7bit",
    "mimetype": "text/plain",
    "size": 1,
    "bucket": "BUCKET_NAME",
    "key": "images/1-1553254491794.txt",
    "acl": "public-read-write",
    "contentType": "application/octet-stream",
    "contentDisposition": "attachment",
    "storageClass": "STANDARD",
    "serverSideEncryption": "AES256",
    "metadata": null,
    "location": "AWS_S3_URL",
    "etag": "\"c4ca4238a0b923820dcc509a6f75849b\""
  },
  {
    "fieldname": "file",
    "originalname": "2.txt",
    "encoding": "7bit",
    "mimetype": "text/plain",
    "size": 1,
    "bucket": "BUCKET_NAME",
    "key": "images/2-1553254491795.txt",
    "acl": "public-read-write",
    "contentType": "application/octet-stream",
    "contentDisposition": "attachment",
    "storageClass": "STANDARD",
    "serverSideEncryption": "AES256",
    "metadata": null,
    "location": "AWS_S3_URL",
    "etag": "\"c81e728d9d4c2f636f067f89cc14862c\""
  },
  {
    "fieldname": "file",
    "originalname": "3.txt",
    "encoding": "7bit",
    "mimetype": "text/plain",
    "size": 1,
    "bucket": "BUCKET_NAME",
    "key": "images/3-1553254491795.txt",
    "acl": "public-read-write",
    "contentType": "application/octet-stream",
    "contentDisposition": "attachment",
    "storageClass": "STANDARD",
    "serverSideEncryption": "AES256",
    "metadata": null,
    "location": "AWS_S3_URL",
    "etag": "\"eccbc87e4b5ce2fe28308fd9f2a7baf3\""
  }
]

파일이 올라갔고 버킷안에 있는 모든 파일 목록을 불러오는 api를 사용합니다.

// s3.js
s3.listObjectsV2({ Bucket: params.Bucket }, (err, data) => {
  if (err) {
    throw err;
  } else {
    let arr = [];
    let contents = data.Contents;
    contents.forEach(content => arr.push(content.Key));
    console.log({ dataList: arr });
  }
});

다음과 같이 콘솔에 출력됩니다.

{ dataList:
   [ 'images/11553254491794.txt',
     'images/21553256161466.txt',
     'images/31553254491795.txt' ] }

깃헙에 코드를 공유했으니 전체 코드가 필요하신 분은 여기를 눌러주세요.

참조