multer

2023. 7. 20. 17:25node.js

 

inpa Dev 님의 블로그를 참고하여 적은 글입니다.

multer 란?

이미지나 동영상 등을 비롯한 여러가지 파일들을 멀티파트 형식으로 업로드 할 때 사용하는 미들웨어 이다.

멀트파트 형식이란 enctype 이 multipart/form-data 인 폼을 통해 업로드하는 데이터의 형식을 의미한다.

multer 문법

storage 는 저장할 공간에 대한 정보, 디스크나 메모리 저장 기능

diskStorage 는 하드디스크에 업로드 파일을 저장한다는 것

destination 은 저장할 경로

filename 은 저장할 파일명(파일명 + 날짜 + 확장자 형식)

limits 는 파일 개수나 파일 사이즈를 제한할 수 있다.

const multer = require('multer');
const fs = require('fs');

try {
	fs.readdirSync('uploads'); // 폴더 확인
} catch(err) {
	console.error('uploads 폴더가 없습니다. 폴더를 생성합니다.');
    fs.mkdirSync('uploads'); // 폴더 생성
}

const upload = multer({
    storage: multer.diskStorage({ // 저장한공간 정보 : 하드디스크에 저장
        destination(req, file, done) { // 저장 위치
            done(null, 'uploads/'); // uploads라는 폴더 안에 저장
        },
        filename(req, file, done) { // 파일명을 어떤 이름으로 올릴지
            const ext = path.extname(file.originalname); // 파일의 확장자
            done(null, path.basename(file.originalname, ext) + Date.now() + ext); // 파일이름 + 날짜 + 확장자 이름으로 저장
        }
    }),
    limits: { fileSize: 5 * 1024 * 1024 } // 5메가로 용량 제한
});
// => 이렇게 설정한 upload라는 객체를 뒤에 라우터에 장착하면 된다.

multer 에 넣은 인수들은 다음과 같다.

먼저 storage 속성에 어디에 (destination) 어떤 이름으로 (filename) 저장할지를 넣었다.

두 함수의 req 매개변수에는 요청에 대한 정보, file 객체에는 업로드한 파일에 대한 정보가 있으며, done 매개변수는 함수이다.

done() 함수는 첫 번째 인수에는 에러가 있다면 에러를 넣고, 두 번째 인수에는 실제 경로나 파일 이름을 넣어 주면 된다.

즉 req 나 file 의 데이터를 가공해 done 으로 넘기는 식이다.

upload.single()

설정이 끝나면 위에서 선언한 upload 객체 변수가 생기게 되는데 이 안에 다양한 종류의 미들웨어가 존재한다.

파일을 하나만 업로드 하는 경우에는 single 미들웨어를 사용한다.

// 단순 웹페이지 get요청 들어오면 html을 띄워준다.
app.get('/upload', (req, res) => {
	res.sendFile(path.join(__dirname, 'multipart.html'));
}

// 위에서 설정한 upload 객체 변수를 라우터에 장착 한다.
// 이미지 업로드 같은 동작은 특정 라우터에만 일어나기 때문에, app.use()에는 장착하지 않는 편이다.
app.post('/upload', upload.single('image'), (req, res) => { // 'image'라는 이름은 multipart.html의 <input type="file" name="image"> 에서 폼데이터 이름으로 온 것이다.
    
    // upload.single('image')의 업로드 정보가 req.file에 넣어진다.
    // <input type="text" name="title"> 의 텍스트 정보가 req.body에 넣어진다.
    console.log(req.file, req.body); 
    res.send('ok');
})

single 미들웨어를 라우터 미들웨어 앞에 넣으면 multer 설정에 따라 파일 업로드 후 req.file 객체가 생성된다.

인수는 input 태그의 name 이나 폼데이터의 키와 일치하게 넣으면 된다.

업로드가 성공하면 결과는 req.file 객체 안에 들어간다.

req.file 객체는 다음과 같이 생김

{
    fieldname: 'img',
    originalname: 'hello.png',
    encoding: '7bit',
    mimetype: 'image/png',
    destination: 'uploads/',
    filename: 'hello1567238581123.png',
    path: 'uploads//hello1567238581123.png',
    size: 44933
}

upload.array()

여러 파일을 업로드 하는 경우엔 HTML 의 input 태그에 multiple 을 쓴다.

<form name="이 폼의 이름" action="이 데이터들을 받을 파일" method="post" enctype="multipart/form-data">
     <input type='file' name='many' multiple/>
</form>

그리고 미들웨어는 single 대신 array 로 교체하면 된다. 이때 업로드 정보들은 배열로 저장된다.

app.post('/upload', upload.array('many'), (req, res) => {
    console.log(req.files); // 업로드 결과도 req.file 대신 req.files 배열에 들어간다
    res.send('ok');
});

upload.field()

한번에 파일을 여러 개 업로드 하는게 아닌, 따로따로 업로드를 여러개 한다면, 즉 input 태그나 폼 데이터의 키가 다른 경우엔 fields 미들웨어를 사용한다.

<form action="/upload" method="post" enctype="multipart/form-data">
  <input type="file" name="image1">
  <input type="file" name="image2">
  <input type="file" name="image3">
  <input type="text" name="title">
  <button type="submit">Submit</button>
</form>

이 경우엔 fields 미들웨어를 사용하고, 인수로 input 태그들의 name 들을 각각 적는다.

name 뿐만 아니라 limits 도 설정할 수 있는데 이미지를 다섯장까지만 넣는다라는 설정이다.

app.post('/upload',
    upload.fields([{ name: 'image1', limits: 5 }, { name: 'image2' }, { name: 'image3' }]), // 배열 객체를 넣는다.
    (req, res) => {
    	// 업로드 결과는 각각 req.files.image1, req.files.image2에 들어간다.
        console.log(req.files.image1);
        console.log(req.files.image2);
        console.log(req.files.image3);
        res.send('ok');
    }
)

upload.none()

특수한 경우에, 파일이 아님에도 멀티파트 형식으로 업로드 하는 경우가 있다.

new FormData() 로 데이터를 보낼때도 있기 때문이다.

그 경우 none 미들웨어를 사용한다.

파일은 없지만 멀티파트 형식이기 때문에 함수가 따로 존재한다.

app.post('/upload', upload.none(), (req, res) => {
    console.log(req.body.title); // 이 경우 파일을 업로드하지 않으므로 req.body만 존재한다.
    res.send('ok');
});

multer-s3

사용법

게시글 이미지를 AWS S3 에 저장하고 싶을때 사용한다.

AWS S3 버킷에 이미지 파일을 저장하고, DB 엔 그 버킷의 이미지 파일 경로(이미지 주소)를 저장하고, 서버는 이 경로를 클라이언트로 응답하는 식으로 프로세스를 구축한다.

npm i multer 
npm i multer-s3@2.10.0  # aws sdk v2 버젼을 사용하기에 multer-s3도 v2용으로
const express = require('express');
const multer = require('multer');
const path = require('path');
const AWS = require("aws-sdk");
const multerS3 = require('multer-s3');
const dotenv = require('dotenv');
dotenv.config();

const router = express.Router();

//* aws region 및 자격증명 설정
AWS.config.update({
   accessKeyId: process.env.S3_ACCESS_KEY_ID,
   secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
   region: 'ap-northeast-2',
});

//* AWS S3 multer 설정
const upload = multer({
   //* 저장공간
   // s3에 저장
   storage: multerS3({
      // 저장 위치
      s3: new AWS.S3(),
      bucket: 'test-bucket-inpa',
      acl: "public-read",
      contentType: multerS3.AUTO_CONTENT_TYPE,
      key(req, file, cb) {
         cb(null, `${Date.now()}_${path.basename(file.originalname)}`) // original 폴더안에다 파일을 저장
      },
   }),
   //* 용량 제한
   limits: { fileSize: 5 * 1024 * 1024 },
});

//* 로컬 multer 설정
const upload2 = multer(); // upload2.none() 용으로 사용

//* 싱글 이미지 파일 업로드 -> uploads/ 디렉토리에 이미지를 올린다.
router.post('/img', isLoggedIn, upload.single('img'), (req, res) => {
   console.log(req.file);
   res.json({ url: req.file.location });
});

//* 게시글 업로드
//* 프론트에서 multipart/form-data" > textarea 형식으로 넘어오기 때문에 upload2.none()형식으로 받아와 준다.
router.post('/', isLoggedIn, upload2.none(), async (req, res, next) => {
   // ...
});

module.exports = router;

위에서 설정한 upload 라는 함수가 실행되면, 설정한 이름의 Bucket 에 파일을 업로드 할 수 있게 처리된다.

key 속성은 업로드하는 파일이 어떤 이름으로 버킷에 저장되는가에 대한 속성이다.

위 속성대로라면, 버킷에 업로드 되는 파일(객체) 의 이름은 현재 시각_파일명 이 되게 된다.

클라이언트에서 이미지를 업로드하고, 서버로 페이로드에 폼데이터를 담아 보내면, 유저의 AWS S3 버킷에 업로드한 파일(객체)를 저장하게 된다.

그리고 이렇게 저장된 이미지의 메타데이터를 req.file 로 받을 수 있다.

그리고 응답으로 req.file.location 정보를 보내주면, s3 객체 url 이 전달되어 이미지를 불러올 수 있다.

주의점

upload 함수는 S3 용 multer 함수 이기에 form-data 로 넘어온 text 를 따로 처리해주어야 한다.

예를 들어 게시글을 작성하고 서버에 포스팅 api 를 보낼때, 게시글에 사용된 이미지는 S3 에 저장되게 하고, 게시글 글 내용은 DB 에 저장해 FULLTEXT INDEX 처리를 해야 하기 때문이다.

//* 로컬 multer 설정
const upload2 = multer(); // upload2.none() 용으로 사용

// ...

//* 게시글 업로드
//* 프론트에서 multipart/form-data" > textarea 형식으로 넘어오기 때문에 upload2.none()형식으로 받아와 준다.
router.post('/', isLoggedIn, upload2.none(), async (req, res, next) => {
   // ...
});

옵션

S3 폴더 별로 업로드

만일 보다 명확한 파일 관리를 위하여 따로 폴더를 만들어 그 폴더 안에서만 이미지 파일을 관리하고 싶다면?

multer s3 설정에서 파일명 자체에 경로를 줘서 설정하면 된다.

객체 ACL 설정하기

버킷에 파일을 업로드할때 다음과 같이 추가 프로퍼티로 acl을 지정하여 보낼 수 있게 설정할 수 있다.

보통 게시글의 이미지는 브라우저에 바로 검색해서 볼 수 있는 것이 일반적이니 public-read 로 설정하였다.

var upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'some-bucket',
    acl: 'public-read', // 공개
    key: function (req, file, cb) {
      cb(null, Date.now().toString())
    }
  })
})

Cache-Control 헤더 설정

cache 를 사용하겠다는 의미, max-age 는 캐시 유효시간

var upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'some-bucket',
    cacheControl: 'max-age=31536000',
    key: function (req, file, cb) {
      cb(null, Date.now().toString())
    }
  })
})

Content-Type 설정

선택적 contentType 옵션을 사용하여 파일의 Content/mime 유형을 설정할 수 있다.

기본적으로 콘텐츠 유형은 application/octet-stream 으로 설정된다.

따라서 이미지와 같은 파일을 다루기 위해서는 multer-s3 가 파일의 내용 유형을 자동으로 찾도록 multerS3.AUTO_CONTENT_TYPE 상수를 사용하여 contentType 을 지정하도록 해야 된다.

var upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'some-bucket',
    contentType: multerS3.AUTO_CONTENT_TYPE,
    key: function (req, file, cb) {
      cb(null, Date.now().toString())
    }
  })
})

multer-s3-transform 을 이용한 이미지 리사이징

고용량의 이미지를 그대로 S3 에 올리는 것은 효율적이지 않다.

따라서 고용량의 이미지를 서버에서 직접 리사이징을 통해 최적화후 S3 에 올려보자

 

npm install multer-s3-transform sharp
const multer = require("multer");
const multerS3 = require("multer-s3-transform"); // multer-s3이 아닌 multer-s3-transform을 임포트
const sharp = require("sharp");

const ImageUpload = multer({
  storage: multerS3({
    s3,
    bucket: "버켓경로",
    contentType: multerS3.AUTO_CONTENT_TYPE,
    shouldTransform: true,
    transforms: [
      {
        id: "resized",
        key: function (req, file, cb) {
          let extension = path.extname(file.originalname);
          cb(null, Date.now().toString() + extension);
        },
        transform: function (req, file, cb) {
          cb(null, sharp().resize(100, 100)); // 이미지를 100x100 으로 리사이징
        },
      },
    ],
    acl: "public-read-write",
  }),
});

const uploadImageMulterMiddleware = ImageUpload.single("file");

주의점

multer-s3 를 require 하면 안된다. multer-s3-transform 을 require 해야한다.

나의 코드

// awsMulterModules.js

const multer = require("multer");
const AWS = require("aws-sdk");
const multerS3 = require("multer-s3");
const path = require("path");
require("dotenv").config();

AWS.config.update({
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  region: "ap-northeast-2",
});

module.exports = multer({
  storage: multerS3({
    s3: new AWS.S3(),
    bucket: "mini-be-bucket",
    contentType: multerS3.AUTO_CONTENT_TYPE,
    key(req, file, cb) {
      const filename = path.basename(file.originalname).trim();
      let newFilename = "";
      for (let value of filename) {
        if (value == "" || value === "_") {
          value = "-";
        }
        newFilename += value;
      }
      cb(null, `upload/${Date.now()}-${newFilename}`);
    },
  }),
  limits: {
    fileSize: 100 * 1024 * 1024,
  },
});
// posts.js

const express = require("express");

const {
  createPost,
  findAllPosts,
  findPost,
  updatePost,
  deletePost,
} = require("../controllers/posts.controller");

const authMiddleware = require("../middleware/auth-middleware");
const upload = require("../multer/awsMulterModules");

const router = express.Router();

router.post("/", authMiddleware, upload.single("imgFile"), createPost);
router.get("/", findAllPosts);
router.get("/:postId", findPost);
router.put("/:postId", authMiddleware, updatePost);
router.delete("/:postId", authMiddleware, deletePost);

module.exports = router;
// posts.controller.js

const { Posts, Users } = require("../models");
const {
  postSchema,
  updatePostSchema,
} = require("../validations/posts-validation");

const createPost = async (req, res) => {
  try {
    let image = req.file.location;
    if (!image) {
      image = "<http://fweusdfn.html>";
    }
    console.log(req.file);
    console.log(image);
    const { title, content } = await postSchema.validateAsync(req.body);

    const { userId } = res.locals.user;

    const existUser = await Users.findOne({ where: { userId } });

    if (!existUser) {
      return res
        .status(404)
        .json({ errorMessage: "해당 유저가 존재하지 않습니다. " });
    }
    console.log(image.replace(/\\/original\\//, "/thumb/"));
    const savedPost = await Posts.create({
      userId,
      title,
      content,
      name: existUser.name,
      imgsrc: image.replace(/\\/original\\//, "/thumb/"),
    });
    return res.status(201).json({ message: "게시글을 생성하였습니다." });
  } catch (error) {
    console.log(error);
    if (error.isJoi) {
      return res.status(412).json({ errorMessage: error.message });
    }
    return res
      .status(400)
      .json({ errorMessage: "게시글 생성에 실패하였습니다. " });
  }
};

참고 링크multer

multer

multer 란?

이미지나 동영상 등을 비롯한 여러가지 파일들을 멀티파트 형식으로 업로드 할 때 사용하는 미들웨어 이다.

멀트파트 형식이란 enctype 이 multipart/form-data 인 폼을 통해 업로드하는 데이터의 형식을 의미한다.

multer 문법

storage 는 저장할 공간에 대한 정보, 디스크나 메모리 저장 기능

diskStorage 는 하드디스크에 업로드 파일을 저장한다는 것

destination 은 저장할 경로

filename 은 저장할 파일명(파일명 + 날짜 + 확장자 형식)

limits 는 파일 개수나 파일 사이즈를 제한할 수 있다.

const multer = require('multer');
const fs = require('fs');

try {
	fs.readdirSync('uploads'); // 폴더 확인
} catch(err) {
	console.error('uploads 폴더가 없습니다. 폴더를 생성합니다.');
    fs.mkdirSync('uploads'); // 폴더 생성
}

const upload = multer({
    storage: multer.diskStorage({ // 저장한공간 정보 : 하드디스크에 저장
        destination(req, file, done) { // 저장 위치
            done(null, 'uploads/'); // uploads라는 폴더 안에 저장
        },
        filename(req, file, done) { // 파일명을 어떤 이름으로 올릴지
            const ext = path.extname(file.originalname); // 파일의 확장자
            done(null, path.basename(file.originalname, ext) + Date.now() + ext); // 파일이름 + 날짜 + 확장자 이름으로 저장
        }
    }),
    limits: { fileSize: 5 * 1024 * 1024 } // 5메가로 용량 제한
});
// => 이렇게 설정한 upload라는 객체를 뒤에 라우터에 장착하면 된다.

multer 에 넣은 인수들은 다음과 같다.

먼저 storage 속성에 어디에 (destination) 어떤 이름으로 (filename) 저장할지를 넣었다.

두 함수의 req 매개변수에는 요청에 대한 정보, file 객체에는 업로드한 파일에 대한 정보가 있으며, done 매개변수는 함수이다.

done() 함수는 첫 번째 인수에는 에러가 있다면 에러를 넣고, 두 번째 인수에는 실제 경로나 파일 이름을 넣어 주면 된다.

즉 req 나 file 의 데이터를 가공해 done 으로 넘기는 식이다.

upload.single()

설정이 끝나면 위에서 선언한 upload 객체 변수가 생기게 되는데 이 안에 다양한 종류의 미들웨어가 존재한다.

파일을 하나만 업로드 하는 경우에는 single 미들웨어를 사용한다.

// 단순 웹페이지 get요청 들어오면 html을 띄워준다.
app.get('/upload', (req, res) => {
	res.sendFile(path.join(__dirname, 'multipart.html'));
}

// 위에서 설정한 upload 객체 변수를 라우터에 장착 한다.
// 이미지 업로드 같은 동작은 특정 라우터에만 일어나기 때문에, app.use()에는 장착하지 않는 편이다.
app.post('/upload', upload.single('image'), (req, res) => { // 'image'라는 이름은 multipart.html의 <input type="file" name="image"> 에서 폼데이터 이름으로 온 것이다.
    
    // upload.single('image')의 업로드 정보가 req.file에 넣어진다.
    // <input type="text" name="title"> 의 텍스트 정보가 req.body에 넣어진다.
    console.log(req.file, req.body); 
    res.send('ok');
})

single 미들웨어를 라우터 미들웨어 앞에 넣으면 multer 설정에 따라 파일 업로드 후 req.file 객체가 생성된다.

인수는 input 태그의 name 이나 폼데이터의 키와 일치하게 넣으면 된다.

업로드가 성공하면 결과는 req.file 객체 안에 들어간다.

req.file 객체는 다음과 같이 생김

{
    fieldname: 'img',
    originalname: 'hello.png',
    encoding: '7bit',
    mimetype: 'image/png',
    destination: 'uploads/',
    filename: 'hello1567238581123.png',
    path: 'uploads//hello1567238581123.png',
    size: 44933
}

upload.array()

여러 파일을 업로드 하는 경우엔 HTML 의 input 태그에 multiple 을 쓴다.

<form name="이 폼의 이름" action="이 데이터들을 받을 파일" method="post" enctype="multipart/form-data">
     <input type='file' name='many' multiple/>
</form>

그리고 미들웨어는 single 대신 array 로 교체하면 된다. 이때 업로드 정보들은 배열로 저장된다.

app.post('/upload', upload.array('many'), (req, res) => {
    console.log(req.files); // 업로드 결과도 req.file 대신 req.files 배열에 들어간다
    res.send('ok');
});

upload.field()

한번에 파일을 여러 개 업로드 하는게 아닌, 따로따로 업로드를 여러개 한다면, 즉 input 태그나 폼 데이터의 키가 다른 경우엔 fields 미들웨어를 사용한다.

<form action="/upload" method="post" enctype="multipart/form-data">
  <input type="file" name="image1">
  <input type="file" name="image2">
  <input type="file" name="image3">
  <input type="text" name="title">
  <button type="submit">Submit</button>
</form>

이 경우엔 fields 미들웨어를 사용하고, 인수로 input 태그들의 name 들을 각각 적는다.

name 뿐만 아니라 limits 도 설정할 수 있는데 이미지를 다섯장까지만 넣는다라는 설정이다.

app.post('/upload',
    upload.fields([{ name: 'image1', limits: 5 }, { name: 'image2' }, { name: 'image3' }]), // 배열 객체를 넣는다.
    (req, res) => {
    	// 업로드 결과는 각각 req.files.image1, req.files.image2에 들어간다.
        console.log(req.files.image1);
        console.log(req.files.image2);
        console.log(req.files.image3);
        res.send('ok');
    }
)

upload.none()

특수한 경우에, 파일이 아님에도 멀티파트 형식으로 업로드 하는 경우가 있다.

new FormData() 로 데이터를 보낼때도 있기 때문이다.

그 경우 none 미들웨어를 사용한다.

파일은 없지만 멀티파트 형식이기 때문에 함수가 따로 존재한다.

app.post('/upload', upload.none(), (req, res) => {
    console.log(req.body.title); // 이 경우 파일을 업로드하지 않으므로 req.body만 존재한다.
    res.send('ok');
});

multer-s3

사용법

게시글 이미지를 AWS S3 에 저장하고 싶을때 사용한다.

AWS S3 버킷에 이미지 파일을 저장하고, DB 엔 그 버킷의 이미지 파일 경로(이미지 주소)를 저장하고, 서버는 이 경로를 클라이언트로 응답하는 식으로 프로세스를 구축한다.

npm i multer 
npm i multer-s3@2.10.0  # aws sdk v2 버젼을 사용하기에 multer-s3도 v2용으로
const express = require('express');
const multer = require('multer');
const path = require('path');
const AWS = require("aws-sdk");
const multerS3 = require('multer-s3');
const dotenv = require('dotenv');
dotenv.config();

const router = express.Router();

//* aws region 및 자격증명 설정
AWS.config.update({
   accessKeyId: process.env.S3_ACCESS_KEY_ID,
   secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
   region: 'ap-northeast-2',
});

//* AWS S3 multer 설정
const upload = multer({
   //* 저장공간
   // s3에 저장
   storage: multerS3({
      // 저장 위치
      s3: new AWS.S3(),
      bucket: 'test-bucket-inpa',
      acl: "public-read",
      contentType: multerS3.AUTO_CONTENT_TYPE,
      key(req, file, cb) {
         cb(null, `${Date.now()}_${path.basename(file.originalname)}`) // original 폴더안에다 파일을 저장
      },
   }),
   //* 용량 제한
   limits: { fileSize: 5 * 1024 * 1024 },
});

//* 로컬 multer 설정
const upload2 = multer(); // upload2.none() 용으로 사용

//* 싱글 이미지 파일 업로드 -> uploads/ 디렉토리에 이미지를 올린다.
router.post('/img', isLoggedIn, upload.single('img'), (req, res) => {
   console.log(req.file);
   res.json({ url: req.file.location });
});

//* 게시글 업로드
//* 프론트에서 multipart/form-data" > textarea 형식으로 넘어오기 때문에 upload2.none()형식으로 받아와 준다.
router.post('/', isLoggedIn, upload2.none(), async (req, res, next) => {
   // ...
});

module.exports = router;

위에서 설정한 upload 라는 함수가 실행되면, 설정한 이름의 Bucket 에 파일을 업로드 할 수 있게 처리된다.

key 속성은 업로드하는 파일이 어떤 이름으로 버킷에 저장되는가에 대한 속성이다.

위 속성대로라면, 버킷에 업로드 되는 파일(객체) 의 이름은 현재 시각_파일명 이 되게 된다.

클라이언트에서 이미지를 업로드하고, 서버로 페이로드에 폼데이터를 담아 보내면, 유저의 AWS S3 버킷에 업로드한 파일(객체)를 저장하게 된다.

그리고 이렇게 저장된 이미지의 메타데이터를 req.file 로 받을 수 있다.

그리고 응답으로 req.file.location 정보를 보내주면, s3 객체 url 이 전달되어 이미지를 불러올 수 있다.

주의점

upload 함수는 S3 용 multer 함수 이기에 form-data 로 넘어온 text 를 따로 처리해주어야 한다.

예를 들어 게시글을 작성하고 서버에 포스팅 api 를 보낼때, 게시글에 사용된 이미지는 S3 에 저장되게 하고, 게시글 글 내용은 DB 에 저장해 FULLTEXT INDEX 처리를 해야 하기 때문이다.

//* 로컬 multer 설정
const upload2 = multer(); // upload2.none() 용으로 사용

// ...

//* 게시글 업로드
//* 프론트에서 multipart/form-data" > textarea 형식으로 넘어오기 때문에 upload2.none()형식으로 받아와 준다.
router.post('/', isLoggedIn, upload2.none(), async (req, res, next) => {
   // ...
});

옵션

S3 폴더 별로 업로드

만일 보다 명확한 파일 관리를 위하여 따로 폴더를 만들어 그 폴더 안에서만 이미지 파일을 관리하고 싶다면?

multer s3 설정에서 파일명 자체에 경로를 줘서 설정하면 된다.

객체 ACL 설정하기

버킷에 파일을 업로드할때 다음과 같이 추가 프로퍼티로 acl을 지정하여 보낼 수 있게 설정할 수 있다.

보통 게시글의 이미지는 브라우저에 바로 검색해서 볼 수 있는 것이 일반적이니 public-read 로 설정하였다.

var upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'some-bucket',
    acl: 'public-read', // 공개
    key: function (req, file, cb) {
      cb(null, Date.now().toString())
    }
  })
})

Cache-Control 헤더 설정

cache 를 사용하겠다는 의미, max-age 는 캐시 유효시간

var upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'some-bucket',
    cacheControl: 'max-age=31536000',
    key: function (req, file, cb) {
      cb(null, Date.now().toString())
    }
  })
})

Content-Type 설정

선택적 contentType 옵션을 사용하여 파일의 Content/mime 유형을 설정할 수 있다.

기본적으로 콘텐츠 유형은 application/octet-stream 으로 설정된다.

따라서 이미지와 같은 파일을 다루기 위해서는 multer-s3 가 파일의 내용 유형을 자동으로 찾도록 multerS3.AUTO_CONTENT_TYPE 상수를 사용하여 contentType 을 지정하도록 해야 된다.

var upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'some-bucket',
    contentType: multerS3.AUTO_CONTENT_TYPE,
    key: function (req, file, cb) {
      cb(null, Date.now().toString())
    }
  })
})

multer-s3-transform 을 이용한 이미지 리사이징

고용량의 이미지를 그대로 S3 에 올리는 것은 효율적이지 않다.

따라서 고용량의 이미지를 서버에서 직접 리사이징을 통해 최적화후 S3 에 올려보자

 

npm install multer-s3-transform sharp
const multer = require("multer");
const multerS3 = require("multer-s3-transform"); // multer-s3이 아닌 multer-s3-transform을 임포트
const sharp = require("sharp");

const ImageUpload = multer({
  storage: multerS3({
    s3,
    bucket: "버켓경로",
    contentType: multerS3.AUTO_CONTENT_TYPE,
    shouldTransform: true,
    transforms: [
      {
        id: "resized",
        key: function (req, file, cb) {
          let extension = path.extname(file.originalname);
          cb(null, Date.now().toString() + extension);
        },
        transform: function (req, file, cb) {
          cb(null, sharp().resize(100, 100)); // 이미지를 100x100 으로 리사이징
        },
      },
    ],
    acl: "public-read-write",
  }),
});

const uploadImageMulterMiddleware = ImageUpload.single("file");

주의점

multer-s3 를 require 하면 안된다. multer-s3-transform 을 require 해야한다.

나의 코드

// awsMulterModules.js

const multer = require("multer");
const AWS = require("aws-sdk");
const multerS3 = require("multer-s3");
const path = require("path");
require("dotenv").config();

AWS.config.update({
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  region: "ap-northeast-2",
});

module.exports = multer({
  storage: multerS3({
    s3: new AWS.S3(),
    bucket: "mini-be-bucket",
    contentType: multerS3.AUTO_CONTENT_TYPE,
    key(req, file, cb) {
      const filename = path.basename(file.originalname).trim();
      let newFilename = "";
      for (let value of filename) {
        if (value == "" || value === "_") {
          value = "-";
        }
        newFilename += value;
      }
      cb(null, `upload/${Date.now()}-${newFilename}`);
    },
  }),
  limits: {
    fileSize: 100 * 1024 * 1024,
  },
});
// posts.js

const express = require("express");

const {
  createPost,
  findAllPosts,
  findPost,
  updatePost,
  deletePost,
} = require("../controllers/posts.controller");

const authMiddleware = require("../middleware/auth-middleware");
const upload = require("../multer/awsMulterModules");

const router = express.Router();

router.post("/", authMiddleware, upload.single("imgFile"), createPost);
router.get("/", findAllPosts);
router.get("/:postId", findPost);
router.put("/:postId", authMiddleware, updatePost);
router.delete("/:postId", authMiddleware, deletePost);

module.exports = router;
// posts.controller.js

const { Posts, Users } = require("../models");
const {
  postSchema,
  updatePostSchema,
} = require("../validations/posts-validation");

const createPost = async (req, res) => {
  try {
    let image = req.file.location;
    if (!image) {
      image = "<http://fweusdfn.html>";
    }
    console.log(req.file);
    console.log(image);
    const { title, content } = await postSchema.validateAsync(req.body);

    const { userId } = res.locals.user;

    const existUser = await Users.findOne({ where: { userId } });

    if (!existUser) {
      return res
        .status(404)
        .json({ errorMessage: "해당 유저가 존재하지 않습니다. " });
    }
    console.log(image.replace(/\\/original\\//, "/thumb/"));
    const savedPost = await Posts.create({
      userId,
      title,
      content,
      name: existUser.name,
      imgsrc: image.replace(/\\/original\\//, "/thumb/"),
    });
    return res.status(201).json({ message: "게시글을 생성하였습니다." });
  } catch (error) {
    console.log(error);
    if (error.isJoi) {
      return res.status(412).json({ errorMessage: error.message });
    }
    return res
      .status(400)
      .json({ errorMessage: "게시글 생성에 실패하였습니다. " });
  }
};

참고 링크

 

https://inpa.tistory.com/entry/EXPRESS-%F0%9F%93%9A-multer-%EB%AF%B8%EB%93%A4%EC%9B%A8%EC%96%B4

 

[EXPRESS] 📚 multer 미들웨어 사용법 💯 정리

multer 모듈 멀터는 사용 방법이 다소 어려운 미들웨어다. 이미지, 동영상 등을 비롯한 여러 가지 파일들을 멀티파트 형식으로 업로드할 때 사용하는 미들웨어이다. 멀티파트 형식이란 enctype이 mul

inpa.tistory.com

https://inpa.tistory.com/entry/AWS-SDK-%F0%9F%91%A8%F0%9F%8F%BB%E2%80%8D%F0%9F%92%BB-Multer-S3-%EC%97%B0%EB%8F%99-%EB%B0%8F-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%A0%95%EB%A6%AC

 

[AWS-SDK] 👨🏻‍💻 Node - Multer S3 연동 및 사용법 정리

multer-s3 사용법 전 시간에 Node.js 에서 AWS SDK S3를 다루는 법에 대해서 알아보았다. 그럼 만약 게시글 이미지를 S3에 저장하도록 구성하고 싶다면 어떻게 할까? 보통 노드 프로젝트에서 파일 업로드

inpa.tistory.com

https://velog.io/@shitaikoto/Node-Multer-S3-aws-sdk

 

[Node.js] Multer-S3를 이용한 이미지 업로드

AWS S3 버킷에 이미지 파일을 저장하고, DB엔 그 버킷의 이미지 파일 경로(이미지 주소)를 저장하고, 서버는 이 경로를 클라이언트로 응답합니다.

velog.io

https://velog.io/@sarifor/0006

 

[에러] This XML file does not appear to have any style information associated with it. The document tree is shown below.

문제 로그인했는데, 특정 사용자의 프로필 이미지가 보이지 않음. 해결 계정과 버킷의 퍼블릭 액세스 차단을 해제하고, S3 > 이 계정의 퍼블릭 액세스 차단 설정 > 네 항목 모두 해제 S3 >

velog.io

https://gksdudrb922.tistory.com/224

 

[AWS] S3 버킷 퍼블릭 엑세스 설정

AWS S3 버킷을 퍼블릭하게 열어두고 사용할 때가 많다. 이번에는 S3 퍼블릭 엑세스에 대한 기본 세팅을 설명한다. 퍼블릭 엑세스 차단 기본적으로 아무 설정 없이 버킷을 생성하면 모든 퍼블릭 엑

gksdudrb922.tistory.com

https://velog.io/@wngud4950/AWS-multer-s3-upload-%EC%98%A4%EB%A5%98

 

[AWS Error] multer-s3 upload 오류

서버 로그를 보다가 사진 업로드가 제대로 안되고 있다는 걸 알게 되었다.this.client.send is not a function에러가 계속 나고 있었다.원인을 찾아보니 multer-s3, aws-sdk 모듈이 호환성이 맞아야된다고 한다

velog.io

 

'node.js' 카테고리의 다른 글

passport.js [ node.js ]  (0) 2023.07.27
에러처리 미들웨어 [ express ] [ node.js ]  (0) 2023.07.22
JWT 토큰 [ node.js ] [ express ]  (0) 2023.07.16
bcrypt 사용방법 [ Javascript ] [ node.js ]  (0) 2023.07.07
PM2 사용  (0) 2023.06.24