multer (23/01/25)

nazzzo·2023년 1월 25일
0
post-thumbnail

이미지 업로드

파일 업로드를 위한 Multer 라이브러리 사용법을 정리합니다


1. 라이브러리 설치

npm init -y
npm install express cors nunjucks
npm install multer

백엔드 디렉토리에 multer 라이브러리를 설치합니다


2. 미들웨어 생성


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



3. 파일 업로드


파일 업로드에는 다음과 같은 메서드를 사용할 수 있습니다

  • single : 업로드할 인풋 박스가 하나뿐일 경우 하나의 파일만 업로드합니다
  • array : 하나의 인풋박스를 통해서 파일 여러개를 업로드할 수 있습니다
  • uploads : 인풋 박스 여러개를 사용해서 여러 파일을 업로드합니다 (배열로 반환)

3-1. Backend

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">


3-2. Frontend


[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을 위와 같은 형식으로 고쳐야 합니다



4. 실습


4-1. Backend

미들웨어 생성

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'))



4-2. Frontend

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 엘리먼트로 전달됩니다


0개의 댓글