Node.js Multer - 사용자의 파일 업로드를 처리하고 서버에 저장하기

 

Multer

  • Express에서 파일 업로드를 처리하기 위한 미들웨어

설치

터미널에서 아래의 명령어를 입력하여 Multer를 설치합니다.

npm install multer

 
또한 Express 내에서 기능을 수행하기 하며, 경로 지정을 위해 아래의 모듈을 추가로 설치합니다.

npm install express
npm install path

 
이 포스팅에서는 multer의 사용 방법을 설명하기 위한 것이므로 자세한 내용은 생략합니다.
각각 모듈의 사용법은 아래의 포스팅에서 자세히 확인 가능합니다.


기본 사용 예제

  • 본 사용 예제는 multer 공식 가이드 문서에서 제공하는 샘플 코드입니다.
const express = require('express')
const multer  = require('multer')
const path = require('path');
const upload = multer({ dest: 'uploads/' })

const app = express()

app.post('/profile', upload.single('avatar'), function (req, res, next) {
  // req.file 은 `avatar` 라는 필드의 파일 정보입니다.
  // 텍스트 필드가 있는 경우, req.body가 이를 포함할 것입니다.
})

app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) {
  // req.files 는 `photos` 라는 파일정보를 배열로 가지고 있습니다.
  // 텍스트 필드가 있는 경우, req.body가 이를 포함할 것입니다.
})

const cpUpload = upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }])
app.post('/cool-profile', cpUpload, function (req, res, next) {
  // req.files는 (String -> Array) 형태의 객체 입니다.
  // 필드명은 객체의 key에, 파일 정보는 배열로 value에 저장됩니다.
  //
  // e.g.
  //  req.files['avatar'][0] -> File
  //  req.files['gallery'] -> Array
  //
  // 텍스트 필드가 있는 경우, req.body가 이를 포함할 것입니다.
})

multer 파일 업로드 구현

파일 업로드 기본 구문 코드

const express = require('express');
const multer = require('multer');
const app = express();
const upload = multer({ dest: 'uploads/' }); // 이미지를 저장할 폴더 설정

// 이미지 업로드 처리
app.post('/upload', upload.single('image'), (req, res) => {
  // req.file 는 `image` 라는 필드의 파일 정보입니다.
  // 텍스트 필드가 있는 경우, req.body가 이를 포함할 것입니다.
  if (!req.file) {
    return res.status(400).json({ error: '이미지를 업로드해주세요.' });
  }
  
  const imagePath = req.file.path;
  // 여기서 imagePath를 이용해 이미지를 저장하거나 다른 작업을 수행할 수 있습니다.
  return res.json({ success: true, imagePath: imagePath });
});

// 서버 실행
const port = 3000;
app.listen(port, () => {
  console.log(`서버가 http://localhost:${port} 에서 실행 중입니다.`);
});
  • multer({ dest: '파일경로' })
    • 사용자가 파일을 업로드하면 정해진 경로(포스팅에서는 upload 폴더)에 파일을 저장합니다.
  • upload.single('필드명')
    • 사용자가 요청(request) 시 필드에 설정한 데이터를 서버에 전달합니다.
    • req.file은 '필드명' 이라는 파일 정보를 의미합니다.
    • 포스팅에서는 single을 기준으로 작성되며 이 옵션은 단일 파일을 업로드하는데 사용하는 것입니다.
      (다중 파일은 array 이용)
  • req.file.path
    • 업로드된 파일의 전체 경로를 반환합니다.
    • 파일의 정보를 담는 속성은 아래와 같습니다.
KeyDescription
fieldname폼에 정의된 필드 명
originalname사용자가 업로드한 파일 명
encoding파일의 엔코딩 타입
mimetype파일의 Mime 타입
size파일의 바이트(byte) 사이즈
destination파일이 저장된 폴더
filenamedesctination 에 저장된 파일 명
path업로드된 파일의 전체 경로
buffer전체 파일의 Buffer

 

  • 업로드 테스트
    • 경로 : http://localhost:3000/upload
    • HTTP Method : POST
    • Body : { 'image' : '파일명'}
    • 포스팅에서는 POSTMAN을 이용하여 API 테스트를 진행하였습니다.

업로드 성공!

 

  • 업로드된 파일 확인

 
여기서 한가지 문제가 있습니다.
파일을 받긴하였으나 파일의 확장자를 설정하지 않아 어떤 파일인지 알 수가 없다는 것이죠.
우선 정상적으로 파일이 전달이 되었는지 수동으로 이미지 파일로 확장자를 변환하여 확인해봅시다.

  • 전달한 파일 : 쵸파.jpg
  • 수동으로 확장자 .jpg 를 붙여서 해당 파일 확인

 
이 형식의 장점과 단점이 있습니다.
장점은 모든 파일을 서버에서 전달받아 처리할 수 있다는 것이고,
단점은 파일의 확장자를 알 수가 없어서 어떤 형식의 파일인지 확인이 어렵다는 것입니다.
이제 파일의 확장자를 지정하여 특정 확장자의 파일만 받도록 해봅시다.


업로드 파일 더 자세히 제어하기

업로드 파일을 더 제어하고 싶을 때 dest 옵션 대신 사용 하는 옵션으로 storage 를 사용합니다.
포스팅에서는 DiskStorage 엔진을 사용하여 업로드 파일의 확장자와 파일명을 지정해봅니다.

  • storage
    • 업로드 파일을 더 자세히 제어하기 위함
    • 스토리지 엔진 DiskStorage와 MemoryStorage를 탑재하여 써드파티로부터 더 많은 엔진을 사용가능케 함
  • DiskStorage
    • 파일을 디스크에 저장하기 위한 모든 제어 기능을 제공하는 디스크 스토리지 엔진
    • destination 과 filename 두 가지 옵션으로 파일을 어디에 저장할 지를 정하는 함수
    • destination : 어느 폴더 안에 업로드 한 파일을 저장할 지를 결정
    • filename : 폴더 안에 저장되는 파일 명을 결정
// 기존 코드
// const upload = multer({ dest: 'uploads/' })

// 업로드 파일을 더 자세히 제어하기 위한 옵션 stroage
const upload = multer( storage: multer.diskStorage({내용작성}));

 
storage 의 내용을 더 구체적으로 작성해봅니다.

  • 조건
    • 업로드한 파일의 확장자는 그대로 유지
    • 업로드한 시점의 시간을 기준으로 파일 저장
  • upload 의 저장 경로를 diskStorage를 이용하여 재설정해봅시다.
// 이미지 파일을 저장할 디렉토리와 파일 이름 설정
const storage = multer.diskStorage({
    // 저장할 파일 경로
    destination: (req, file, cb) => {
        cb(null, './uploads/'); // 이미지 파일 저장 경로 설정
    },
    // 저장할 파일 이름
    filename: (req, file, cb) => {
        /* 현재시간 구하기 yyyy-MM-dd-Thh-mm-ss-fff */
        const today = new Date(); // 현재시간
        const year = today.getFullYear(); // 년 (yyyy)
        const month = today.getMonth() + 1;  // 월 (MM)
        const date = today.getDate();  // 일 (dd)
        const hours = today.getHours(); // 시 (hh)
        const minutes = today.getMinutes();  // 분 (mm)
        const seconds = today.getSeconds();  // 초 (ss)
        const milliseconds = today.getMilliseconds(); // 밀리초 (fff)

        // 현재시간으로 파일 이름 설정
        const now = year+"-"+month+"-"+date+"-T"+hours+"-"+minutes+"-"+seconds+"-"+milliseconds;
        const extension = path.extname(file.originalname); // 파일 확장자
        const filename = `${now}${extension}`; // 파일 이름 생성
        cb(null, filename); // 파일 이름 설정
    }
});

const upload = multer({ storage: storage }); // 이미지를 저장할 폴더 설정

 

  • 업로드 테스트
    • 파일 업로드 기본 구문 코드에 명시된 조건 그대로 진행
    • 이미지 파일과 html 파일 등 여러가지 파일 업로드 테스트

 
1. 이미지 업로드 테스트

 
2. html 문서 파일 업로드 테스트

  • key 값은 혼동의 우려가 있어서 따로 바꾸지 않고 그대로 'image' 상태로 두었습니다.
  • 실제로는 상황에 맞는 키 값으로 변경해서 사용하세요!

 

  • 업로드된 파일 확인
    • 테스트한 파일 이미지 파일(png)과 HTML 문서파일이 정상적으로 업로드 되었다!

파일 업로드 시 텍스트와 함께 전달하기

이번에는 파일 업로드 시, 파일과 텍스트 내용을 함께 전달하여 봅시다.
대부분의 게시판은 파일을 첨부하여 글도 작성하겠죠?
그러한 기능을 할 수 있도록 변경해봅시다.
 
POST 메서드를 수행하는 함수 내에 아래의 코드를 추가해줍니다.

req.body.키값
  • 포스팅에서의 POST 메서드 수행 경로 : http://localhost:3000/upload
  • 전달 받을 텍스트 데이터 : req.body.필드명
    (텍스트 데이터가 존재할 경우, req.body에 내용이 포함됩니다.)
  • 전달 받을 파일 : 이미지 (req.file)
// 이미지 업로드 처리
// upload.single('a') : req.file은 a라는 필드의 파일 정보
// upload.field([{'a':'A'},{'b':'B'}]) : req.files는 String -> Array 형태의 객체
app.post('/upload', upload.single('image'), (req, res) => {
  // req.files 는 `image` 라는 파일정보를 배열로 가지고 있습니다.
  // 텍스트 필드가 있는 경우, req.body가 이를 포함할 것입니다.
  if (!req.file) {
    return res.status(400).json({ error: '이미지를 업로드해주세요.' });
  }
  // 텍스트 정보를 저장
  const textData = req.body.name; // 필드명 : name
  const textData1 = req.body.content; // 필드명 : content
  // 이미지 정보를 저장
  const imageData = req.file;

  console.log('Text Data: name => ', textData);
  console.log('Text Data: content => ', textData1);
  console.log('Image Data:', imageData);
  
  const imagePath = req.file.path;
  // 여기서 imagePath를 이용해 이미지를 저장하거나 다른 작업을 수행할 수 있습니다.
  return res.json({ success: true, imagePath: imagePath });
});

이미지 파일과 함께 name, content 키를 사용하여 서버로 전달 받은 데이터를 확인합니다.

  • 텍스트 데이터
    • 텍스트 데이터는 req.body에 저장
    • 'req.body.키' 의 형태로 전달 받은 데이터에 접근할 수 있으며, 해당 키(key) 호출 시 값(value)을 확인
  • 이미지 파일
    • 파일(이미지 파일 외 모든 파일)은 req.file 에 저장
    • 포스팅에서는 설명을 위한 예시로 이미지 파일이라고 한정지어 이야기합니다.
    • req.file에는 파일의 정보가 담겨있습니다.

 
텍스트와 함께 데이터가 제대로 전달되는지 확인해봅시다.
 
1. 이미지와 텍스트 내용 전달하기

 
2. 서버에서 전달 받은 내용 확인하기 (텍스트와 파일 정보)

  • 포스팅에서 제공하는 코드는 콘솔로 확인 가능하도록 코딩되어있습니다. 때문에 콘솔에서 해당 값을 확인해봅니다.

전체 소스 코드

const express = require('express');
const multer = require('multer');
const path = require('path');

const app = express();

// 이미지 파일을 저장할 디렉토리와 파일 이름 설정
const storage = multer.diskStorage({
    // 저장할 파일 경로
    destination: (req, file, cb) => {
        cb(null, './uploads/'); // 이미지 파일 저장 경로 설정
    },
    // 저장할 파일 이름
    filename: (req, file, cb) => {
        /* 현재시간 구하기 yyyy-MM-dd-Thh-mm-ss-fff */
        const today = new Date(); // 현재시간
        const year = today.getFullYear(); // 년 (yyyy)
        const month = today.getMonth() + 1;  // 월 (MM)
        const date = today.getDate();  // 일 (dd)
        const hours = today.getHours(); // 시 (hh)
        const minutes = today.getMinutes();  // 분 (mm)
        const seconds = today.getSeconds();  // 초 (ss)
        const milliseconds = today.getMilliseconds(); // 밀리초 (fff)

        // 현재시간으로 파일 이름 설정
        const now = year+"-"+month+"-"+date+"-T"+hours+"-"+minutes+"-"+seconds+"-"+milliseconds;
        const extension = path.extname(file.originalname); // 파일 확장자
        const filename = `${now}${extension}`; // 파일 이름 생성
        cb(null, filename); // 파일 이름 설정
    }
});

const upload = multer({ storage: storage }); // 이미지를 저장할 폴더 설정

// 이미지 업로드 처리
// upload.single('a') : req.file은 a라는 필드의 파일 정보
// upload.field([{'a':'A'},{'b':'B'}]) : req.files는 String -> Array 형태의 객체
app.post('/upload', upload.single('image'), (req, res) => {
  // req.files 는 `image` 라는 파일정보를 배열로 가지고 있습니다.
  // 텍스트 필드가 있는 경우, req.body가 이를 포함할 것입니다.
  if (!req.file) {
    return res.status(400).json({ error: '이미지를 업로드해주세요.' });
  }
  // 텍스트 정보를 저장
  const textData = req.body.name; // 필드명 : name
  const textData1 = req.body.content; // 필드명 : content
  // 이미지 정보를 저장
  const imageData = req.file;

  console.log('Text Data: name => ', textData);
  console.log('Text Data: content => ', textData1);
  console.log('Image Data:', imageData);
  
  const imagePath = req.file.path;
  // 여기서 imagePath를 이용해 이미지를 저장하거나 다른 작업을 수행할 수 있습니다.
  return res.json({ success: true, imagePath: imagePath });
});

// 서버 실행
const port = 3000;
app.listen(port, () => {
  console.log(`서버가 http://localhost:${port} 에서 실행 중입니다.`);
});

참고