λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ‘¨‍πŸ’» web.dev/node

multer λͺ¨λ“ˆμ„ ν™œμš©ν•œ 이미지 파일 μ—…λ‘œλ“œ νŠœν† λ¦¬μ–Ό

by HandHand 2021. 3. 5.

κ²Œμ‹œνŒμ— 이미지 μ—…λ‘œλ“œ κΈ°λŠ₯ μΆ”κ°€ν•˜κΈ°

multer λͺ¨λ“ˆ

multer λͺ¨λ“ˆμ€ multipart/form-data λ₯Ό μ²˜λ¦¬ν•˜κΈ° μœ„ν•œ node.js λ―Έλ“€μ›¨μ–΄μž…λ‹ˆλ‹€.
이번 ν¬μŠ€νŒ…μ—μ„œλŠ” μ§€λ‚œ ν¬μŠ€νŒ…μ— μ΄μ–΄μ„œ node.js μ—μ„œ 이미지(파일)λ₯Ό μ—…λ‘œλ“œν•˜λŠ” 방법에 λŒ€ν•΄ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

ν˜„μž¬ κ²Œμ‹œνŒ κΈ€μ“°κΈ° ν•„λ“œμ—λŠ” λ‹€μŒκ³Ό 같이 이미지 μ—…λ‘œλ“œ κΈ°λŠ₯이 μ—†λŠ” μƒνƒœλΌμ„œ multer λͺ¨λ“ˆμ„ 톡해
κ²Œμ‹œκΈ€λ§ˆλ‹€ 이미지λ₯Ό μ—…λ‘œλ“œν•˜κ³  λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯ν•˜λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€.

νŒ¨ν‚€μ§€ μ„€μΉ˜

$ npm i multer

MySQL 컬럼 μΆ”κ°€

기쑴의 κ²Œμ‹œκΈ€ ν…Œμ΄λΈ”μ€ λ‹€μŒκ³Ό 같은 ν˜•νƒœλ₯Ό 띄고 μžˆμŠ΅λ‹ˆλ‹€.

mysql> desc board;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| idx        | int unsigned | NO   | PRI | NULL    | auto_increment |
| creator_id | varchar(100) | NO   |     | NULL    |                |
| title      | varchar(100) | NO   |     | NULL    |                |
| content    | mediumtext   | NO   |     | NULL    |                |
| passwd     | varchar(100) | NO   |     | NULL    |                |
| hit        | int unsigned | NO   |     | 0       |                |
+------------+--------------+------+-----+---------+----------------+

이제 각 κ²Œμ‹œκΈ€λ§ˆλ‹€ 이미지λ₯Ό μΆ”κ°€μ μœΌλ‘œ κ΄€λ¦¬ν•΄μ•Όν•˜λ―€λ‘œ 이미지 경둜λ₯Ό μ €μž₯ν•  μ»¬λŸΌμ„ μƒˆλ‘œ λ§Œλ“€μ–΄μ€λ‹ˆλ‹€.
λ°”μ΄λ„ˆλ¦¬ λ°μ΄ν„°λ‘œ DB에 μ €μž₯ν•  경우 λΆ€ν•˜κ°€ 크기 λ•Œλ¬Έμ—
이미지 경둜만 μ €μž₯해놓고 μ„œλ²„μ—μ„œ λ‘œλ“œν•΄μ„œ μ œκ³΅ν•΄μ£ΌλŠ” ν˜•νƒœλ‘œ ν•œ κ²ƒμž…λ‹ˆλ‹€.

mysql> ALTER TABLE board ADD image VARCHAR(200) NOT NULL DEFAULT '';

이제 λ‹€μ‹œ κ²Œμ‹œκΈ€ ν…Œμ΄λΈ”μ„ 확인해보면 λ‹€μŒκ³Ό 같이 μƒˆλ‘œμš΄ 컬럼이 μΆ”κ°€λœ 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

mysql> desc board;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| idx        | int unsigned | NO   | PRI | NULL    | auto_increment |
| creator_id | varchar(100) | NO   |     | NULL    |                |
| title      | varchar(100) | NO   |     | NULL    |                |
| content    | mediumtext   | NO   |     | NULL    |                |
| passwd     | varchar(100) | NO   |     | NULL    |                | 
| hit        | int unsigned | NO   |     | 0       |                |
| image      | varchar(200) | NO   |     |         |                | <== πŸ’‘
+------------+--------------+------+-----+---------+----------------+

파일 μ—…λ‘œλ“œ 폼 생성

이제 기쑴의 κΈ€ μž‘μ„± νΌμ—μ„œ 이미지 μ—…λ‘œλ“œλ₯Ό μœ„ν•œ 파일 선택 창을 μƒˆλ‘œ μΆ”κ°€ν•΄μ€λ‹ˆλ‹€.
form νƒœκ·Έμ˜ μ†μ„±μœΌλ‘œ enctype="multipart/form-data" κ°€ μ§€μ •λ˜μ–΄μ•Ό 함에 μœ μ˜ν•©λ‹ˆλ‹€.

write.ejs

<form action="/board/write" method="post" enctype="multipart/form-data">
  <table border="1">
    <!-- μ€‘λž΅ -->
    <tr>
      <td>이미지</td>
      <td><input type="file" name="image" /></td>
    </tr>
    <tr>
      <td colspan="2">
        <button type="submit">κΈ€μ“°κΈ°</button>
      </td>
    </tr>
  </table>
</form>

router & multer μ„€μ •

κ²Œμ‹œκΈ€ μ—…λ‘œλ“œλ₯Ό λ‹΄λ‹Ήν•˜λŠ” 라우트 νŒŒμΌμ— κ°€μ„œ λͺ‡ 가지 섀정을 ν•΄μ€λ‹ˆλ‹€.

파일이 μ €μž₯될 κ²½λ‘œλ‚˜ 파일λͺ…을 λ³€κ²½ν•΄μ£ΌκΈ° μœ„ν•΄μ„œλŠ” multer 객체의 μ˜΅μ…˜μ„ λ³€κ²½ν•΄μ£Όλ©΄ λ©λ‹ˆλ‹€.
μ—¬κΈ°μ„œλŠ” ν˜„μž¬ λ‚ μ§œλ₯Ό 파일λͺ…에 μΆ”κ°€ν•˜μ—¬ μ€‘λ³΅λœ 사진이 μƒμ„±λ˜λŠ” 것을 λ°©μ§€ν•˜λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€.
이미지 μ—…λ‘œλ“œλ₯Ό λ‹΄λ‹Ήν•˜λŠ” λΌμš°ν„° 파일 상단에 λ‹€μŒκ³Ό 같은 μ½”λ“œλ₯Ό 톡해 섀정을 ν•΄μ€λ‹ˆλ‹€.
express ν”„λ‘œμ νŠΈ 생성 μ‹œ 기본적으둜 μ œκ³΅λ˜λŠ” public/images 디렉토리 ν•˜μœ„μ— 이미지듀을 μ €μž₯해주도둝 ν•˜κ² μŠ΅λ‹ˆλ‹€.

routes/board.js

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

var storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, "public/images/");
  },
  filename: function (req, file, cb) {
    const ext = path.extname(file.originalname);
    cb(null, path.basename(file.originalname, ext) + "-" + Date.now() + ext);
  },
});

var upload = multer({ storage: storage });

λ˜ν•œ κ²Œμ‹œκΈ€ ν•˜λ‚˜μ—λŠ” ν•˜λ‚˜μ˜ 이미지가 μ‘΄μž¬ν•˜λ―€λ‘œ upload.single('image') 둜
이미지 처리λ₯Ό μœ„ν•œ 미듀웨어λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
μ—¬κΈ°μ„œ image λŠ” input νƒœκ·Έμ˜ name μ†μ„±κ°’μž…λ‹ˆλ‹€.
μ•„κΉŒμ™€ λ™μΌν•˜κ²Œ board.js 의 파일 μ—…λ‘œλ“œλ₯Ό λ‹΄λ‹Ήν•˜λŠ” λΌμš°ν„°μ— 미듀웨어λ₯Ό μ„€μ •ν•΄μ€λ‹ˆλ‹€.

routes/board.js

// ... μ€‘λž΅

// 파일 μ—…λ‘œλ“œ λΌμš°ν„°
router.post("/write", upload.single("image"), (req, res, next) => {
  const creator_id = req.body.creator_id;
  const title = req.body.title;
  const content = req.body.content;
  const passwd = req.body.passwd;
  const image = `/images/${req.file.filename}`; // image 경둜 λ§Œλ“€κΈ°
  const datas = [creator_id, title, content, passwd, image];

  const sql =
    "INSERT INTO board(creator_id, title, content, passwd, image) values(?, ?, ?, ?, ?)";
  connection.query(sql, datas, (err, rows) => {
    if (err) {
      console.error("err : " + err);
    } else {
      console.log("rows: " + JSON.stringify(rows));

      res.redirect("/board");
    }
  });
});

이미지 μ €μž₯ 확인

이제 터미널을 μ—΄κ³  μƒˆλ‘œμš΄ κ²Œμ‹œκΈ€μ„ μž‘μ„±ν•œ λ’€ 쿼리문을 톡해 λ ˆμ½”λ“œλ₯Ό 확인해보면
λ‹€μŒκ³Ό 같이 이미지 κ²½λ‘œκ°€ μ €μž₯된 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

기쑴에 이미지가 μ—†λŠ” κ²Œμ‹œκΈ€λ“€μ€ 기본값이 μ„€μ •λ˜μ–΄ μžˆλŠ” 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

mysql> select * from board;
+-----+------------+----------------------+---------------------+--------+-----+--------------------------------
| idx | creator_id | title                | content             | passwd | hit | image                         |
+-----+------------+----------------------+---------------------+--------+-----+--------------------------------
|  12 | simpson    | springfield          | κ²Œμ‹œκΈ€ μž‘μ„±            | 1234   | 0 |                                 |
|  15 | apple      | lenna                | lenna test          | 1234   | 0 | /images/lenna-1589908536559.png |
+-----+------------+----------------------+---------------------+--------+-----+--------------------------------

λ˜ν•œ 둜컬 ν™˜κ²½μ— 이미지가 μ˜¬λ°”λ₯΄κ²Œ μ €μž₯λ˜μ—ˆλŠ”μ§€ ν™•μΈν•΄λ΄…μ‹œλ‹€.

public/images/ ν•˜μœ„μ— λ‹€μŒκ³Ό 같이 μ €μž₯된 μ‹œκ°„κ³Ό ν•¨κ»˜
μƒˆλ‘œμš΄ 파일λͺ…을 λ§Œλ“  λ’€ μ €μž₯λ˜μ–΄μžˆλŠ” 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

κ²Œμ‹œκΈ€ 이미지 좜λ ₯

ν˜„μž¬λŠ” κ²Œμ‹œκΈ€ 쑰회 μ‹œ μ €μž₯된 이미지λ₯Ό 좜λ ₯ν•˜μ§€ λͺ»ν•˜λŠ” μƒνƒœμž…λ‹ˆλ‹€.

이제 κ²Œμ‹œκΈ€ 쑰회 μ‹œ 이미지λ₯Ό 같이 좜λ ₯해쀄 수 μžˆλ„λ‘ 기쑴의 λΌμš°ν„°λ₯Ό μˆ˜μ •ν•˜κ² μŠ΅λ‹ˆλ‹€.
κΈ°μ‘΄ 쿼리문의 μš”μ²­ ν•„λ“œμ— image λ₯Ό μΆ”κ°€ν•΄μ€λ‹ˆλ‹€.

routes/board.js

router.get("/read/:idx", (req, res, next) => {
  const idx = req.params.idx;
  const sql =
    "SELECT idx, creator_id, title, content, hit, image FROM board WHERE idx=?";
  connection.query(sql, [idx], (err, row) => {
    if (err) {
      console.error(err);
    } else {
      res.render("read", { title: "κΈ€ 쑰회", row: row[0] });
    }
  });
});

그리고 ejs νŒŒμΌμ—μ„œ img νƒœκ·Έλ₯Ό 톡해 정적 νŒŒμΌμ„ 화면에 좜λ ₯ν•˜λ„λ‘ ν•©λ‹ˆλ‹€.
μ΄λ•Œ 이미지 μ†ŒμŠ€λŠ” μ„œλ²„λ‘œλΆ€ν„° 전달받은 row κ°μ²΄μ—μ„œ κ°€μ Έμ˜€λ„λ‘ ν•©λ‹ˆλ‹€.

read.ejs

<table>
  <!-- μ€‘λž΅ -->
  <tr>
    <td>이미지</td>
    <td><img src="<%= row.image %> " alt="이미지" width="300" /></td>
  </tr>
</table>

κ²°κ³Ό 확인

참고 자료

λ°˜μ‘ν˜•

πŸ’¬ λŒ“κΈ€