백앤드/Node.js

[포스코x코딩온] 웹개발자 입문 과정 5주차 | 파일 업로드

영최 2023. 4. 3. 15:12
728x90

1.파일 업로드 방법

 보통 post 방식으로 데이터를 받을 경우 'body-parser'을 이용해서 req.body로 데이터를  전송하지만

 파일 업로드 시에는 req.body로 파일을 전송 받거나 전송할 수 없다. 

 즉, 멀티파트 데이터(이미지, 동영상, 파일)를 처리하지 못한다. 

 따라서 다른 방식으로 파일 정보를 받는 방법이 필요한데, 그게 바로 'multer'라는 미들 웨어를 사용해야한다.

 multer은 내장되지 않았기때문에 별도로 설치해야한다.

2.multer

 가.설치 

npm install multer

 

 나.서버단 설정(app.js)

//multer 불러오기
const multer = require("multer");

//path: 파일, 폴더 경로를 쉽게 설정하게 함
//파일 업로드 경로 설정 = 서버 저장 경로 설정
const path = require("path");

//파일 업로드할 경로 설정
app.use("/uploads", express.static(__dirname + "/uploads"));

 multer 사용시 추가되는 코드이다. 먼저 multer를 불러온 후 path를 설정한다.

 path를 설정하면 파일의 path를 불러와서

 세부 설정 시 저장되는 파일 이름을 사용자가 변경할 수 있게 한다.  

 파일 업로드할 경로 설정을 하고

 multer에서 기본 설정시 dest, 세부설정시 destination의 done에서 동일한 경로로 설정하면

 파일이 업로드 될 경로인 '/upload' 파일이 새로 생성되고, 해당 경로에 파일이 저장된다.

 

 multer 기본 설정 & 세부설정 방법(택 1)

// 1-1. 세부설정 없이 multer 사용할 때(파일 이름 변경 안할 때)
const upload = multer({
  dest: "uploads/", 
});

//뒤에 'uploads/'슬래시: 폴더를 의미함
//dest 키: 파일이 저장될 경로를 지정

//1-2. multer 세부설정(파일 이름 변경)
const uploadDetail = 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);
    },
  }),
  limits: { fileSize: 5 * 1024 * 1024 },
});

//destination: 경로 설정
//done(error 처리, destination 경로 지정)
//path.extname(file.originalname):
//file.originalname에서 '확장자' 추출
// 가정) apple.png 파일 업로드 => 'png' 추출
//path.basename(file.originalname, ext) + Date.now() + ext:
//[파일명 + 현재시간.확장자] 형식으로 파일 업로드
//path.basename(file.originalname,ext): 파일이름에서 확장자 제거 => 'apple'추출
//Date.now() => 현재시간(1680309598964)
//1680309598964: 1970년 1월 1일 0시 0분 0초를 기준으로 현재까지 경과된 밀리초
//limits: { fileSize: 5 * 1024 * 1024 }: 5MB로 제한
//5 * 2^10 = 5KB
//5 * 2*10 * 2^10 = 5MB

 

다.multer 파일업로드 방법 세가지

 form 속성에서 enctype="multipart/form-data"를 반드시 설정해야한다. 

 multer는 multipart/form-data가 아닌 폼에서 동작하지 않기 때문이다.

  1)single(): 하나의 파일 업로드

   - 클라이언트(index.ejs)

    <h2>하나의 input에 하나의 파일 업로드</h2>
    <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>

  - 서버(app.js) 

    single()사용 시 파일 정보는 req.file에 존재한다.

    single 안에 매개변수는 form 내 input의 name과 일치한다.

app.post("/upload", uploadDetail.single("userfile"), (req, res) => {
  //업로드한 파일 정보는 req.file에 존재 (req.body에 없고)
  console.log(req.file);
  //폼 정보 req.body에 존재
  console.log(req.body);
  res.send("upload 완료~!!");
});

  2)array(): 한번에 여러개 파일 업로드

  - 클라이언트(index.ejs)

    input 속성 내에 'multiple' 속성을 넣어주어야 여러개 파일을 업로드 가능하다.

    <h2>하나의 input에 여러개의 파일 업로드</h2>
    <form action="/upload/array" method="POST" enctype="multipart/form-data">
      <!-- input type="file"일 때 multiple 속성을 지정해야 여러개 업로드 가능 -->
      <input type="file" name="userfile" multiple /><br />
      <input type="text" name="title" /><br />
      <button type="submit">업로드</button>
    </form>

  - 서버(app.js) 

    array()사용 시 파일 정보는 req.files에 존재한다.

//2-2. array() 한번에 여러개 업로드
app.post("/upload/array", uploadDetail.array("userfile"), (req, res) => {
  //array 일 때 req.file's' 로 file 받음
  console.log(req.files); //[{}, {}, {}] 3개 올렸을 때
  console.log(req.body); // {title: 'xxx'}
  res.send("한번에 여러개 파일 업로드 완료!");
});

  3)feilds(): 여러 input에 각각 파일 업로드하기

  - 클라이언트(index.ejs)

    <h2>여러 input에 각각 파일 업로드</h2>
    <form action="/upload/fields" method="POST" enctype="multipart/form-data">
      <!-- input type="file"일 때 multiple 속성을 지정해야 여러개 업로드 가능 -->
      <input type="file" name="userfile1" /><br />
      <input type="text" name="title1" /><br />
      <input type="file" name="userfile2" /><br />
      <input type="text" name="title2" /><br />
      <button type="submit">업로드</button>
    </form>

  - 서버(app.js) 

    feilds()사용 시 파일 정보는 req.files에 존재한다.

//2-3. fields() 여러 input에 각각 파일 업로드
app.post(
  "/upload/fields",
  // [{ name: "userfile1" }, { name: "userfile2" }]: input 개수 만큼 어레이 넣어야함
  uploadDetail.fields([{ name: "userfile1" }, { name: "userfile2" }]),
  (req, res) => {
    //fields 일 때 파일 여러개니까 req.file's' 로 file 받음
    console.log(req.files); //{userfile1: [{}], userfile2: [{}]} 형태로 파일 정보 출력
    console.log(req.body); //{title1: '...', title2: '...'}
    res.send("각각 여러개 파일 업로드 완료!");
  }
);

 

 라.Axios 동적 파일 업로드 방법

  - 클라이언트(index.ejs)

    FormData()객체를 사용해서 동적 파일을 전송한다.

    <h2>동적 파일 업로드</h2>
    <p>페이지 변환하지 않고, 현재 페이지에서 파일 업로드 결과 확인하기</p>
    <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() {
        console.log("click upload btn!");
        //FormData란? form 태그의 데이터를 동적으로 제어가능한 기능
        //axios/ajax ..등등과 함께 사용됨
        //동적 파일 전송시 FormData사용해야함
        //페이지 전환없이 "현재 페이지"에서 동적으로 파일 업로드하고 싶을 때 사용됨
        const formData = new FormData();
        const fileInput = document.getElementById("dynamic-file");

        console.dir(fileInput);
        console.dir(fileInput.files);
        console.dir(fileInput.files[0]);

        // formData.append(key, value)
        // input의 name 값이 'dynamic-userfile'인 요소에 fileInput.files[0] 데이터가 입력되었음
        formData.append("dynamic-userfile", fileInput.files[0]);

        axios({
          url: "/dynamicFile",
          method: "POST",
          data: formData,
          headers: {
            "Content-Type": "multipart/form-data", // enctype="multipart/form-data"
          },
        }).then((response) => {
          console.log(response);
          console.log(response.data.path);
          const imgElem = document.querySelector(".img-box");
          imgElem.src = `/${response.data.path}`;
          imgElem.classList.add("profile");
        });
      }
    </script>

  - 서버(app.js) 

//3 동적 파일 업로드
app.post(
  "/dynamicFile",
  uploadDetail.single("dynamic-userfile"),
  (req, res) => {
    //요청의 파일 정보 확인
    console.log(req.file);
    //클라이언트에게 파일을 응답
    res.send(req.file);
  }
);

 

 마.파일 업로드 방법 전체 코드

  - 클라이언트(index.ejs)

<!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>파일 업로드</title>
    <!-- axios CDN -->
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <style>
      .profile {
        width: 200px;
        height: 200px;
        border-radius: 50%;
        box-shadow: 0 0 20px #0002;
      }
    </style>
  </head>

  <body>
    <h1>파일 업로드를 배워보자</h1>

    <!-- 주의) multer 는 multipart(enctype="multipart/form-data")가 아닌 폼에서는 동작하지 않음!! -->
    <h2>하나의 input에 하나의 파일 업로드</h2>
    <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>

    <h2>하나의 input에 여러개의 파일 업로드</h2>
    <form action="/upload/array" method="POST" enctype="multipart/form-data">
      <!-- input type="file"일 때 multiple 속성을 지정해야 여러개 업로드 가능 -->
      <input type="file" name="userfile" multiple /><br />
      <input type="text" name="title" /><br />
      <button type="submit">업로드</button>
    </form>

    <h2>여러 input에 각각 파일 업로드</h2>
    <form action="/upload/fields" method="POST" enctype="multipart/form-data">
      <!-- input type="file"일 때 multiple 속성을 지정해야 여러개 업로드 가능 -->
      <input type="file" name="userfile1" multiple /><br />
      <input type="text" name="title1" /><br />
      <input type="file" name="userfile2" multiple /><br />
      <input type="text" name="title2" /><br />
      <button type="submit">업로드</button>
    </form>

    <h2>동적 파일 업로드</h2>
    <p>페이지 변환하지 않고, 현재 페이지에서 파일 업로드 결과 확인하기</p>
    <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() {
        console.log("click upload btn!");
        //FormData란? form 태그의 데이터를 동적으로 제어가능한 기능
        //axios/ajax ..등등과 함께 사용됨
        // 동적 파일 전송시 FormData사용해야함
        // 페이지 전환없이 "현재 페이지"에서 동적으로 파일 업로드하고 싶을 때 사용됨
        const formData = new FormData();
        const fileInput = document.getElementById("dynamic-file");

        console.dir(fileInput);
        console.dir(fileInput.files);
        console.dir(fileInput.files[0]);

        // formData.append(key, value)
        // input의 name 값이 'dynamic-userfile'인 요소에 fileInput.files[0] 데이터가 입력되었음
        formData.append("dynamic-userfile", fileInput.files[0]);

        axios({
          url: "/dynamicFile",
          method: "POST",
          data: formData,
          headers: {
            "Content-Type": "multipart/form-data", // enctype="multipart/form-data"
          },
        }).then((response) => {
          console.log(response);
          console.log(response.data.path);
          const imgElem = document.querySelector(".img-box");
          imgElem.src = `/${response.data.path}`;
          imgElem.classList.add("profile");
        });
      }
    </script>
  </body>
</html>

  - 서버(app.js) 

const express = require("express");
const app = express();
const PORT = 8000;

//multer 미들웨어 사용하기
//multer 불러오기
const multer = require("multer");
//path 불러오기(내장 모듈)
//path: 파일, 폴더 경로를 쉽게 설정하게 함
const path = require("path");
//파일 업로드 경로 설정 = 서버 저장 경로 설정
//dest 키: 파일이 저장될 경로를 지정

// 1-1. 세부설정 없이 multer 사용할 때(파일 이름 변경 안할 때)
const upload = multer({
  dest: "uploads/", //뒤에 'uploads/'슬래시: 폴더를 의미함
});

//1-2. multer 세부설정(파일 이름 변경)
const uploadDetail = multer({
  storage: multer.diskStorage({
    //destination: 경로 설정
    destination(req, file, done) {
      //done(error 처리, destination 경로 지정)
      done(null, "uploads/");
    },
    filename(req, file, done) {
      //file.originalname에서 '확장자' 추출
      // 가정) apple.png 파일 업로드 => 'png' 추출
      const ext = path.extname(file.originalname);
      //[파일명 + 현재시간.확장자] 형식으로 파일 업로드
      //path.basename(file.originalname,ext): 파일이름에서 확장자 제거 => 'apple'추출
      //Date.now() => 현재시간(1680309598964)
      //1970년 1월 1일 0시 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 },
});

app.set("view engine", "ejs");
app.use("/views", express.static(__dirname + "/views"));
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use("/uploads", express.static(__dirname + "/uploads")); //경로 의미 앞에 '/uploads'슬래시

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

//2-1. single() 업로드
//.single(): 하나의 파일 업로드 할 때 사용
//.single()의 매개변수는 input의 name과 일치시킴
//<input type="file" name="userfile" />
app.post("/upload", uploadDetail.single("userfile"), (req, res) => {
  //업로드한 파일 정보는 req.file에 존재 (req.body에 없고)
  console.log(req.file);
  // -- 1-1 출력 결과 --
  //   {
  //     fieldname: 'userfile', // 폼에 정희한 name
  //     originalname: 'hamster1.jpeg', // 사용자가 업로드한 파일명
  //     encoding: '7bit', // 파일 인코딩 타입
  //     mimetype: 'image/jpeg',  //파일 mime 타입
  //     destination: 'uploads/', // 파일 업로드할 경로(폴더)
  //     filename: '1369e9cb106e385bfa0d42a16f5655d1', // destination에 저장된 파일 명
  //     path: 'uploads/1369e9cb106e385bfa0d42a16f5655d1', //업로드된 파일의 전체 경로
  //     size: 5059 //파일 크기(byte)
  //   }
  //폼 정보 req.body에 존재
  console.log(req.body);
  // -- 1-1 출력 결과 --
  //   [Object: null prototype] { title: '첫번째 햄스터' } //input title에 입력한 값
  res.send("upload 완료~!!");
});

//2-2. array() 한번에 여러개 업로드
app.post("/upload/array", uploadDetail.array("userfile"), (req, res) => {
  //array 일 때 req.file's' 로 file 받음
  console.log(req.files); //[{}, {}, {}] 3개 올렸을 때
  console.log(req.body); // {title: 'xxx'}
  res.send("한번에 여러개 파일 업로드 완료!");
});

//2-3. fields() 여러 input에 각각 파일 업로드
app.post(
  "/upload/fields",
  // [{ name: "userfile1" }, { name: "userfile2" }]: input 개수 만큼 어레이 넣어야함
  uploadDetail.fields([{ name: "userfile1" }, { name: "userfile2" }]),
  (req, res) => {
    //fields 일 때 파일 여러개니까 req.file's' 로 file 받음
    console.log(req.files); //{userfile1: [{}], userfile2: [{}]} 형태로 파일 정보 출력
    console.log(req.body); //{title1: '...', title2: '...'}
    res.send("각각 여러개 파일 업로드 완료!");
  }
);

//3 동적 파일 업로드
app.post(
  "/dynamicFile",
  uploadDetail.single("dynamic-userfile"),
  (req, res) => {
    //요청의 파일 정보 확인
    console.log(req.file);
    //클라이언트에게 파일을 응답
    res.send(req.file);
  }
);

app.listen(PORT, () => {
  console.log(`http://localhost:${PORT}`);
});

 

728x90