๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ‘จโ€๐Ÿ’ป 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>

๊ฒฐ๊ณผ ํ™•์ธ

์ฐธ๊ณ  ์ž๋ฃŒ

๋ฐ˜์‘ํ˜•

๐Ÿ’ฌ ๋Œ“๊ธ€