게시글

5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭
강좌17 - NodeJS - 7년 전 등록 / 5년 전 수정

multer를 사용해 이미지 업로드하기

폼데이터 처리

안녕하세요. 이번 시간에는 multer를 사용해 이미지를 업로드 및 폼데이터를 처리하는 것을 알아보겠습니다.

보통 JSON 형식으로 된 데이터는 AJAX로든 폼 태그로든 쉽게 업로드할 수 있습니다. 하지만 이미지 파일만큼은 사람들의 골치를 썩이는데요. express와 함께 사용하면 쉽게 이미지 업로드를 도와주는 multer 모듈이 있습니다.

npm i multer

이제 설치된 multer를 사용해 코딩을 해봅시다. 익스프레스 라우터 부분에 다음을 추가해줍니다.

const multer = require('multer');
// 기타 express 코드
const upload = multer({ dest: 'uploads/', limits: { fileSize: 5 * 1024 * 1024 } });
app.post('/up', upload.single('img'), (req, res) => {
  console.log(req.file); 
});

이제 폼데이터나 폼 태그를 통해 업로드한 이미지를 올리면 req.file로 정보가 들어오고, dest 속성에 지정해둔 경로에 이미지가 저장됩니다. limits 속성은 선택 사항인데 여러 가지 제한을 걸 수 있습니다. 위에서는 파일 사이즈를 5MB로 제한했습니다. 폼데이터로 업로드하는 강좌는 여기를 참고하세요. upload.single('img') 미들웨어를 라우터 콜백함수 전에 끼워넣었는데요. 폼데이터의 속성명이 img이거나 폼 태그 인풋의 name이 img인 파일 하나를 받겠다는 뜻입니다. 이미지가 아닌 나머지 데이터는 그대로 req.body에 들어옵니다.

만약 이미지를 하나가 아닌 여러 개를 받고 싶다 하면 upload.array('키', 최대파일개수) 하면 됩니다. req.file 대신 req.files에 정보가 담깁니다.

app.post('/up', upload.array('img'), (req, res) => {
  console.log(req.files);
});

만약 여러 개의 키로 이미지를 올렸다면 upload.fields를 써야 합니다(이외에 upload.none도 있습니다). 사용한 키들을 배열 안에 넣어주면 됩니다.

app.post('/up', upload.fields([{ name: 'img' }, { name: 'photos' }]), (req, res) => {
  console.log(req.files);
});

문제는 uploads 폴더에 뭔가 생성이 되긴 하는데 이름도 ce243370b74107493fea0743d249a176처럼 이상하게 바뀌어있고 확장자도 붙어 있지 않아 쓸 수가 없습니다. 사실 이것은 보안상 의도된 것이지만 지금은 불편하기 때문에 이름이 원래대로 나오게 해보겠습니다. dest 속성 대신 storage 속성을 사용해 upload 변수에 넣어주면 됩니다.

const upload = multer({
  storage: multer.diskStorage({
    destination: function (req, file, cb) {
      cb(null, 'uploads/');
    },
    filename: function (req, file, cb) {
      cb(null, file.originalname);
    }
  }),
});

네. 조금 복잡해졌긴 하지만 위의 코드를 보시면 저장될 경로(destination)과 파일명(filename)을 조작하고 있음을 알 수 있습니다. 이제 다시 파일을 올리면 원본 파일명 그대로 올라갑니다. 그런데 파일명이 중복되는 경우 문제가 생길 수 있죠. 파일명을 타임스탬프로 해서 중복되지 않게 해봅시다.

const path = require('path');
const upload = multer({
  storage: multer.diskStorage({
    destination: function (req, file, cb) {
      cb(null, 'uploads/');
    },
    filename: function (req, file, cb) {
      cb(null, new Date().valueOf() + path.extname(file.originalname));
    }
  }),
});

위와 같이 하시면 타임스탬프.확장자 형식으로 파일명이 지정됩니다.

지금까지는 실제 디스크에 파일을 업로드했는데요. S3같은 곳에 업로드하시는 분들도 있을 겁니다. S3에 업로드하는 방식은 크게 두 가지가 있는데요. 디스크에 있는 파일을 업로드하거나, 파일 버퍼(메모리에 저장)를 업로드하는 겁니다. 파일을 S3에 업로드한 후에는 남아있는 파일을 지워줘야 하는데 이게 번거롭죠. 그래서 처음부터 메모리에 파일을 버퍼 형식으로 저장하고, 그것을 업로드하는 겁니다.

const upload = multer({
  storage: multer.memoryStorage(),
});

메모리스토리지를 쓸 경우는 req.file이나 req.files 안의 파일 데이터(객체)에, 디스크스토리지 전용 속성인 destination, filename, path 대신 buffer라는 속성이 새로 생기고 그 값으로 버퍼들이 저장됩니다. 이 버퍼를 사용해서 S3에 버퍼로 업로드하시면 됩니다. 이 방식의 단점은 파일이 여러 개고, 용량이 너무 크면 메모리를 초과해서 서버가 멈춰버릴 수도 있습니다. 보통은 그럴 일은 없겠지만 그럴 수도 있다는 것을 알아두세요. 이러한 문제가 걱정되고, 버퍼를 다루는 게 어렵다면 multer-s3 패키지를 고려해보세요.

조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.
5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭

댓글

24개의 댓글이 있습니다.
8달 전
프로젝트, 파일구조좀 보여주세요...
3년 전
안녕하세요 ^^ filefilter를 사용해서 .. 특정 확장자만 업로드할수있도록 코드를 썼는데, filefilter가 안 먹네요. ㅠㅠ 특정 확장자 말고 다른 확장자도 올라갑니다 .. 다른 확장자파일을 업로드시 알림팝업을 띄우고 싶은데, 이 경우에 어떻게 하는게 좋을까요 ?
5년 전
혹시 filename: function (req, file, cb) { cb(null, file.originalname); }
이부분에서 파일이름을 그대로 받아오는 것이 아닌
파일이름을 항상 고정시켜서 매번 덮어서 저장할 수있게(ex. 어떤 그림파일이던지 2.jpg 로 받아옴) 하는 방법은 없을까요?
4년 전
file.originalname 부분을 고정된 값으로 변경하시면 됩니다.
5년 전
이 모듈로 이미지 캐시 서버를 제작해도 효과를 볼 수 있을까요?
캐시서버로 사용하는 다른 클라우드 서비스는 너무 비싸네요
기존 도메인의 서브도메인에 연결해 놓고 쓰고싶은데 말이죠
5년 전
cloud front같은 서비스가 비싸다는 말씀이신가요? 직접 구현하는 것이 cloud front 사용하는 것보다 효과가 있을지는 잘 모르겠습니다.
5년 전
정말 감사합니다ㅠㅠㅠㅠㅠㅠㅠ 이틀동안 삽질하다 깔끔한 설명에 바로 해결했습니다!! 작은 감사의 표현으로 광고 눌러드렸습니다
5년 전
댓글중 저한테 도움되는내용이 있어서 정말 감사합니다.
(request.body에 값이 없었는데 file을 가장 나중에 추가해야 들어온다는것)
import AWS from 'aws-sdk';
AWS.S3.putObject 으로 제가 지정한 위치에 파일이 잘 들어간건 확인이 되고있습니다.
문제는 putObject 가 비동기로 처리되서 뷰에서는 파일업로드가 성공이든 실패든 실패로 인식하고 있다는게 문제입니다. 동기처리 방식으로
async
await AWS.S3.putObject 해보았지만 현상은 같습니다. 도와주세요 ㅠㅠ
5년 전
timestamp 형식으로 로컬과 파일으름은 디비에 저장했는데, 삭제할 경우는 어떻게 처리하나요?? 디비이름 가져와서 타임스템프찍힌걸 Decode해서 처리해야하나여?
5년 전
파일이름을 그대로 재구성해서 fs 모듈로 삭제하시면 될 것 같습니다.
5년 전
안녕하세요 좋아요 기능을 구현하고 나서 로그인 후에 해시태그 검색이 안됩니다ㅠㅠ main.pug 파일의 -const like = user && twit && twit.Liker.map(l => l.id).includes(user.id); 문장에서 else if user && like Cannot read property 'map' of undefined 이렇게 에러가 뜨는데 혹시 원인을 알 수 있을까요?
5년 전
user && twit && twit.Liker && twit.Liker.map으로 중간에 하나를 더 추가해주세요.
5년 전
안녕하세요~ 인프런 강의 보고 찾아왔습니다. 이미지를 업로드하면 ajax로 /img로 이동하고 multer로 이미지 데이터를 받은 후 res.json으로 filename을 전달하는 방식이잖아요. req.body에서 받으시던데 저는 값이 전달되지 않는듯 합니다. req를 콘솔에 찍어봐도 나오질 않습니다.
5년 전
append할 때의 key(예를 들어 formData.append('img'))와 upload.single('img')에서의 img가 일치해야 합니다. 일치하나요?
5년 전
데이터 자체는 받아와지는데 res.json으로 url이 전달이 되지 않아서요..!
5년 전
console.log(req.file) 했을 때 데이터 확인해보셨나요?? 그리고 s3에 올리는게 아니라 diskStorage 사용하시는 거죠?
5년 전
네. fieldname, originalname, encoding, mimetype, destination, filename, path, size 담겨있습니다. uploads 폴더에 저장되구요!
5년 전
그러면 res.json({ url: req.file.filename }) 이런 식으로 프론트에 주소가 전달될텐데요??
5년 전
multer-s3에 대해 질문드립니다. 저걸 사용하면 AWS EC3에 파일이 저장되는데, 특정 유저가 올렸다는것을 남기기 위해선 어디서 어떤 설정을 해야 하나요?
5년 전
db에 따로 기록하셔야합니다.
6년 전
제가 잘 안되는게 있어서 그러는데 혹시 괜찮으시다면 도움을 좀 받을 수 있을까요?안되는 이유라도 알고 싶은데 그걸 모르겠어서....
6년 전
아 그러네요...감사합니다!
6년 전
그러면 제가 만약 photoF라는 폴더에 사진을 저장하고 싶을경우에는
fs.writeFile(new Date().toLocaleString()+'./photoF/photo.jpg',base64Decode,function(err)
이렇게 하면 되나요??ㅜ
6년 전
new Date().toLocaleString()은 왜 자꾸 붙이시는거죠? 파일 이름에 붙이셔야 하지 않을까요?
6년 전
파일의 경로라는게 그 파일이 저장될 위치를 말하시는건가요?
6년 전
6년 전
오!! 감사합니다.그리고 예를 들어
base64Decode = new Buffer.from(urldoce,'base64');후에
fs.writeFile(new date().toLocaleString(),base64Decode,function(err) 이런식으로 하면 폴더에 jpg파일로 저장 되나요?ㅠㅠ 제가 입문자라 숟가락을 떠주셔서 먹질 못해서..
6년 전
writeFile의 첫 번째 인자는 파일경로니까 파일 경로를 제대로 지정해주셔야겠죠. jpg 확장자도 붙이고요.
6년 전
궁금한게 있는데요 저는 이미지를 base64->url로 바꿔서 받은 후 노드 서버에서 url->base64로 디코딩을 해줬는데요 그렇게 해서 비트맵으로 바꿔서 사용할려고 하는데 안드로이드에서 보낸것처럼 node에서 bitmapFactory같으게 사용할 수 있나요? 없으면 어떻게 다시 bitmap으로 바꿀지가 막막해서.ㅠ
6년 전
base64문자열을 Buffer.from(문자열, 'base64') 하시면 바이너리로 변경됩니다. 그걸로 fs.writeFile 하시거나 하시면 됩니다.
6년 전
multer를 사용하면 form-data를 req.body에 예쁘게 (body-parser처럼) 넣을수 있는데 문제는 제가 값체크를 먼저하고 (권한체크등) 나중에 업로드를 하고싶습니다. 그럴려면 bodyparser가 동작해야하는데 문제는 form-data에 경우에는 먹히질 않네요 어떻게 할수있을까요?...
6년 전
fileFilter라는 옵션이 있습니다.
6년 전
파일필터 옵션은 제가 보기에 파일의 값을 체크하는것 같은데 제가 말한 값 체크는 예를들어서 name값이 10자를 넘는지, content가 10자 이상인지등의 값 체크입니다 보통은 그냥 body.name... 으로 호출해서 쓰면 되는데 form-data 는 body-parser가 안먹어서요... 혹시 멀티파트 데이터를 parser하는 기능을 아시나요?... 한마디로 파일업로드전에 body 값을 체크하고싶습니다
6년 전
fileFilter에서 req에 접근할 수 있는데 req.body로 접근이 안 되시나요? formData에 키를 추가하는 순서도 중요합니다(이미지를 제일 마지막에 추가해야 합니다)
6년 전
책의 예제를 그대로 적었는데
POST /profile/upload 413 11.326 ms - 2641
PayloadTooLargeError: request entity too large 라는 에러가 발생했습니다.
구글링을 했고 여러시도를 해봤습니다. limits: { fileSize: 5 * 1024 * 1024 }을 limits: {fileSize: 100000000}로 바꾸거나app.use(bodyParser.json({limit:'50mb'}));
app.use(bodyParser.urlencoded ({extended: true,limit:'50mb',parameterLimit:50000 }));을 app.js에 추가해보았습니다
postman의 form-data에 400kb짜리 사진을 업로드 해봤지만.... POST /profile/upload 413 13.445 ms - 2641
PayloadTooLargeError: request entity too large 여전히 이렇네요
6년 전
에러 메시지만 보면 용량제한에 걸린 것 맞는데요. 포스트맨 말고 그냥 웹으로 한 번 보내보시겠어요?
6년 전
대댓글로 썼는데 댓글이 날라갔네요ㅠ

기존에 \u003cform>태그로 넘기면 uplaod.single()에 file input의 id를 넘기잖아요,
근데 formdata로 넘기면 여기에 넣을게 없는데 이걸 어떻게 해결해야 하나요?

지금 클라이어늩에서 서버로 req.files.image에 버퍼 이런게 넘어오기만하고

이 넘어온 데이터를 가지고 실제로 uploads/ 폴더에 올리는 방법을 모르겠습니다ㅠㅠ
6년 전
formData.append('image', 이미지데이터)하시면 upload.single('image')를 통해 req.file.image가 되는 식입니다. 이렇게 하면 uploads 폴더에도 올라갑니다.
6년 전
https://gist.github.com/YanghaKoo/0743a6d70f4027a3cd7ceffde6768863

말씀하신대로 해봤는데 req.file이 계속 undefined이고, 업로드가 되지 않습니다..ㅠ
혹시 괜찮으시다면 한번만 봐주실수 있나요..?
6년 전
안녕하세요 앞 강의인 formData로 해서 파일을 넘겼습니다

req.files.image = {
name: '이미지.jpg',
data: \u003cBuffer ff d8 ff e1 28 1b 45 78 69 66 00 00 49 49 2a 00 08 00 00 00 0c 00 0e 01 02 00 20 00 00 00 9e 00 00 00 0f 01 02 00 05 00
00 00 be 00 00 00 10 01 02 00 ... >,
encoding: '7bit',
truncated: false,
mimetype: 'image/jpeg',
md5: [Function: md5],
mv: [Function: mv]
}


이걸 multer로 업로드 하려면 어떻게해야하나요??
multer쪽의 코드는 제로초님 책을 보고 작성했습니다
6년 전
req.files.image라는 건 이미 멀터로 업로드한 결과물 아닌가요?
6년 전
폼데이터를 업로드하는 방법의 링크가 여기 링크와 똑같이 나와요!
6년 전
수정했습니다 감사합니다
7년 전
게시판과 같은 것을 만들 때는 파일을 업로드하지 않고, 어떤 방법을 쓰나요? 데이터베이스를 쓰나요, 아니면 그냥 파일을 쓰나요?
7년 전
디비보다는 파일을 씁니다. 하지만 파일을 서버에 저장하기 보다는 cdn에 저장하곤 합니다.
7년 전
안녕하세요^^ 혹시 파일업로드 관련해서 혹시 도움 좀 주실 수 있을까요?
7년 전
multer 사용하시나요?
7년 전
그건 아닌데 폼 방식이 multipart/form-data 여서요~
7년 전
어떤 부분이 궁금하신가요??
7년 전
내용이 조금 있어서 혹시 메신저나 이메일 주소 등의 연락방법이 있을가요^^?
7년 전
옆의 페북 메신저 사용하시거나 알림 메일가는 주소로 연락주시면 됩니다.
7년 전
안녕하세요. 글 잘 읽어봤는데요. 한가지 궁금한 점이 있는데 이 게시글에서는 multer 모듈을 사용해서 이미지를 파일로 저장하셨는데 찾아보니까 몽고 디비를 이용해서 저장을 하는것도 있더라구요. (https://medium.com/@alvenw/how-to-store-images-to-mongodb-with-node-js-fb3905c37e6d) 속도나 효율 등의 면에서 보통 어떤 방식이 주로 사용되는지 알 수 있을까요? 그리고 'S3같은 곳에 업로드하시는 분들도 있을 겁니다' 에서 S3가 뭔가요?
7년 전
S3는 아마존의 파일 스토리지입니다. 파일로 저장하는 것이 몽고디비 gridfs 등을 사용하는 것보다 편리합니다.