파일 업로드를 위한 Multer
라이브러리 사용법을 정리합니다
npm init -y
npm install express cors nunjucks
npm install multer
백엔드 디렉토리에 multer
라이브러리를 설치합니다
multer()
호출시 몇 가지 옵션을 지정할 수 있습니다
storage
: 저장에 관한 옵션입니다
destination
: 업로드 한 파일을 어디에 저장할 지 지정합니다filename
: 저장 할 파일의 이름을 결정합니다limits
: 업로드할 파일의 용량 제한을 설정합니다
그 밖에도 두 가지 옵션이 더 내장돼있지만 여기서는 생략...
[upload.js]
const multer = require("multer"); // 바디파서 역할을 하는 미들웨어를 만드는 것이 목적입니다
const path = require("path");
// client에서 이미지 파일을 텍스트 형태로 건네면 server 측에서 그 텍스트를 파일로 저장합니다
// console.log(multer());
const upload = multer({
storage: multer.diskStorage({
destination: (req, file, done) => {
done(null, "uploads/");
},
filename: (req, file, done) => {
// path 라이브러리 사용, 업로드된 파일명이 겹치지 않도록 해야합니다
// 예시: 1.jpg > 1_[timestamp].jpg
const ext = path.extname(file.originalname) // ext ~ 확장자, originalname: 클라이언트가 요청을 보낸 파일명 (1.jpg)
// basename ~ 확장자를 제외한 파일명 (1.jpg > 1)
const basename = path.basename(file.originalname, ext)
const filename = `${basename}_${Date.now()}${ext}` // 파일명+시간+확장자
done(null, filename)
}
}),
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB (1024byte = 1kb * 1024 = 1MB)
});
module.exports = upload
파일 업로드에는 다음과 같은 메서드를 사용할 수 있습니다
single
: 업로드할 인풋 박스가 하나뿐일 경우 하나의 파일만 업로드합니다array
: 하나의 인풋박스를 통해서 파일 여러개를 업로드할 수 있습니다uploads
: 인풋 박스 여러개를 사용해서 여러 파일을 업로드합니다 (배열로 반환)
const express = require("express");
const cors = require("cors");
const nunjucks = require("nunjucks");
const upload = require("./middlewares/upload");
const app = express();
app.set("view enging", "html");
nunjucks.configure("views", { express: app });
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.get("/single", (req, res, next) => {
res.render("single.html");
});
app.get("/array", (req, res, next) => {
res.render("array.html");
});
app.get("/uploads", (req, res, next) => {
res.render("uploads.html");
});
app.post("/single", upload.single("upload"), (req, res) => {
// 인풋박스 1개 + 파일 1개
console.log("req.file :", req.file);
// req.file : {
// fieldname: 'upload',
// originalname: 'fail.png',
// encoding: '7bit',
// mimetype: 'image/png',
// destination: 'uploads/',
// filename: 'fail_1674615666348.png',
// path: 'uploads/fail_1674615666348.png',
// size: 215986
// }
console.log(req.body);
// DB에는 경로 + 파일 이름만 건넵니다
res.send("upload");
});
app.post("/array", upload.array("upload"), (req, res) => {
// 인풋 박스 1개 + 파일 여러개
console.log("req.files :", req.files);
console.log(req.body);
res.send("upload");
});
// [{name:'uplonad}]
app.post(
"/uploads",
upload.fields([
{ name: "upload1" },
{ name: "upload2" },
{ name: "upload3" },
{ name: "upload4" },
]),
(req, res) => {
console.log(req.files.upload1); // [{}]
console.log(req.files.upload2); // [{}]
console.log(req.files.upload3); // [{}]
console.log(req.files.upload4); // [{}]
console.log(req.body);
res.send("upload");
}
);
app.listen(3000, () => {
console.log("back start");
});
여기서 주의할 점은 각 메서드의 인자값이 업로드할 인풋 엘리먼트의 name 속성이라는 것!
<input type="file" name="upload">
[html]
<form method="post" action="/single" id="photoFrm">
<p class="title">사진 등록</p>
<!--name value가 multer 메서드의 인자-->
<input type="file" name="photoid">
<button type="submit">보내기</button>
</form>
[javascript]
// 이미지 등록
console.log(document.querySelector("#photoFrm"))
document.querySelector("#photoFrm").addEventListener("submit", async (e) => {
e.preventDefault()
const body = new FormData(e.target) // FormData: 바디영역을 생성해주는 내장객체
const response = await
// 첫번째 인자는 백엔드 URL, 두번째 인자는 요청바디(FormData가 자동생성), 세번째 인자로는 Content-type을 변경해야 합니다
request.post("/users/single", body, {
headers: {
['Content-Type']: "multipart/form-data"
}
})
console.log(response)
})
요청을 보낼 때 헤더 영역의 Content-Type
을 위와 같은 형식으로 고쳐야 합니다
미들웨어 생성
const multer = require('multer');
const path = require('path');
const uploadImage = multer({
storage: multer.diskStorage({
// 1. 저장할 경로
destination: (req, file, done) => {
done(null, "uploads/")
},
// 2. 파일명_시간.확장자
filename: (req, file, done) => {
const ext = path.extname(file.originalname)
const basename = path.basename(file.originalname, ext)
done(null, basename + '_' + Date.now() + ext);
}
}),
limits:{ fileSize: 10* 1024 * 1024 }, // 10MB
})
module.exports = uploadImage
라우터 추가
router.post("/single", upload.single("photoid"), (req, res) => {
res.send(req.file);
});
router.post("/array", upload.array("photoid"), (req, res) => {
res.send(req.files);
});
정적파일 연결
백엔드 서버에 정적파일 연결을 위한 코드를 추가합니다
app.use(express.static('uploads'))
form 엘리먼트 설정
<form method="post" action="/single" id="photoFrm">
<p class="title">사진 등록</p>
<input type="file" name="photoid">
<button type="submit">전송</button>
</form>
이미지 업로드를 위한 form 태그는 회원가입을 위한 form(아래)과 따로 분리되어야 합니다
<form method="post" action="/signup" id="signupFrm">
<h2 id="bannerText">회원가입</h2>
<ul class="container">
<div id="imageBox">
<img id="previewImg" src="" alt="">
<input type="hidden" name="image" id="userImg">
</div>
클라이언트에서 비동기 요청하기
document.querySelector("#photoFrm").addEventListener("submit", async (e) => {
e.preventDefault();
const body = new FormData(e.target);
const response = await request.post("/users/single", body, {
headers: {
["Content-Type"]: "multipart/form-data",
},
});
document.querySelector('#userImg').value = response.data.filename;
document.querySelector('#previewImg').src = `http://127.0.0.1:3001/${response.data.filename}`;
// console.log(document.querySelector('#userImg').value)
// console.log(document.querySelector('#imageBox > img').src)
});
이제 응답 데이터는 회원가입을 위한 form 태그의 img, input 엘리먼트로 전달됩니다