본문 바로가기

Node.js

Node.js(13) - XMLHttpRequest/fetch/axios, 비동기 통신 댓글 CRUD(axios 활용)

728x90
반응형

1) Node.js

   1-1) XMLHttpRequest, fetch, axios

   1-2) 비동기 통신 - 댓글 CR(Create, Read)

   1-3) 비동기 통신 - 댓글 UD(Update, Delete)

 

 

 

 

 

 

 

1) Node.js

"요청을 잘 보내고 잘 받는 것"에 숙달되는 것이 중요하다!!


"display: none;"된 element가 "template element"이다!!
어떤 요청을 보내면 어떻게 응답을 줄지(구체적으로 어떤 데이터 타입으로 줄지를 명시한 것을 의미함)를 정의한 것이 "API 문서"이다!!

 

 

1-1) XMLHttpRequest, fetch, axios

const request = ({ method, path, body }) => {
  return new Promise((resolve, reject) => {
    const host = "http://localhost:3000";
    const xhr = new XMLHttpRequest();
    xhr.open(method, `${host}${path}`);
    xhr.setRequestHeader("Content-type", "application/json");
    xhr.send(JSON.stringify(body));

    xhr.onload = () => {
      if (xhr.readyState === 4 && xhr.status === 200) {
        resolve(JSON.parse(xhr.response));
      } else {
        reject(`ERR`);
      }
    };
  });
};

// "즉시 실행 함수"처럼 프로그램 시작 시 최초에 한 번만 실행하면 되는 함수를 "main 함수"라 한다!!
(async () => {
  // XMLHttpRequest(Javascript 내장)
  const ajax = await request({
    method: "get",
    path: "/comments",
    body: null,
  });
  console.log(ajax);

  // fetch(Javascript 내장)
  const response = await fetch("http://localhost:3000/comments", {
    method: "get",
    headers: {
      "Content-type": "application/json",
    },
    body: null,
  });
  // console.log(response); // response message를 객체화해서 보여줌!!(단, body는 확인 불가하다!!)
  const body = await response.json();
  console.log(body); // body를 확인하기 위해서는 위와 같이 await을 두 번 써야 한다!!

  // axios(이는 외부에서 만든 것으로, XMLHttpRequest나 fetch에 비해 좀 더 편하게 쓰려 만든 것이다!!)
  // 물론 axios 역시 XMLHttpRequest에 기반하여 만들어진 것이다!!
  // <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  // 공식 문서 : https://axios-http.com/kr/docs/example
  // "get"은 데이터를 가져오는 행위인 반면, "post"는 데이터를 넣는 행위이다!!
  const res = await axios.get("http://localhost:3000/comments");
  const res2 = await axios.post("http://localhost:3000/comments", {
    content: "내용 잘 들어갔나~??",
  });
  console.log(res.data);
  console.log(res2);
})();

// 오래전 Javascript에서 사용하던 것이 "XMLHttpRequest()"인데
// 이후 Javascript가 버전 업이 되면서 내장 객체를 새로 하나 만들었는데 이것이 바로 "fetch"이다!!
// 그 후 좀 더 편하게 쓰기 위해 외부 모듈 혹은 라이브러리가 등장하는데 이것이 바로 "axios"이다!

 

 

 

1-2) 비동기 통신 - 댓글 CR(Create, Read)

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>
  </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/comment.js

const row = ({ id, userid, content, register }) => {
  const template = document.querySelector("#commentRow");
  // console.log(template.content);

  const clone = document.importNode(template.content, true);
  // console.log(clone);

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

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

  // console.log(ul);
  // console.log(ul.dataset);
  return ul;
};

const drawing = (ul) => {
  const commentElement = document.querySelector("#comment-list");
  commentElement.append(ul);
  const ulElement = document.querySelectorAll("#comment-list > ul");
  totalRecord(ulElement.length);
};

const getCommentList = async () => {
  try {
    const res = await axios.get("http://localhost:3000/comments");
    return res.data;
  } catch (e) {
    return null;
  }
};

const addComment = async (content) => {
  try {
    const res = await axios.post("http://localhost:3000/comments", {
      content,
    });
    return res.data; // 여기서 res.data의 return 값은 객체("{}")이다!!
  } catch (e) {
    return null;
  }
};

const totalRecord = (count) => {
  const countElement = document.querySelector("h4 > span");
  countElement.innerHTML = `(${count})`;
};

// init : 요청을 때리고 데이터를 저장하는 역할을 함!
const init = async () => {
  const list = await getCommentList(); // list의 데이터 타입은 배열("[]")이다!!
  // console.log(list);
  if (list === null) return;
  // 아래 comment의 데이터 타입은 객체("{}")이다!!
  for (const comment of list) {
    const item = row(comment); // row 함수의 return 값은 "ul element"이다!!
    drawing(item);
  }
};

const events = ({ submitHandler }) => {
  const form = document.querySelector("#commentFrm");
  form.addEventListener("submit", submitHandler);
};

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

  const item = await addComment(content.value);
  const ul = row(item);
  drawing(ul);

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

// "즉시 실행 함수"처럼 프로그램 시작 시 최초에 한 번만 실행하면 되는 함수를 "main 함수"라 한다!!
(async () => {
  // axios(이는 외부에서 만든 것으로, XMLHttpRequest나 fetch에 비해 좀 더 편하게 쓰려 만든 것이다!!)
  // 물론 axios 역시 XMLHttpRequest에 기반하여 만들어진 것이다!!
  // <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  // 공식 문서 : https://axios-http.com/kr/docs/example
  // "get"은 데이터를 가져오는 행위인 반면, "post"는 데이터를 넣는 행위이다!!
  // const res = await axios.get("http://localhost:3000/comments");
  // const res2 = await axios.post("http://localhost:3000/comments", {
  //   content: "내용 잘 들어갔나~??",
  // });

  init();
  events({
    submitHandler,
  });
})();

// 오래전 Javascript에서 사용하던 것이 "XMLHttpRequest()"인데
// 이후 Javascript가 버전 업이 되면서 내장 객체를 새로 하나 만들었는데 이것이 바로 "fetch"이다!!
// 그 후 좀 더 편하기 쓰기 위해 외부 모듈 혹은 라이브러리가 등장하는데 이것이 바로 "axios"이다!

 

 

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());

/*
`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}`
    );
    response.updated = false;
    res.json(response);
  } catch (e) {
    next(e);
  }
});

app.get("/comments/:id", (req, res, next) => {
  try {
    res.send("하나 게시물 가져오기");
  } catch (e) {
    next(e);
  }
});

app.put("/comments/:id", (req, res, next) => {
  try {
    res.send("게시물 수정하기");
  } catch (e) {
    next(e);
  }
});
app.delete("/comments/:id", (req, res, next) => {
  try {
    res.send("게시물 삭제");
  } catch (e) {
    next(e);
  }
});

app.use((error, req, res, next) => {
  console.log(error.message);
  res.send(`${error}`);
});

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-3) 비동기 통신 - 댓글 UD(Update, Delete)

※ 아래 내용의 경우, 위의 CR 구현 결과에서 내용이 추가로 바뀐 파일만 소개함!!

 

front

public/js/comment.js

const basicBox = (content) => {
  const template = document.querySelector("#content-baisc");
  const clone = document.importNode(template.content, true);
  const span = clone.querySelector(".comment-update-btn");
  span.innerHTML = content;
  return clone;
};

const box = (updated, content) => {
  if (updated) {
    // updated가 true일 때는 수정 화면
  } else {
    // updated가 false일 때는 기본 화면
    return basicBox(content);
  }
};

const row = ({ id, userid, content, register, updated }) => {
  /*
  <row example>
  content: "hello world~~"
  id: 1
  register: "2023-01-05"
  updated: false
  userid: "web7722"
  */

  const template = document.querySelector("#commentRow");
  // console.log(template.content);

  const clone = document.importNode(template.content, true);
  // console.log(clone);

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

  ul.dataset.index = id;
  li[0].innerHTML = userid;
  li[1].append(box(updated, content)); // box 함수의 return 값은 "element"이다!!
  li[2].innerHTML = register;

  // console.log(ul);
  // console.log(ul.dataset);
  return ul;
};

const drawing = (ul) => {
  const commentElement = document.querySelector("#comment-list");
  commentElement.append(ul);
  const ulElement = document.querySelectorAll("#comment-list > ul");
  totalRecord(ulElement.length);
};

const getCommentList = async () => {
  try {
    const res = await axios.get("http://localhost:3000/comments");
    return res.data;
  } catch (e) {
    return null;
  }
};

const addComment = async (content) => {
  try {
    const res = await axios.post("http://localhost:3000/comments", {
      content,
    });
    return res.data; // 여기서 res.data의 return 값은 객체("{}")이다!!
  } catch (e) {
    return null;
  }
};

const totalRecord = (count) => {
  const countElement = document.querySelector("h4 > span");
  countElement.innerHTML = `(${count})`;
};

// init : 요청을 때리고 데이터를 저장하는 역할을 함!
const init = async () => {
  const list = await getCommentList(); // list의 데이터 타입은 배열("[]")이다!!
  // console.log(list);
  if (list === null) return;
  // 아래 comment의 데이터 타입은 객체("{}")이다!!
  for (const comment of list) {
    const item = row(comment); // row 함수의 return 값은 "ul element"이다!!
    drawing(item);
  }
};

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

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

  const item = await addComment(content.value);
  const ul = row(item);
  drawing(ul);

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

const clickHandler = (e) => {
  console.log(e.target.parentNode.parentNode);
  console.log(e.target.parentNode.parentNode.dataset.index);
  if (e.target.className === "comment-update-btn") {
    console.log("제목을 클릭했다~~~");
    // id: DB 식별자
    // id에 해당하는 화면을 바꿔야 함
    // 내용을 입력하고,
    // Enter 이벤트를 발동시키면
    // put method(request method)를 써서 Web Server에 요청함
    // 응답을 받고
    // 그 후 element 내용을 수정한 다음 다시 updated의 값을 false로 바꿔줌!
  } else if (e.target.className === "comment-delete-btn") {
    console.log("삭제 버튼을 클릭했다~~~");
    // id: DB 식별자
    // delete method(request method)를 써서 Web Server에 요청함
    // 응답을 받고
    // 그 후 element를 삭제함!!
  }
};

// "즉시 실행 함수"처럼 프로그램 시작 시 최초에 한 번만 실행하면 되는 함수를 "main 함수"라 한다!!
(async () => {
  // axios(이는 외부에서 만든 것으로, XMLHttpRequest나 fetch에 비해 좀 더 편하게 쓰려 만든 것이다!!)
  // 물론 axios 역시 XMLHttpRequest에 기반하여 만들어진 것이다!!
  // <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  // 공식 문서 : https://axios-http.com/kr/docs/example
  // "get"은 데이터를 가져오는 행위인 반면, "post"는 데이터를 넣는 행위이다!!
  // const res = await axios.get("http://localhost:3000/comments");
  // const res2 = await axios.post("http://localhost:3000/comments", {
  //   content: "내용 잘 들어갔나~??",
  // });

  init();
  events({
    submitHandler,
    clickHandler,
  });
})();

// 오래전 Javascript에서 사용하던 것이 "XMLHttpRequest()"인데
// 이후 Javascript가 버전 업이 되면서 내장 객체를 새로 하나 만들었는데 이것이 바로 "fetch"이다!!
// 그 후 좀 더 편하기 쓰기 위해 외부 모듈 혹은 라이브러리가 등장하는데 이것이 바로 "axios"이다!

 

 

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());

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

app.get("/comments", async (req, res, next) => {
  try {
    // "[response]"의 결과물은 배열("[]")이다!! --> 현재: [{id, userid, content, register}] >> 이렇게 바꾸려 함: [{id, userid, content, register, updated}]
    const [response] = await mysql.query(
      `SELECT id, userid, content, DATE_FORMAT(register,'%Y-%m-%d') as register FROM Comment`
    );
    const result = response.map((v) => {
      return {
        ...v,
        updated: false,
      };
    });
    res.json(result);
  } 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}`
    );
    response.updated = false;
    res.json(response);
  } catch (e) {
    next(e);
  }
});

app.get("/comments/:id", (req, res, next) => {
  try {
    res.send("하나 게시물 가져오기");
  } catch (e) {
    next(e);
  }
});

app.put("/comments/:id", (req, res, next) => {
  try {
    res.send("게시물 수정하기");
  } catch (e) {
    next(e);
  }
});
app.delete("/comments/:id", (req, res, next) => {
  try {
    res.send("게시물 삭제");
  } catch (e) {
    next(e);
  }
});

app.use((error, req, res, next) => {
  console.log(error.message);
  res.send(`${error}`);
});

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