본문 바로가기

Node.js

Node.js(23) - Multer를 활용한 파일 업로드

728x90
반응형

1) Node.js

   1-1) Multer를 활용한 파일 업로드

      1-1-1) back

      1-1-2) front

 

 

 

 

 

 

 

1) Node.js

1-1) Multer를 활용한 파일 업로드

1-1-1) back

파일 업로드

이미지 파일은 컴퓨터 입장에서는 어떤 내용을 가지고 있는가?

 

이미지도 컴퓨터 입장에서는 "text"이다!(이미지도 "binary file"이다!)
이미지 확장자를 ".jpg"에서 ".txt"로 변환해봤더니 이상한 텍스트들이 많았다!

"http protocol"에는 request messageresponse message가 있다.
이 중 request message에는 start line, header, body 내용이 있다!

여기서 request message의 header 영역에는 "Content-type" 속성이 있음!
Content-type: application/json
Content-type: application/x-www-form-urlencoded
Content-type: multipart/form-data

 

$ npm init -y
$ npm install express cors nunjucks


GET /single single.html
GET /array array.html
GET /uploads uploads.html

 

multipart/form-data bodyparser
Multer: request header의 Content-type이 "multipart/form-data"인 경우를 parsing할 수 있는 body parser를 의미함!

$ npm install multer

 

 

server.js

const express = require("express");
const cors = require("cors");
const nunjucks = require("nunjucks");
const upload = require("./middlewares/upload");
const app = express();

app.set("view engine", "html");
nunjucks.configure("views", {
  express: app,
});

app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.get("/single", (req, res) => {
  res.render("single.html");
});

app.get("/array", (req, res) => {
  res.render("array.html");
});

app.get("/uploads", (req, res) => {
  res.render("uploads.html");
});

// single: upload할 input box가 하나밖에 없는 경우(단 하나의 이미지만 업로드하는 경우) 사용하는 메서드
// single 메서드의 인자값은 type이 "file"인 input box의 name 값을 넣어주면 됨!
app.post("/single", upload.single("upload"), (req, res) => {
  console.log(req.file);
  console.log(req.body);
  // DB에는 경로, 파일명을 저장해줘야 함!
  res.send("upload");
});

// array: 하나의 input에 여러 개의 파일을 업로드할 때 사용하는 메서드
app.post("/array", upload.array("upload"), (req, res) => {
  console.log(req.files);
  console.log(req.body);

  res.send("upload");
});

// fields: 여러 개의 input에서 각각 하나의 파일을 업로드할 때 사용하는 메서드
app.post(
  "/uploads",
  upload.fields([
    { name: "upload1" },
    { name: "upload2" },
    { name: "upload3" },
    { name: "upload4" },
  ]),
  (req, res) => {
    console.log(req.files);
    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("server start");
});

 

 

views/single.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h2>업로드</h2>
    <!-- enctype 속성을 통해 request header의 "Content-type"의 값을 지정할 수 있음!(default 값은 "application/x-www-form-urlencoded"이다.) -->
    <!-- "multipart/form-data"는 input box에 내용을 입력하여 던져줄 때 단순히 string 값만 던져주는 것이 아니라 이미지 파일과 같이 큰 내용을 보낼 때 사용하는 type이다! -->
    <form method="post" action="/single" enctype="multipart/form-data">
      <ul>
        <li>
          <input type="text" name="subject" />
        </li>
        <li>
          <!-- input 타입이 "file"이면 브라우저 화면 상에 파일 선택 버튼이 생성되어 해당 버튼을 누르면 이미지 파일을 선택할 수 있는 팝업창이 뜸!! -->
          <input type="file" name="upload" />
        </li>
        <li>
          <button type="submit">전송</button>
        </li>
      </ul>
    </form>
  </body>
</html>

 

 

views/array.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h2>업로드</h2>
    <form method="post" action="/array" enctype="multipart/form-data">
      <ul>
        <li>
          <input type="text" name="subject" />
        </li>
        <li>
          <!-- multiple: 하나의 input box에 여러 파일을 넣을 수 있음 -->
          <input type="file" name="upload" multiple />
        </li>
        <li>
          <button type="submit">전송</button>
        </li>
      </ul>
    </form>
  </body>
</html>

 

 

views/uploads.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h2>업로드</h2>
    <form method="post" action="/uploads" enctype="multipart/form-data">
      <ul>
        <li>
          <input type="text" name="subject" />
        </li>
        <li>
          <input type="file" name="upload1" />
          <input type="file" name="upload2" />
          <input type="file" name="upload3" />
          <input type="file" name="upload4" />
        </li>
        <li>
          <button type="submit">전송</button>
        </li>
      </ul>
    </form>
  </body>
</html>

 

 

middlewares/upload.js

const multer = require("multer");
const path = require("path");

// multer 또한 하나의 미들웨어이다!
// multer의 주목적: multer는 미들웨어로서 body parser 역할을 수행함

// 파일 업로드를 구현하기 위해 multer를 활용함!
// client(브라우저)에서 server 측에 파일 내용을 보내줘야 함!
// client에서 이미지 파일의 텍스트를 받으면 server 측에서는 그 텍스트를 파일로 저장하기

const upload = multer({
  // storage: client 측에서 받은 텍스트를 어디에 저장할 것인지 지정하는 역할을 수행함!
  // 우리가 컴퓨터에서 데이터를 저장하는 공간이 총 2개가 있는데 바로 "하드 디스크"와 "메모리"이다!
  // "storage"는 하드 디스크에 저장할지 혹은 메모리에 저장할지를 지정하는 역할을 함!
  storage: multer.diskStorage({
    // destination: 하드 디스크의 어느 공간에 저장할지, 즉 어떤 디렉토리에 저장할지를 지정해주는 역할을 함
    destination: (req, file, done) => {
      // done: 결과값을 어떻게 할지, 즉 비동기 코드가 언제 끝나게 할지를 설정하는 메서드
      done(null, "uploads/");
    },
    filename: (req, file, done) => {
      // path: Node.js의 내장 모듈
      // 1.jpg
      // 1_timestamp.jpg
      // file.originalname: 해당 파일명을 가져옴
      // extname: 확장자 명만 뽑아오는 메서드
      const ext = path.extname(file.originalname); // .jpg
      // basename: 첫번째 인수값(file.originalname)에서 두번째 인수값(ext)을 빼버리는 메서드
      const basename = path.basename(file.originalname, ext);
      const filename = `${basename}_${Date.now()}${ext}`;
      done(null, filename);
    },
  }),
  limits: { fileSize: 5 * 1024 * 1024 }, // 1024 byte는 1KB이고, 1KB가 1024개 있으면 1MB(메가 바이트)이고, 최종적으로 1MB가 5개 있으므로 5MB이다!
});
// console.log(multer);

// console.log(upload);
// console.log(upload.__proto__);

module.exports = upload;

// 1 byte = 8 bit
// 1KB = 1024 byte
// 1MB = 1024KB

 

 

 

 

 

1-1-2) front

server.js

const express = require("express");
const nunjucks = require("nunjucks");
const app = express();

app.set("view engine", "html");
nunjucks.configure("views", {
  express: app,
});

app.get("/", (req, res) => {
  res.render("index.html");
});

app.listen(3005, () => {
  console.log("front server start");
});

 

 

views/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  </head>
  <body>
    <form id="frm">
      <input type="text" name="subject" />
      <input type="file" name="upload" />
      <button type="submit">전송</button>
    </form>

    <script type="text/javascript">
      const frm = document.querySelector("#frm");
      frm.addEventListener("submit", async (e) => {
        e.preventDefault();

        // const { subject, upload } = e.target;

        // FormData: body 영역을 만들어줌!
        const body = new FormData(e.target);
        body.append("name", "sangbeom");
        const response = await axios.post(
          "http://localhost:3000/single",
          body,
          {
            headers: {
              ["Content-type"]: "multipart/form-data",
            },
          }
        );
        console.log(response);
      });
    </script>
  </body>
</html>