클라이언트에서 서버로 파일을 전송하는 방법을 알아보았다.
input 태그
<input type="file" name="userfile">
- type = "file"로 지정
- name = "서버에서 파일을 인식할 이름"
name으로 이름을 지정하면, 서버에서는 이 이름으로 데이터를 얻는다는 것이다.
예) upload.single("userfile")로 설정해야 한다.
일반 form 전송 - form 태그
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="userfile" /><br />
<input type="text" name="title" /><br />
<button type="submit">업로드</button>
</form>
- action = "서버에 보낼 경로"
- enctype = "multipart/form-data"로 반드시 설정해야 한다.
multer는 multipart(multipart/form-data)가 아닌 폼에서는 동작하지 않는다.
body-parser
데이터를 쉽게 처리할 수 있도록 도와주는 라이브러리
사용 목적: post로 정보를 전송할 때 요청의 body(req.body)로 받을 수 있게 도와준다. → 설정하지 않으면, undefined가 뜬다.
express 4.x부터 body-parser가 내장되어 있어 설치할 필요 없다.
// body-parser 설정
app.use(express.urlencoded({ extended: true }));
// json 형태로 받겠다고 명시
app.use(express.json());
- extended: true → express에 기본 내장된 querystring 모듈을 사용하도록 설정하는 명령어이다.
- extended: false(default 기본값) → querystring 모듈보다 확장된 qs 모듈을 사용하도록 설정하는 명령어이다.
- 단점: body-parser는 멀티파트 데이터(이미지, 동영상, 파일 등)를 처리하지 못한다. → 멀티파트 데이터를 처리하기 위해서 multer를 이용한다.
multer
파일 업로드를 위해 사용되는 미들웨어
express로 서버를 구축할 때 가장 많이 사용되는 미들웨어
파일 업로드 경로 설정
// app.js
// multer 불러오기
const multer = require("multer");
const upload = multer({
// dest: 파일을 업로드하고, 그 파일이 저장될 경로를 지정하는 속성
dest: "uploads/",
});
dest: 파일을 업로드하고, 그 파일이 저장될 경로를 지정한다.
세부 설정
경로 뿐만 아니라 파일명, 파일 크기 등을 직접 지정, 제어한다.
// multer 세부 설정
const uploadDetail = multer({
// storage: 저장할 공간에 대한 정보
// diskStorage: 파일을 디스크에 저장하기 위한 모든 제어 기능을 제공
storage: multer.diskStorage({
// destination: 경로 설정
// done: callback 함수
destination(req, file, done) {
done(null, "uploads/");
},
// 가정) apple.png 파일을 업로드
filename(req, file, done) {
// file.originalname에서 "확장자" 추출 => png
const ext = path.extname(file.originalname);
// path.basename(file.originalname, ext) => apple (확장자 제거한 파일 이름만!!)
// Date.now() => 현재 시간 => 1970년 1월 1일 0시 0분을 기준으로 현재까지 경과된 밀리초
// [파일명 + 현재시간.확장자] 형식으로 파일 업로드
done(null, path.basename(file.originalname, ext) + Date.now() + ext);
},
}),
// 5MB로 파일 크기 제한
// 5 * 2^10 = 5KB
// 5 * 2^10 * 2^10 = 5MB
limits: { fileSize: 5 * 1024 * 1024 },
});
- storage: 저장할 공간에 대한 정보
- diskStorage: 파일을 디스크에 저장하기 위한 모든 제어 기능을 제공한다.
- destination: 저장할 경로
- filename: 파일명
- limits: 파일 제한
- fileSize: 파일 사이즈 제한
- diskStorage: 파일을 디스크에 저장하기 위한 모든 제어 기능을 제공한다.
하나의 파일 업로드
// 싱글 파일 업로드
app.post("/upload", uploadDetail.single("userfile"), (req, res) => {
// req.file: 파일 업로드 성공 결과 (업로드한 파일 정보)
console.log(req.file);
// req.body: title 데이터 정보 확인 가능 (form에 입력한 정보)
console.log(req.body);
res.send("Upload!!");
});
- single(): 하나의 파일 업로드
- req.file: 업로드한 파일의 정보
- req.body: 파일 외에 form에 입력한 나머지 정보
여러 개의 파일 업로드 ver1
// 멀티(ver1)
app.post("/upload/array", uploadDetail.array("userfiles"), (req, res) => {
// req.file: [{}, {}, ...] 배열 형태로 각 파일 정보 가짐
console.log(req.files);
// req.body: title 데이터 정보 확인 가능
console.log(req.body);
res.send("Upload Multiple Each!!");
});
- array(): 하나의 요청 안에 여러 개의 파일을 업로드할 때 사용한다.
- form 태그 안에 있는 input 태그의 속성에 반드시 multiple 속성을 입력해야 한다.
- req.file: [{}, {}, ...] 배열 형태로 각 파일 정보를 가진다.
- req.body: 파일 외에 form에 입력한 나머지 정보
여러 개의 파일 업로드 ver2
// 멀티(ver2)
app.post(
"/upload/fields",
uploadDetail.fields([{ name: "userfile1" }, { name: "userfile2" }]),
(req, res) => {
// req.files: {userfile1: [{}], userfile2: [{}]} 형태로 각 파일 정보 제공
console.log(req.files);
// req.body: title 데이터 정보 확인 가능
console.log(req.body);
res.send("Upload Multiple Each!!");
}
);
- fields(): 하나의 요청이 아닌 여러 개의 파일을 업로드할 때 사용한다.
- req.file: {userfile1: [{}], userfile2: [{}]} 형태로 각 파일 정보를 가진다.
- req.body: 파일 외에 form에 입력한 나머지 정보
동적 파일 업로드
파일 업로드할 때에는 주로 동적 파일 업로드 방식을 사용하는 것이 좋다고 한다.
위 방식을 사용하는 이유는 두 가지가 있다.
- 기본적으로 form 태그 안에 있는 모든 button의 type은 "submit"으로 인식된다. button을 누르면 type="submit"이기 때문에, 페이지 새로고침이 발생하게 된다. 이러한 페이지 새로고침 현상을 발생하지 않게 하기 위해서 동적 파일 업로드 방식을 사용한다.
- 비동기로 실행하기 위해서 동적 파일 업로드 방식을 사용한다.
app.js
// app.js
const express = require('express');
const multer = require('multer');
const app = express();
const path = require('path');
const PORT = 8000;
// multer 세부 설정
const uploadDetail = multer({
storage: multer.diskStorage({
destination(req, file, done) {
// destination: 경로 설정
// done: callback 함수
done(null, 'uploads/');
},
filename(req, file, done) {
// 가정) apple.png 파일을 업로드
const ext = path.extname(file.originalname); // file.originalname에서 "확장자" 추출 => png
console.log('ext: ', ext);
// path.basename(file.originalname, ext) => apple (확장자 제거한 파일이름만!!)
// Date.now() => 현재 시간 (1680309346279)
// => 1970년 1월 1일 0시 0분 0초를 기준으로 현재까지 경과된 밀리초
done(null, path.basename(file.originalname, ext) + Date.now() + ext);
// [파일명 + 현재시간.확장자] 형식으로 파일 업로드
},
}),
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB로 파일 크기 제한
// 5 * 2^10 = 5KB
// 5 * 2^10 * 2^10 = 5MB
});
app.set('view engine', 'ejs');
app.use('/views', express.static(__dirname + '/views'));
app.use('/uploads', express.static(__dirname + '/uploads'));
// body - parser
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.get('/', (req, res) => {
res.render('index');
});
app.post('/dynamic', uploadDetail.single('dynamic-file'), (req, res) => {
console.log(req.file);
console.log(req.body);
res.send(req.file);
});
app.listen(PORT, () => {
console.log(`http://localhost:${PORT}`);
});
index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- axios cdn -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<title>파일 업로드</title>
</head>
<body>
<h1>파일 업로드 하기</h1>
<!-- 동적 파일 업로드 -->
<h2>동적 파일 업로드</h2>
<input type="text" name="text" id="text" /><br />
<input type="file" name="dynamic-userfile" id="dynamic-file" /><br />
<button type="button" onclick="fileUpload();">업로드</button>
<!-- 업로드할 파일을 보여줄 이미지 태그 -->
<br /><img class="img-box" src="" />
<script>
function fileUpload() {
// 동적으로 파일을 업로드할 때 FormData를 만들어줘야 한다.
// formData: form 태그의 데이터를 동적으로 제어할 수 있는 기능
const formData = new FormData();
const text = document.getElementById("text");
const fileInput = document.getElementById("dynamic-file");
console.log(fileInput.files);
formData.append("dynamic-file", fileInput.files[0])
formData.append("text", text.value)
axios({
url: "/dynamic",
method: "POST",
data: formData,
headers: {
"Content-Type": "multipart/form-data"
}
}).then((res) => {
console.log(res)
const imgElement = document.querySelector(".img-box");
imgElement.src = `/${res.data.path}`
})
}
</script>
</body>
</html>