본문 바로가기

Node.js

Node.js(14) - back 댓글 CRUD 구현, 모듈 및 class를 활용한 댓글 front 화면 구현

728x90
반응형

1) Node.js

   1-1) 댓글 CRUD 구현 - back

   1-2) 댓글 front 화면 구현(CRUD) - 모듈 및 class 활용

 

 

 

 

 

 

 

 

1) Node.js

1-1) 댓글 CRUD 구현 - back

back

server.js

const express = require("express");
const cors = require("cors");
const app = express();
const mysql = require("./models");

app.use(express.urlencoded({ extended: false }));
app.use(express.json());
app.use(
  cors({
    origin: true,
    credentials: true,
  })
);

/*
`id`
`userid`
`content`
`register`
*/

app.get("/comments", async (req, res, next) => {
  try {
    const [response] = await mysql.query(
      `SELECT id, userid, content, DATE_FORMAT(register,'%Y-%m-%d') as register FROM Comment`
    );
    res.json(response);
  } catch (e) {
    next(e);
  }
});

app.post("/comments", async (req, res, next) => {
  try {
    const userid = `web7722`;
    const { content } = req.body;

    // if (!userid) throw new Error("userid가 없습니다.");
    if (!content) throw new Error("content 없음!");

    const sql = `INSERT INTO Comment(userid, content) VALUES('${userid}','${content}')`;
    const [{ insertId }] = await mysql.query(sql);
    const [[response]] = await mysql.query(
      `SELECT id, userid, content, DATE_FORMAT(register,'%Y-%m-%d') as register FROM Comment WHERE id=${insertId}`
    );
    res.json(response);
  } catch (e) {
    next(e);
  }
});

app.get("/comments/:id", async (req, res, next) => {
  try {
    const { id } = req.params;
    const [[response]] = await mysql.query(
      `SELECT id, userid, content, DATE_FORMAT(register,'%Y-%m-%d') as register FROM Comment WHERE id=${id}`
    );
    res.json(response);
  } catch (e) {
    next(e);
  }
});

/*
[
  {
    "fieldCount": 0,
    "affectedRows": 1,
    "insertId": 0,
    "info": "Rows matched: 1 Changed: 1 Warnings: 0",
    "serverStatus": 2,
    "warningStatus": 0,
    "changedRows": 1
  },
  null
]
*/

app.put("/comments/:id", async (req, res, next) => {
  try {
    const { id } = req.params; // DB 식별자
    const { content } = req.body; // client한테 받은 데이터

    const [{ changedRows }] = await mysql.query(
      `UPDATE Comment SET content='${content}' WHERE id='${id}'`
    );

    if (changedRows <= 0)
      throw new Error("수정된 데이터가 없습니다. id를 다시 확인해주세요!!");
    res.json({ result: changedRows });
  } catch (e) {
    next(e);
  }
});

// CRUD
// R --> SELECT
// C --> INSERT INTO
// U --> UPDATE SET
// D --> DELETE

/*
[
  {
    "fieldCount": 0,
    "affectedRows": 0,
    "insertId": 0,
    "info": "",
    "serverStatus": 2,
    "warningStatus": 0
  },
  null
]
*/

app.delete("/comments/:id", async (req, res, next) => {
  try {
    const { id } = req.params;
    const [{ affectedRows }] = await mysql.query(
      `DELETE FROM Comment WHERE id=${id}`
    );

    // affectedRows의 데이터 타입은 "Number"이다!
    if (affectedRows <= 0)
      throw new Error("삭제된 데이터가 없습니다. id를 다시 확인해주세요!!");
    res.json({ result: affectedRows });
  } catch (e) {
    next(e);
  }
});

app.use((err, req, res, next) => {
  res.status(500).json({
    message: err.message,
  });
});

app.listen(3000, () => {
  console.log(`server start`);
});

 

models/index.js

const mysql = require("mysql2");

const pool = mysql
  .createPool({
    host: "127.0.0.1",
    port: "3306",
    user: "root",
    password: "본인 MySQL 비밀번호",
    database: "comments",
  })
  .promise();

module.exports = pool;

 

 

 

1-2) 댓글 front 화면 구현(CRUD) - 모듈 및 class 활용

모듈
webpack: "webpack"은 React 배울 때 필수사항이다!!

Node.js에서 자체 개발한 것이 바로 "CommonJS 모듈 시스템"이기에

대부분의 Javascript 코드에서 "require()", "module.exports"를 사용함!(안정성이 좋음!)

module
CommonJS 모듈 시스템: require(), module.exports

  • 파일을 받아올 때는 "require()"를 사용함
  • 파일을 보낼 때는 "module.exports"를 사용함

ES6, 7 모듈(신문법)

  • import ... from ...
  • export default {}
import express from 'express'
const express = require('express')

import mysql from './models/index.js'
const mysql = require('./models/index.js')

module.exports = {...}
export default {}

exports.add = () => {}
export const add = () => {}

 

 

class
class로 화면을 표현하는 것을 배워야 한다!
이유1) class를 작성하는 능력을 키우기 위함이다!
이유2) React 때 쉬어진다!
이유3) 디자인 패턴을 어느 정도 배울 수 있게 된다!(이를 통해 디자인 패턴이 어떤 용도로 사용되는지 알 수 있음!)

--> 22가지로 여러가지 패턴을 다 구현할 수 있다!

express도 class로 바꿔서 하는 것이 가능해진다!

 

 

+) switch 문

if (boolean) {};

const test = 'web7722';

switch(test) {
  case "sangbeom":
    // code block
    break;
  case "sang":
    // code block
    break;
  default:
    // code block
    break;
}

switch(test) {
  case "web7722":
  case "web":
  case "7722":
    // code block
    console.log('hello');
    break;
  case "sangbeom":
  case "sang":
  case "hello":
    // code block
    console.log('world');
    break;
  default:
    console.log(null);
    break;
}

 

 

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.use(express.static("public"));
app.use(express.json());

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

app.listen(3005, () => {
  console.log("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>
    <link href="/css/index.css" rel="stylesheet" />
  </head>
  <body>
    <div>
      <ul class="comment">
        <li class="comment-form">
          <form id="commentFrm">
            <h4>
              댓글쓰기
              <span></span>
            </h4>
            <span class="ps_box">
              <input
                type="text"
                placeholder="댓글 내용을 입력해주세요."
                class="int"
                name="content"
              />
            </span>
            <input type="submit" class="btn" value="등록" />
          </form>
        </li>
        <li id="comment-list"></li>
      </ul>
    </div>
    <template id="commentRow">
      <ul class="comment-row" data-index="1">
        <li class="comment-id"></li>
        <li class="comment-content"></li>
        <li class="comment-date"></li>
      </ul>
    </template>
    <template id="content-baisc">
      <span class="comment-update-btn">입력입력입력~~~</span>
      <span class="comment-delete-btn">❌</span>
    </template>
    <template id="content-update">
      <span>
        <input type="text" class="comment-update-input" data-value="" />
      </span>
      <span class="comment-delete-btn">❌</span>
    </template>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <!-- <script src="/js/comment.js" type="text/javascript"></script> -->
    <script src="/js/commentRequest.js" type="module"></script>
    <script src="/js/request.js" type="module"></script>
    <script src="/js/comment.js" type="module"></script>
    <script src="/js/index.js" type="module"></script>
  </body>
</html>

 

public/css/index.css

* {
  margin: 0;
  padding: 0;
}

ul,
li {
  list-style: none;
}

.comment {
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  padding: 30px;
  width: 600px;
  margin: 0 auto;
}

.comment > li {
  margin-top: 20px;
}

.comment > li:nth-child(1) {
  margin: 0px;
}

.comment-row {
  display: flex;
  justify-content: space-between;
  flex-direction: row;
}

.comment-row {
  margin-top: 20px;
  width: 100%;
}

.comment-row > li:nth-child(2) {
  flex-shrink: 0;
  flex-grow: 1;
  padding-left: 25px;
  z-index: 1;
  width: 100%;
}

.comment-row > li:nth-child(2) {
  width: 85px;
}

.comment-form > form {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: space-between;
}

.comment-form > form > h4 {
  width: 100%;
  margin: 14px 0 14px 0;
}

.comment-content {
  cursor: pointer;
  word-break: break-all;
  padding-right: 25px;
}

.ps_box {
  display: block;
  position: relative;
  width: 80%;
  height: 51px;
  border: solid 1px #dadada;
  padding: 10px 14px 10px 14px;
  background: #fff;
  box-sizing: border-box;
}

.ps_box > input {
  outline: none;
}

.int {
  display: block;
  position: relative;
  width: 100%;
  height: 29px;
  padding-right: 25px;
  line-height: 29px;
  border: none;
  background: #fff;
  font-size: 15px;
  box-sizing: border-box;
  z-index: 10;
}

.btn {
  width: 18%;
  padding: 18px 0 16px;
  text-align: center;
  box-sizing: border-box;
  text-decoration: none;
  border: none;
  background: #333;
  color: #fff;
  font-size: 14px;
}

.comment-delete-btn {
  display: inline-block;
  margin-left: 7px;
  cursor: pointer;
}

.comment-update-input {
  border: none;
  border-bottom: 1px solid #333;
  font-size: 16px;
  color: #666;
  outline: none;
}

 

public/js/request.js

/*
// "export"는 값을 객체로 넣는 형식이다!!
export const a = 10;
// 위의 코드는 "exports.a = 10"과 동일함!!

// "export default"는 값 자체를 넣는 형식이기에 한 번 밖에 못쓴다!!
export default (a, b) => {
  return a + b;
};
// 위의 코드는 "module.exports = (a, b) => {return a + b}"과 동일함!!
*/

// axios.get("http://localhost:3000/comments");

// "axios.create()"는 axios를 가지고 새로운 axios를 만든다는 의미이다!!
const request = axios.create({
  baseURL: "http://localhost:3000",
  withCredentials: true,
});

// request.get("/comments").then((data) => console.log(data));

export default request;

 

public/js/comment.js

/*
import sum, { a } from "/js/request.js";

console.log(a);
console.log(sum(1, 2));
console.log(axios);
*/

// import request from "/js/request.js";
// import request from "/js/commentRequest.js";

// request.get("/comments").then((data) => console.log(data));

// (async () => {
//   const response = await request.find();
//   console.log(response);
// })();

class Template {
  constructor(templates) {
    // this = {}
    // return this
    return this.create(templates);
  }
  create(templates) {
    let obj = {};
    for (const key in templates) {
      obj[key] = this.clone(templates[key]);
    }
    return obj;
  }

  clone(selector) {
    // const template = document.querySelector(selector);
    // const clone = document.importNode(template.content, true);
    // return clone;
    return () => {
      const template = document.querySelector(selector);
      const clone = document.importNode(template.content, true);
      return clone;
    };
  }
}

/*
// 생성자 함수
function Template1(templates) {
  //this = {}
  // return this

  this.create = (templates) => {
    let obj = {};
    for (const key in templates) {
      obj[templates[key]] = this.clone(`#${templates[key]}`);
    }
    return obj;
  };

  this.clone = (selector) => {
    const template = document.querySelector(selector);
    const clone = document.importNode(template.content, true);
    return clone;
  };

  return this.create(templates);
}
*/

const config = {
  row: "#commentRow",
  basic: "#content-baisc",
  update: "#content-update",
};

// const configArr = ["commentRow", "content-baisc", "content-update"];

const template = new Template(config);

// console.log(template.row === template.row); // true
// console.log(template.row() === template.row()); // false

export default template;

 

public/js/commentRequest.js

import request from "/js/request.js";

// 4개의 method를 만들 계획
class CommentRequest {
  constructor(request) {
    this.request = request;
  }

  async create(body) {
    try {
      const { data } = await this.request.post("/comments", body);
      return data;
    } catch (e) {
      return null;
    }
  }
  async find() {
    try {
      const { data } = await this.request.get("/comments");
      return data;
    } catch (e) {
      return null;
    }
  }
  async update(id, body) {
    try {
      const { data } = await this.request.put(`/comments/${id}`, body);
      return data;
    } catch (e) {
      return null;
    }
  }
  async delete(id) {
    try {
      const { data } = await this.request.delete(`/comments/${id}`);
      return data;
    } catch (e) {
      return null;
    }
  }
}

const req = new CommentRequest(request);
/*
const req = {
  request: axios(),
  create: () => {},
  find: () => {},
  update: () => {},
  delete: () => {}
}
*/

// req.create({ content: "hello world~~" }).then((data) => {
//   console.log(data);
// });

// (async () => {
//   const response1 = await req.create({ content: "hello world~~" });
//   console.log(response1);

//   const response2 = await req.find();
//   console.log(response2);

//   const response3 = await req.update(11, { content: "수정입니다~~" });
//   console.log(response3);

//   const response4 = await req.delete(33);
//   console.log(response4);
// })();

export default new CommentRequest(request);

 

public/js/index.js

import request from "/js/commentRequest.js";
import template from "/js/comment.js";

const basic = (content) => {
  const clone = template.basic();

  const span = clone.querySelector(".comment-update-btn");
  span.innerHTML = content;
  return clone;
};

const row = ({ id, userid, content, register }) => {
  const clone = template.row();

  const ul = clone.querySelector("ul");
  const li = clone.querySelectorAll("li");

  ul.dataset.index = id;
  li[0].innerHTML = userid;
  li[1].append(basic(content));
  li[2].innerHTML = register;

  return ul;
};

const drawing = (ul) => {
  const commentElement = document.querySelector("#comment-list");
  commentElement.prepend(ul);
};

const init = async () => {
  const list = await request.find();
  if (list === null || list.length === 0) return;

  for (const comment of list) {
    const element = row(comment);
    drawing(element);
  }
};

const submitHandler = async (e) => {
  e.preventDefault();
  const { content } = e.target;
  // console.log(content.value);

  const newComment = await request.create({ content: content.value });
  const newElement = row(newComment);
  drawing(newElement);

  e.target.reset();
  content.focus();
};

const changeBtn = (contentEle, before) => {
  const enterHandler = (e) => {
    if (e.keyCode !== 13) return;
    try {
      if (e.target.value === "") throw new Error("빈칸은 하지 마세요~~");
      if (e.target.value === before) return;

      // request.update() id
      // result: 1

      contentEle.innerHTML = "";
      const basicEle = basic(e.target.value);
      contentEle.append(basicEle);
    } catch (e) {
      alert(e.message);
    }
  };

  // 화면을 바꿔주기 구현
  contentEle.innerHTML = "";
  const clone = template.update();
  const input = clone.querySelector("span > input");
  input.value = before;

  input.addEventListener("keyup", enterHandler);
  contentEle.append(clone);

  // enter를 쳤을 시 원래 상태로 되돌리기 구현
};

const deleteBtn = (ulElement) => {
  try {
    if (!confirm("삭제하시겠습니까?")) return;
    ulElement.remove();
  } catch (e) {
    alert(e.message);
  }
};

const clickHandler = (e) => {
  const contentElement = e.target.parentNode;
  const ulElement = contentElement.parentNode;
  const { index: id } = ulElement.dataset;

  switch (e.target.className) {
    case "comment-update-btn":
      changeBtn(contentElement, e.target.innerHTML);
      break;
    case "comment-delete-btn":
      deleteBtn(ulElement);
      break;
  }
};

const events = ({ submitHandler, clickHandler }) => {
  const form = document.querySelector("#commentFrm");
  const list = document.querySelector("#comment-list");

  form.addEventListener("submit", submitHandler);
  list.addEventListener("click", clickHandler);
};

(async () => {
  init();
  events({
    submitHandler,
    clickHandler,
  });
})();