일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 오블완
- 자바스크립트
- 자바스크립트 실행 컨텍스트
- 티스토리챌린지
- html 코드
- 프론트엔드
- 자바스크립트 생성자 함수
- 자바스크립트 promise
- 자바스크립트 클래스
- css position
- HTML
- 자바스크립트 scope
- 리액트 함수형 컴포넌트
- 자바스크립트 연산자
- javascript opreators
- 자바스크립트 클로저
- html 주석
- javascript closure
- CSS
- 자바스크립트 async await
- 리액트 개념
- css3
- javascript opreator
- 자바스크립트 프로미스
- javascript
- 프론트엔드 리액트
- 자바스크립트 상속
- css 포지션
- 웹 개발 트렌드
- 자바스크립트 반복문
- Today
- Total
Multi Developer SuHo
Node js Multer 모듈로 이미지 업로드 본문
안녕하세요~ Node js에서 Multer 모듈로 이미지를 업로드하는 방식을 구현해보겠습니다.
📑목차
1. 파일 업로드 모듈 Multer
1-1. Multer 모듈이란?
1-2. Multer 모듈 설치
1-3. Multer 모듈을 왜 사용하는가?
1-4. 웹에서 파일을 업로드 하는 경우
1-5. Multer 모듈 구조
1-6. Stoarge 옵션 (diskStorage, memoryStorage)
1-7. multipart/form-data 형식이란?
1-8. MIME 타입 (Multipurpose Internet Mail Extensions)
1-9. Multer 모듈 사용 코드
서론
오늘은 Multer 모듈에 대해 알아보겠습니다. Multer 라고 들어보신적 있으시간요? Node js를 접해보셨더라면 "Multer"가 이거였구나! 라고 생각될 수 있습니다. 처음 Node js를 입문하신다면 Multer가 무엇인지 잘모를 수 있습니다. Multer 가 무엇이고, 이것을 왜 사용하는지 알아봐야합니다. 그럼 본문으로 넘어가시죠~~

본론
1. 파일 업로드 모듈 Multer
1-1. Multer 모듈이란?
express 환경에서 파일의 업로드(이미지나, 동영상)를 처리하는 미들웨어
1-2. Multer 모듈설치
$ npm i multer
1-3. Multer 모듈을 왜 사용하는가?
이전에는 fs 모듈을 사용하여 파일을 불러오고 서버에서 데이터를 청크 단위로 쪼개서 받거나, 버퍼 처리를 통해 데이터를 관리해야하고, 업로드한 파일을 서버에 저장하는 과정도 관리해야했고. 버퍼 처리를 통해서 데이터를 관리해야 했고, 업로드한 파일을 서버에 저장하는 과정도 직접 구현해야했습니다. 이러한 기능을 보완하고 서버에서 동적으로 처리할 수 있는 모듈이 Multer 모듈에서 제공되기 때문에 Multer 모듈을 사용합니다.
1-4. 웹에서 파일을 업로드 하는 경우
form 요소로 파일을 업로드를 했고, 데이터를 효과적으로 관리하기 위해 entype=multipart/form-data 방식을 사용하게 되었습니다. 파일을 업로드를 할 때 응답 헤더에 'content-type' 이 multipart/form-data 방식인지 확인해서 파일을 처리합니다. 업로드한 파일의 파싱 내용도 요청 객체(req)에 생성합니다.
참고
파싱 내용이라는 것은 서버가 업로드한 파일을 처리하면서 파일의 해당 내용과 메타 데이터등을 추출하고 분석하는 과정을 말합니다.
1-5. Multer 모듈의 구조
1. 스트림을 이용한 파일 처리
Multipart/form-data 형식의 데이터를 처리하기 위해 스트림을 사용합니다. 스트림은 데이터를 한 번에 메모리에 전달하지 않고, 데이터를 일부분씩 처리하는 방식으로, 대용량 파일들을 처리할 때 스트림 방식으로 효율적으로 처리할 수 있기 때문에, Multer는 요청 객체에서 파일 데이터를 스트림 방식으로 받아 처리할 수 있습니다.
2. 스토리지 옵션(diskStorage, memoryStorage)
파일을 저장할 방법을 설정할 수 있는 옵션
3. 파일 필터링(fileFilter)
업로드되는 파일을 필터링해서 확장자 등의 조건에 맞는 파일만 처리할 수 있는 옵션
4. 파일 크기 제한(Limits)
파일의 크기를 제한할 수 있는 옵션
1-6. Storage 옵션(diskStorage, memoryStorage)
업로드된 파일을 저장할 방법을 설정하는 옵션입니다.
diskStorage : 서버에 디스크의 저장하는 옵션, 즉, 사용자의 컴퓨터가 아닌 웹 서버에 있는 특정 폴더나 파일에 저장한다는 뜻을 말합니다.
memoryStorage : 서버에 디스크가 아닌 메모리의 저장하는 옵션을 말합니다.
diskStorage에는 두 가지 함수가 있습니다. 두 개의 함수 모두 공통적으로 3개의 매개변수를 받습니다.
1. destination : 파일이 저장될 디렉터리를 설정
첫 번째 매개변수로 요청객체(req)를 받습니다.
두 번째 매개변수로 "file" 이라는 업로드한 파일의 정보를 받습니다.
세 번째 매개변수로 cb 콜백을 받습니다. 콜백을 호출할 때는 cb(error, result)로 호출이 되고, error는 에러가 발생했을 때 전달되고, result는 저장한 폴더의 경로나, 파일명을 전달하는데 사용합니다.
2. filename : 파일의 이름을 설정
첫 번째 매개변수로 요청객체(req)를 받습니다.
두 번째 매개변수로 "file" 이라는 업로드한 파일의 정보를 받습니다.
세 번째 매개변수로 cb 콜백을 받습니다. 콜백을 호출할 때는 cb(error, result)로 호출이 되고, error는 에러가 발생했을 때 전달되고, result는 저장한 폴더의 경로나, 파일명을 전달하는데 사용합니다.
const storage = multer.diskStorage({
destination : (req, file, cb) => {
cb(null, 'uploads');
},
// destination, filename 키 고정
filename : (req, file, cb) => {
cb(null, file.fieldname + '_' + Date.now() + path.extname(file.originalanme))
}
})
// file : 파일의 내용을 가지고 있는 객체
// cb : 다음 미들웨어를 호출할 콜백함수
// file키에 저장할 이름의 형식을 전달해준다.
// file.fieldname : HTML 폼에서 파일 입력 필드
// file.originalname : 업로드된 파일의 원본 이름
// Date.now() : 현재 시간을 반환하는 메서드
// path.extname() : 파일의 확장자를 보여주는 메서드
cb(null, file.fieldname + '_' + Date.now() + path.extname(file.originalname))
코드를 해석하면 HTML 입력폼에서 파일 입력필드와 현재시간과 파일의 확장자를 보여주는데 파일의 확장자에 업로드된 파일의 원본 이름을 포함시킵니다.
1-7. multipart/form-data 형식이란?
Binary Data를 전달할 수 있고, 여러가지 데이터도 같이 전달할 수 있는 형식을 말한다.
1-8. MIME 타입 (Multipurpose Internet Mail Extensions)
### MIME 타입 (Multipurpose Internet Mail Extensions)
> 컨텐츠를 전달할 때 사용하는 파일의 종류를 요청 메세지에서 알려준다.
> content-type : text/html
> content-type : image/png
> content-type : application/json
> content-type : multipart/form-data
> content-type : application/actet-stream
> multipart/form-data : Binary Data를 전달할 수 있고, 여러가지 데이터도 같이 전달할 수 있다.
> 파일과 텍스트 데이터를 같은 요청에 포함해서 보낼 수 있다.
요청 헤더에 담긴 "multipart/form-data" 가 보이시나요? 요청 헤더가 content-type : "multipart/form-data" 형식으로 서버에 전송되면, 서버는 요청 본문을 여러 부분으로 나누어 처리하게 됩니다.
1-9. Module을 사용한 이미지 업로드 구현
DB가 없기 때문에 임시 데이터를 활용하였습니다.
MVC 패턴으로 Model에는 데이터를 저장하고, Controller에서는 데이터를 가져와서 제어한 뒤, View에서 전달하여 보여줍니다.
Model 에서 관리하는 board.js (게시판 DB)
// 임시 데이터베이스
let coffee = [];
// 데이터베이스 생성
// 매개변수로는 title, menu, imgPath 전달
// push 메서드 사용하여 배열에 추가
// [{}] 형태로 추가
const CreateCoffee = (title, menu, imgPath) => {
coffee.push({title, menu, imgPath});
return "게시글 추가 완료~"
}
// 전체 번호 조회
const selectAll = () => {
return coffee;
}
// 특정 번호 조회
const selectIndex = (index) =>{
return coffee[index];
}
// 객체 형태로 내보낸다.
module.exports = {CreateCoffee, selectAll, selectIndex}
Controller에서 Model에 있는 임시데이터를 가져와서 제어
const {CreateCoffee, selectAll, selectIndex} = require('../Model/board.js')
// 매개변수로는 Express router에서 자동으로 전돨된 요청객체를 받는다
const CoffeBoard = (req) => {
const {title, menu} = req.body;
console.log("요청된 제목", title);
console.log("요청된 메뉴", menu);
const {filename} = req.file;
console.log("너냐?", filename)
const imgName = "http://localhost:3000/image/" + filename;
CreateCoffee(title, menu, imgName)
}
// 전제 게시판 조회
const selectBoardAll = () => {
return selectAll();
}
// 특정 게시판 조회
const selectBoardIndex = (index) => {
return selectIndex(index);
}
module.exports = {CoffeBoard, selectBoardAll, selectBoardIndex};
Views (화면에 보여줄 파일)
coffee.ejs (게시판 작성 페이지)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/public/coffee.css">
</head>
<body>
<h1>내가 좋아하는 커피 업로드</h1>
<form action="/coffee" method="post" enctype="multipart/form-data">
<label for="">제목</label>
<input type="text" name="title">
<label for="">메뉴</label>
<input type="text" name="menu">
<label for="">이미지 업로드</label>
<input type="file" name="image">
<button>저장하기</button>
</form>
</body>
</html>
main.ejs (메인 페이지)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/public/style.css">
</head>
<body>
<h1>수호 커피 게시판입니다</h1>
<div class="container">
<ul>
<% for(let i = 0; i < coffee.length; i++) {%>
<li>
<span><%= coffee[i].title %> </span>
<span><%= coffee[i].menu %> </span>
<span><img src="<%= coffee[i].imgPath %>" alt=""> </span>
</li>
<% } %>
</ul>
</div>
</body>
</html>
lib 폴더
imageUpload.js (Multer를 적용한 이미지 업로드 기능)
const multer = require('multer');
const path = require('path');
exports.upoladimage = multer({ storage : multer.diskStorage(
{
destination : (req, file ,cb) => {
cb(null, "Uploads");
},
filename : (req, file , cb) => {
const ext = path.extname(file.originalname);
const basename = path.basename(file.originalname, ext) + "_" + Date.now() + ext;
cb(null, basename, + ext);
}
}),
limits : {fileSize : 5 * 1024 * 1024}
})
board.router.js (정적 라우팅 처리)
// express 내부에서 제공하는 Router 라이브러리 사용
const router = require('express').Router();
const {upoladimage} = require('../lib/imageUpload')
const {CoffeBoard, selectBoardAll, selectBoardIndex} = require('../Controller/board.controller')
// get 요청으로 루트 경로 들어오면 main 페이지를 응답한다.
router.get('/', (req, res) => {
res.render('coffee');
})
router.get('/main', (req, res) => {
const coffee =selectBoardAll()
res.render('main', {coffee});
})
router.post('/coffee', upoladimage.single("image"), (req, res) => {
CoffeBoard(req)
res.redirect('/main')
})
module.exports = router;
server.js (서버 상태 관리)
// express 모듈 불러오기
const express = require('express');
// path 모듈 불러오기
const path = require('path');
const router = require('./routers/board.router');
// 서버 객체 생성
const app = express();
// view engine에 'ejs'를 사용
app.set('view engine', 'ejs');
// public 경로를 정적으로 처리하는 미들웨어 추가
app.use("/public", express.static(path.join(__dirname, "public")));
// image 경로를 정적으로 처리하는 미들웨어 추가
app.use("/image", express.static(path.join(__dirname, "Uploads")));
// body(본문)의 내용을 파싱하겠다. 미들웨어 추가
app.use(express.urlencoded({extended : false}));
app.use(router);
app.listen(3000, () => {
console.log("서버 작동하고 있다");
})
coffee.ejs (커피 게시판 작성 페이지)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/public/coffee.css">
</head>
<body>
<h1>내가 좋아하는 커피 업로드</h1>
<form action="/coffee" method="post" enctype="multipart/form-data">
<label for="">제목</label>
<input type="text" name="title">
<label for="">메뉴</label>
<input type="text" name="menu">
<label for="">이미지 업로드</label>
<input type="file" name="image">
<button>저장하기</button>
</form>
</body>
</html>
main.ejs (메인 페이지)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/public/style.css">
</head>
<body>
<h1>수호 커피 게시판입니다</h1>
<div class="container">
<ul>
<% for(let i = 0; i < coffee.length; i++) {%>
<li>
<span><%= coffee[i].title %> </span>
<span><%= coffee[i].menu %> </span>
<span><img src="<%= coffee[i].imgPath %>" alt=""> </span>
</li>
<% } %>
</ul>
</div>
</body>
</html>
결론
Multer 모듈을 사용하여 게시판 페이지를 간단하게 만들어보았는데요, 이미지 업로드 기능을 통해 사용자들이 이미지를 첨부할 수 있습니다. Multer 모듈을 효율적으로 활용한다면 다양한 멀티미디어 기능을 구현할 수 있습니다.
'Node.js 기록' 카테고리의 다른 글
Node js AJAX, Fetch, Axios, Form 태그의 요청과 차이 (1) | 2025.03.15 |
---|---|
Node js JWT 토큰(JSON Web Token)이란 (0) | 2025.03.13 |
Node js [GET 방식과 POST 방식, 요청 객체(Requset), 응답 객체(Response) ] (0) | 2025.03.10 |
Node js MVC(Model-View-Controller) 패턴이란? (0) | 2025.03.08 |
Node js 라우터(Router)란? (0) | 2025.03.07 |