개발이 취미인 사람

[Node.js] Express Multer 파일 업로드 본문

백앤드(Back-End)/Node.JS

[Node.js] Express Multer 파일 업로드

RyanSin 2021. 1. 17. 21:21
반응형

- 지난 시간

안녕하세요. 지난 시간에는 Express 웹 애플리케이션 프레임워크을 사용해서 RESTFaul API 서버를 만들어 보았습니다.

 

혹시 지난 글을 못 보신 분들은 아래 글을 통해 설치 및 구축을 하고 오시면 감사하겠습니다.

any-ting.tistory.com/14

 

[Node.js] Express RESTFul API 서버 만들기

- 지난 시간 안녕하세요. 지난 시간에는 Node.js 설치 및 Express 웹 애플리케이션 프레임워크를 구축하는 시간을 가졌습니다. 혹시 지난 글을 못 보신 분들은 아래 글을 통해 설치 및 구축을 하고 오

any-ting.tistory.com

 

- 개요

이번 시간에는 RESTFul API에서 파일 업로드하는 부분에 대해서 알아보겠습니다.

 

Node.js에서 파일을 업로드하는 미들웨어는 다양합니다. 하지만 이 글에서는 multer라는 미들웨어를 사용해보겠습니다.

 

- 설치

새로운 프로젝트를 만들어서 진행할 예정입니다. 아래 라이브러리(모듈)을 설치해주세요. :)

 

  • npm init : 프로젝트 생성(package.json 파일 생성)
  • npm install express : Express 웹 어플리케이션 프레임워크
  • npm install multer : 파일 업로드 미들웨어
  • npm install cors : http 통신 허용

- 기본 세팅

const express = require('express')
const cors = require("cors")
const app = express()

/**
 * Multer 미들웨어는 파일 업로드를 위해 사용되는 multipart/form-data에서 사용된다.
 * 다른 폼으로 데이터를 전송하면 적용이 안된다.
 * Header의 명시해서 보내주는게 좋다.
 */
const multer  = require('multer')

//파일을 저장할 디렉토리 설정 (현재 위치에 uploads라는 폴더가 생성되고 하위에 파일이 생성된다.)
const upload = multer({ 
    dest: __dirname+'/uploads/', // 이미지 업로드 경로
}) 

app.use(cors())// Test를 하기 위해서 세팅 "실제 서버에 배포할 때는 아이피를 설정 해야된다."

app.listen(3000, () => console.log("Multer Server Start"));

주석에도 설명을 남겨놨지만 Client에서 HTTP Header에 multipart/forom-data 라고 지정하지 않으면 안됩니다. (참고!)

 

3000번 포트 번호로 서버 시작

 

- 기능

- 단일 파일 업로드

/**
 * @author Ryan
 * @description 단일 파일 업로드
 * 
 * 클라이언트에서 file이라는 Key(fieldname) 값을 통해 파일을 전송하면 req.file 안에 파일 정보를 얻을 수 있다.
 * 
 * 단일 이미지 전송이기 때문에 여러 파일을 보내게 되면 에러가 발생된다.
 * 
 */
app.post('/single/upload', upload.single('file'), (req, res, next) => {

  const { fieldname, originalname, encoding, mimetype, destination, filename, path, size } = req.file
  const { name } = req.body;

  console.log("body 데이터 : ", name);
  console.log("폼에 정의된 필드명 : ", fieldname);
  console.log("사용자가 업로드한 파일 명 : ", originalname);
  console.log("파일의 엔코딩 타입 : ", encoding);
  console.log("파일의 Mime 타입 : ", mimetype);
  console.log("파일이 저장된 폴더 : ", destination);
  console.log("destinatin에 저장된 파일 명 : ", filename);
  console.log("업로드된 파일의 전체 경로 ", path);
  console.log("파일의 바이트(byte 사이즈)", size);

  res.json({ok: true, data: "Single Upload Ok"})

})

 

 

- 멀티 파일 업로드

/**
 * @author Ryan
 * @description 여러 파일 업로드
 * 
 * 클라이언트에서 file이라는 Key(fieldname) 값을 통해 파일을 전송하면 req.files 안에 파일 정보를 배열([]) 형태로 얻을 수 있다.
 * 
 * array('fieldname', maxCount) 필드 이름과 최대 파일 수를 정합니다.
 * 지정된 수 보다 더 많은 파일을 업로드하면 에러가 발생합니다.
 */
app.post('/multipart/upload', upload.array('file'), (req, res, next) => {

  const { name } = req.body;
  console.log("body 데이터 : ", name);

  //배열 형태이기 때문에 반복문을 통해 파일 정보를 알아낸다.
  req.files.map(data => {
    console.log("폼에 정의된 필드명 : ", data.fieldname);
    console.log("사용자가 업로드한 파일 명 : ", data.originalname);
    console.log("파일의 엔코딩 타입 : ", data.encoding);
    console.log("파일의 Mime 타입 : ", data.mimetype);
    console.log("파일이 저장된 폴더 : ", data.destination);
    console.log("destinatin에 저장된 파일 명 : ", data.filename);
    console.log("업로드된 파일의 전체 경로 ", data.path);
    console.log("파일의 바이트(byte 사이즈)", data.size);
  })

  res.json({ok: true, data: "Multipart Upload Ok"})

})

 

- 단일 & 멀티 파일 업로드

/**
 * @author Ryan
 * @description 단일 및 여러 파일 업로드 
 * 
 * fields를 설정해서 특정 파일은 단일 또는 여러 파일 그리고 특정 파일을 나눠서 업로드가 가능하다.
 * 
 * Ex) 클라이언트가 요청할 때 pdf 파일은 한개를 받고 이미지 파일은 여러개를 받는 상황
 *     이런식으로 정의해서 사용할 수 있다.
 */
const fileFields = upload.fields([
    { name: 'file1', maxCount: 1 },
    { name: 'file2', maxCount: 8 },
]);

app.post('/fields/upload', fileFields, (req, res, next) => {


    const { file1, file2 } = req.files;
    const { name } = req.body;


    console.log("body 데이터 : ", name);

    //배열 형태이기 때문에 반복문을 통해 파일 정보를 알아낸다.
    file1.map(data => {
        console.log("file1");
        console.log("     ");
        console.log("폼에 정의된 필드명 : ", data.fieldname);
        console.log("사용자가 업로드한 파일 명 : ", data.originalname);
        console.log("파일의 엔코딩 타입 : ", data.encoding);
        console.log("파일의 Mime 타입 : ", data.mimetype);
        console.log("파일이 저장된 폴더 : ", data.destination);
        console.log("destinatin에 저장된 파일 명 : ", data.filename);
        console.log("업로드된 파일의 전체 경로 ", data.path);
        console.log("파일의 바이트(byte 사이즈)", data.size);
    })
    
    console.log("     ");
    console.log("-----------------------------------------------");
    console.log("     ");
    
    //배열 형태이기 때문에 반복문을 통해 파일 정보를 알아낸다.
    file2.map(data => {
        console.log("file2");
        console.log("     ");
        console.log("폼에 정의된 필드명 : ", data.fieldname);
        console.log("사용자가 업로드한 파일 명 : ", data.originalname);
        console.log("파일의 엔코딩 타입 : ", data.encoding);
        console.log("파일의 Mime 타입 : ", data.mimetype);
        console.log("파일이 저장된 폴더 : ", data.destination);
        console.log("destinatin에 저장된 파일 명 : ", data.filename);
        console.log("업로드된 파일의 전체 경로 ", data.path);
        console.log("파일의 바이트(byte 사이즈)", data.size);
    })

    res.json({ok: true, data: "Fields Upload Ok"})

})

fields를 두 개 설정했기 때문에 구조분해를 통해 file1과 file2를 가져올 수 있습니다.

 

모든 업로드 되는 파일들은 현재 위치에 uploads라는 폴더 하위에 저장이 됩니다. (파일 이름은 임시 저장)

uploads 폴더 하위 이미지 저장

"파일 이름이 임시저장 하는 부분이 싫으시면 따로 설정 할 수 있습니다."

console.log
body 데이터 : Ryan
폼에 정의된 필드명 : file
사용자가 업로드한 파일 명 : Ryan.png
파일의 엔코딩 타입 : 7bit
파일의 Mime 타입 : image/png
파일이 저장된 폴더 : /Users/sinhangug/ryan/server/multer/uploads/
destinatin에 저장된 파일 명 : e4c62ee4640773dd875ada8f16854ab8
업로드된 파일의 전체 경로 /Users/sinhangug/ryan/server/multer/uploads/e4c62ee4640773dd875ada8f16854ab8
파일의 바이트(byte 사이즈) 21111

 

로그에서 데이터를 확인 할 수 있습니다.

 

만약 임시 이름이 싫거나 특정 폴더를 지정하고 싶으면 아래 코드를 활용해서 옵션을 설정 할 수 있습니다.

// 파일 경로 및 이름 설정 옵션
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, '/tmp/my-uploads') // 파일 업로드 경로
  },
  filename: function (req, file, cb) {
    cb(null, file.fieldname + '-' + Date.now()) //파일 이름 설정
  }
})

const upload = multer({ storage: storage })
  • destination 옵션은 어느 폴더안에 파일을 저장할 지 결정합니다. (이 옵션을 주지 않으면, 운영체제 시스템 임시 파일을 저장하는 기본 디렉토리를 사용합니다.)
  • filename 옵션은 폴더안에 저장되는 파일 이름을 결정합니다. (디렉토리를 생성하지 않으면 에러가 발생!! )

Storage 옵션

multer에는 MemoryStorage 방식과 DiskStorage 방식 두 가지가 있습니다.

 

보통 AWS S3와 같은 외부 파일 스토리즈 서버에 이미지를 업로드를 할 때는 MemoryStorage를 사용합니다.

 

RAM 안에 이미지 Butter라는 값을 통해 값을 확인 할 수 있습니다.

 

하지만 해당 운영체제 RAM 용량이 적으면 데이터 손실 또는 에러가 발생할 수 있습니다.

 

그렇기 때문에 DiskStorage에 파일을 쓰고 삭제하는 방식을 추천드립니다.

 

DiskStorage 방식은 말 그대로 "Disk에 파일을 저장하겠다" 라고 생각하시면 됩니다. 그렇게 때문에 uploads라는 폴더 아래에 파일이 저장됩니다.

 

Limites & Filter 옵션

클라이언트가 아무 이미지나 올리면 안되겠죠? 이럴 때 사용할 수 있는 옵션입니다.

 

//multer 미들웨어 파일 제한 값 (Doc 공격으로부터 서버를 보호하는데 도움이 된다.)
const limits = {
    fieldNameSize: 200, // 필드명 사이즈 최대값 (기본값 100bytes)
    filedSize: 1024 * 1024, // 필드 사이즈 값 설정 (기본값 1MB)
    fields: 2, // 파일 형식이 아닌 필드의 최대 개수 (기본 값 무제한)
    fileSize : 16777216, //multipart 형식 폼에서 최대 파일 사이즈(bytes) "16MB 설정" (기본 값 무제한)
    files : 10, //multipart 형식 폼에서 파일 필드 최대 개수 (기본 값 무제한)
}

/**
 * @author Ryan
 * @description 파일 업로드시 파일 체크 함수
 * 
 * @param {Object} file 파일 정보
 * 
 * {
 *     fieldname: 'file',
 *     originalname: '001.png',
 *     encoding: '7bit',
 *     mimetype: 'image/png'
 * }
 * 
 * @param {Function} callback 파일 업르도 허용 및 거부 처리
 *               
 *               허용: callback(null, true);
 *               거부: callback(null, false);
 */
const fileFilter = (req, file, callback) =>{

    const typeArray = file.mimetype.split('/');

    const fileType = typeArray[1]; // 이미지 확장자 추출
    
    //이미지 확장자 구분 검사
    if(fileType == 'jpg' || fileType == 'jpeg' || fileType == 'png'){
        callback(null, true)
    }else {
        return callback({message: "*.jpg, *.jpeg, *.png 파일만 업로드가 가능합니다."}, false)
    }
}

//파일을 저장할 디렉토리 설정 (현재 위치에 uploads라는 폴더가 생성되고 하위에 파일이 생성된다.)
const upload = multer({ 
    dest: __dirname+'/uploads/', // 이미지 업로드 경로
    limits: limits, // 이미지 업로드 제한 설정
    fileFilter : fileFilter // 이미지 업로드 필터링 설정
}) 

 

기존 multer 코드 안에 limits와 fileFilter 항목에 옵션을 설정할 수 있습니다.

 

 

만약 에러가 발생한다면 multer 미들웨어는 Express 프레임워크에 위임 합니다.

 

Express에 Error Handler를 설정해서 에러를 캐치하면 됩니다.

//Error Handler
app.use((err, req, res, next) => {

    res.json({ok: false, data: err.message})

})

app.listen(3000, () => console.log("Multer Server Start"));

 

위 코드는 가장 아래에 설정해야 됩니다.

 

app.use는 모든 요청에 대해서 캐치가 가능하기 때문에 우리는 에러가 발생했을 때만 해당 라우터가 실행하기 때문입니다.

 

소스  저장소

GitHub : github.com/Ryan-Sin/Node_Express_Multer

 

Ryan-Sin/Node_Express_Multer

Contribute to Ryan-Sin/Node_Express_Multer development by creating an account on GitHub.

github.com

- 참고 사이트

GitHub : github.com/expressjs/multer/blob/master/doc/README-ko.md

 

expressjs/multer

Node.js middleware for handling `multipart/form-data`. - expressjs/multer

github.com