본문 바로가기
Node.js

익스프레스 웹 서버 만들기 - 미들웨어(multer)

by Programmer.Junny 2025. 1. 15.

1. multer 란?

multer Express.js 애플리케이션에서 파일 업로드를 간편하게 처리할 수 있도록 도와주는 미들웨어이다. 특히 멀티파트 폼 데이터(multipart/form-data)를 파싱하여 서버 측에서 사용할 수 있게 해준다.

1.1. multer의 기본 개념

1.1.1. 멀티파트 폼 데이터

  • HTML 폼을 통해 파일을 업로드할 때, 브라우저는 multipart/form-data 형식으로 서버에 전송한다.
  • 일반적인 application/x-www-form-urlencoded와는 다르게, 파일 바이너리를 포함할 수 있으므로 특수한 파서가 필요하다.

1.1.2 multer의 역할

  • **multer**는 **multipart/form-data**를 파싱하여, 텍스트 필드 업로드된 파일 req.body, req.file(단일 파일) 또는 req.files(다중 파일) 형태로 제공한다.
  • 파일 저장에 대해 다양한 전략(메모리, 디스크, 커스텀 저장소 등)을 설정할 수 있다.

2. 설치

npm install multer

3. 주요 옵션 및 설정

3.1 저장소(Storage) 설정

  • multer.diskStorage: 파일을 디스크에 저장.
  • multer.memoryStorage: 파일을 메모리 버퍼에 저장.
  • 커스텀 저장소: 원하는 저장소를 직접 구현할 수 있다.
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/');
  },
  filename: (req, file, cb) => {
    cb(null, Date.now() + path.extname(file.originalname));
  }
});

const upload = multer({ storage: storage });

3.2 파일 필터링(fileFilter)

  • 업로드 파일 유형을 제한하거나 확장자를 검증하는 기능을 제공한다.
const fileFilter = (req, file, cb) => {
  if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') {
    cb(null, true);
  } else {
    cb(new Error('지원하지 않는 파일 형식입니다.'), false);
  }
};

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

3.3 업로드 제한(limits)

  • 파일 크기(fileSize)와 같은 업로드 제한을 설정하여 보안을 강화할 수 있다.
const upload = multer({
  storage: storage,
  limits: { fileSize: 5 * 1024 * 1024 } // 최대 5MB
});

4. 멀티 파일 업로드 & 필드 처리

4.1 단일 파일 업로드

  • upload.single('fieldname'): 특정 필드 이름으로 단일 파일 업로드.
app.post('/uploadSingle', upload.single('image'), (req, res) => {
  // 파일 정보: req.file
  res.send('단일 파일 업로드 완료');
});

4.2 다중 파일 업로드

  • upload.array('fieldname', maxCount): 여러 파일을 업로드.
app.post('/uploadMultiple', upload.array('images', 3), (req, res) => {
  // 파일 정보: req.files (배열)
  res.send('다중 파일 업로드 완료');
});

4.3 여러 필드 처리

  • upload.fields([{ name: 'image', maxCount: 1 }, { name: 'documents', maxCount: 2 }]):
    • 여러 필드를 각각 다른 개수로 업로드.
app.post('/uploadFields', upload.fields([
  { name: 'image', maxCount: 1 },
  { name: 'documents', maxCount: 2 }
]), (req, res) => {
  // req.files.image -> 단일 파일 배열
  // req.files.documents -> 다중 파일 배열
  res.send('여러 필드 업로드 완료');
});

5. 에러 처리

5.1 파일 크기 초과

  • **limits.fileSize**를 초과하면, **multer**가 에러를 던집니다.
  • 에러 핸들링 미들웨어에서 처리하거나, 라우트 내에서 처리해야 합니다.
app.post('/upload', upload.single('image'), (err, req, res, next) => {
  if (err instanceof multer.MulterError && err.code === 'LIMIT_FILE_SIZE') {
    return res.status(400).send('파일 크기가 너무 큽니다.');
  }
  next(err);
});

5.2 파일 형식 제한

  • fileFilter에서 형식이 맞지 않으면 에러를 발생시킬 수 있습니다.
  • 마찬가지로 에러 핸들링 미들웨어에서 처리 가능합니다.

6. 사용 예시

static 을 위한 public 디렉토리

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>업로드 파일 없음</title>
</head>
<body>
<script>
  alert("업로드할 파일이 비어있습니다!");
  location.href = "/upload";
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>에러 발생</title>
</head>
<body>
<script>
  alert("이미지 파일만 업로드 가능합니다!");
  location.href = "/upload";
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>이미지 업로드 성공</title>
</head>
<body>
<script>
  alert("이미지 업로드 성공!");
  location.href = "/upload";
</script>
</body>
</html>

public에 포함된 emptyFiles.html, error.html, success.html 을 위와 같이 구성하였다.

const express = require('express');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const session = require('express-session');
// const dotenv = require('dotenv');
const path = require('path');

// dotenv.config();
const app = express();
app.set('port', process.env.PORT || 3000);

app.use(morgan('dev'));
app.use('/', express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(session({
  resave: false,
  saveUninitialized: false,
//   secret: process.env.COOKIE_SECRET,
    secret: '1234',
  cookie: {
    httpOnly: true,
    secure: false,
  },
  name: 'session-cookie',
}));

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

try {
  fs.readdirSync('uploads');
} catch (error) {
  console.error('uploads 폴더가 없어 uploads 폴더를 생성합니다.');
  fs.mkdirSync('uploads');
}
const upload = multer({
  storage: multer.diskStorage({
    destination(req, file, done) {
      done(null, 'uploads/');
    },
    filename(req, file, done) {
      const ext = path.extname(file.originalname);
      done(null, path.basename(file.originalname, ext) + Date.now() + ext);
    },
  }),
  fileFilter: (req, file, done) => {
    // 이미지 파일만 허용
    if (/^image\/(jpeg|png|gif)$/.test(file.mimetype)) {
        done(null, true);
    } else {
        done(new Error('이미지 파일만 업로드 가능합니다.'), false);
    }
  },
  limits: { fileSize: 5 * 1024 * 1024 },
});

app.get('/upload', (req, res) => {
  res.sendFile(path.join(__dirname, 'multipart.html'));
});

// app.post('/upload', upload.array('image'), (req, res) => {
// //   console.log(req.file);
//     console.dir(req.files);
//   res.send('ok');
// });

app.post('/upload', (req, res) => {
    upload.array('image')(req, res, (err) => {
        if(err) {
            // 에러 발생 시 -> error.html 응답
            return res.sendFile(path.join(__dirname, 'public', 'error.html'));
        }

        if(!req.files || req.files.length == 0) {
            return res.sendFile(path.join(__dirname, 'public', 'emptyFiles.html'));
        }

        // 성공 시
        res.sendFile(path.join(__dirname, 'public', 'success.html'));
    });
});

app.get('/', (req, res, next) => {
  console.log('GET / 요청에서만 실행됩니다.');
  next();
}, (req, res) => {
  throw new Error('에러는 에러 처리 미들웨어로 갑니다.')
});

app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).send(err.message);
});

app.listen(app.get('port'), () => {
  console.log(app.get('port'), '번 포트에서 대기 중');
});

위의 코드에서 const upload 에 multer를 세팅한다.

upload 경로

브라우저에서 3000포트의 /upload 의 경로로 이동하면, app.get('/upload' ~~) 라우터가 실행되며 'multipart.html'이 실행되며 위 사진과 브라우저에 html을 렌더링한다. (클라이언트[브라우저] 에서는 get으로 multipart.html을 받아 렌더링함)

파일을 선택 시

파일 선택 후 업로드 버튼을 누르면 app.post('/upload', ~~) 가 실행된다.

업로드 버튼을 누르면 static에 있는 success.html 호출

upload.array('image')(req, res, (err) => {
        if(err) {
            // 에러 발생 시 -> error.html 응답
            return res.sendFile(path.join(__dirname, 'public', 'error.html'));
        }

        if(!req.files || req.files.length == 0) {
            return res.sendFile(path.join(__dirname, 'public', 'emptyFiles.html'));
        }

        // 성공 시
        res.sendFile(path.join(__dirname, 'public', 'success.html'));
    });

upload.array 미들웨어를 호출하여 public 내에 있는 html들 중 하나를 실행하게 된다.

최근댓글

최근글

skin by © 2024 ttuttak