My Boundary As Much As I Experienced

express로 댓글기능 구현하기 본문

BackEnd/Node.js

express로 댓글기능 구현하기

Bumang 2024. 9. 18. 14:12

미리보기

말 그대로 댓글기능이다. express + mongoDB로 구현하였다.

 

구현해야됐던 로직이다.

1. 글마다 밑에 input이랑 전송버튼이 있고 누르면 서버로 댓글이 전송

2. 그럼 서버는 댓글을 DB에 저장

3. 상세페이지 방문시 댓글을 가져와서 보여주기

 

어떻게 구현해야할까?

 

 

1. 글 문서에 댓글도 같이 있는 경우

이렇게 한다고 경찰서에 잡혀가진 않지만... 댓글이 1억개 달릴 시 조회에 매우 큰 리소스가 들게 된다.

또한 DB에 있는 array 자료형에서 원하는 항목 하나만 수정, 삭제하는게 매우 어렵고, (1억개 다 들고 와야함)

document 하나는 16MB의 데이터까지 저장할 수 있다는 점도 유의해야된다.

{
  _id: @!@#,
  title : '어쩌구',
  content : '어쩌구',
  comment : [ '댓글1', '댓글2', '댓글3' ]
}

 

결과적으로 not good이다.

 

 

2. 댓글 하나하나가 한 문서인 경우

댓글 컬랙션을 따로 만들고 댓글 하나하나를 다 추가한다.

그리고 parent_id로 어떤 문서를 바라보고 있는지를 기입한다.

 

이것도 결국 전체 댓글을 조회해야되기 때문에 리소스가 많이 들지 않냐고?

그런데 DB가 제일 잘하는게 한 컬렉션에서 많은 문서들을 조회하는 것을 제일 잘한다고 한다.

document안의 세부 속성에 접근하는 것보다 훨씬 잘한다.

 

{
  _id: @!@#,
  author : '어쩌구',
  comment: '야호~~~!",
  parent_id : '어쩌구',
}

 

하여튼 이제 댓글 컬렉션을 만들고 parent_id를 추가하는 식으로 구현해보자.

 

 

3. detail.ejs에 댓글 파트 추가

서버에서 받은 배열을 반복문을 써서 댓글형식으로 구현해준다.

 

      <form
        class="form-box"
        action="/reply"
        method="POST"
        id="replyForm"
        enctype="multipart/form-data"
      >
        <h4>댓글 달기</h4>
        <input name="content" id="content" />
        <button type="submit">전송</button>
      </form>

      <h5>댓글 수 <%= replies.length %></h5>
      <% for (var i = 0; i < replies.length; i++){ %>
      <div class="list-box">
        <a href="/detail/<%= replies[i]._id %>">
          <h4><%= replies[i].username %></h4>
        </a>
        <p><%= replies[i].content %></p>
        <% if (replies[i].img) { %>
        <img
          src="<%= replies[i].img %>"
          style="width: 200px; height: 200px; object-fit: cover"
        />
        <% } %>
        <a href="/modify/<%= replies[i]._id %>">수정</a>
        <button class="delete" data-id="<%= replies[i]._id %>">🗑️</button>
      </div>
      <% } %>

 

 

4. 댓글 API 요청

해당 포스트의 id와 댓글 내용(content)을 post 메소드로 전송한다.

(fetch API 오랜만에 써보네..)

 

댓글 포스트 성공하면 그냥 새로고침 해준다.

다시 호출 안 하고 댓글 컴포넌트에 담아 댓글란에 하나 마운트 해놓을수도 있었지만..

바닐라js로 컴포넌트 하나 셋업하는거 너무 힘들어...

 

그냥 새로고침해서 추가된 댓글 확인하게 했다.

  <script>
    document
      .querySelector("#replyForm")
      .addEventListener("submit", async (event) => {
        event.preventDefault();

        const content = document.getElementById("content").value;
        const id = document.querySelector("#id-storage").dataset.id;

        console.log(content, "content");
        console.log(id, "id");

        try {
          // fetch API로 서버에 POST 요청 보내기
          const response = await fetch("/reply", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              content,
              id,
            }),
          });

          document.getElementById("content").value = "";

          if (response.ok) {
            location.reload();
          }
        } catch (err) {
          console.error("Error:", err);
          document.getElementById("responseMessage").innerText =
            "An error occurred.";
        }

        document.getElementById("content").value = "";

        return;
      });
  </script>

 

 

 

5. 디테일 페이지에서 댓글 조회 API 추가

이제부터 서버 단 설명이다.

디테일 페이지 로딩할 때 post말고도 reply라는 새로운 콜렉션을 조회해서

combinedTo가 해당글의 id와 똑같은 녀석들을 골라

배열로 만들어 detail.ejs페이지에 주입한다.

 

app.get("/detail/:id", async (req, res) => {
  let result;
  let replies;

  try {
    result = await db
      .collection("post")
      .findOne({ _id: new ObjectId(req.params.id) });

    replies = await db
      .collection("reply")
      .find({ combinedTo: new ObjectId(req.params.id) })
      .toArray();
  } catch (err) {
    console.log(err, "err");
    return;
  }

  res.render("detail.ejs", {
    title: result?.title,
    content: result.content,
    img: result.img,
    replies: replies,
    id: result._id,
  });
});

 

5. 새로운 댓글 추가 API

프론트에서 댓글 내용과 글id를 찾아왔으면 추가적으로

req.user에서 username을 불러온다.

 

그리고 reply 컬렉션을 가져와서

해당 댓글이 바라보는 글id를 기입,

메시지 내용, 유저 이름 등을 넣고

reply를 추가해준다.

 

이게 끝이다.

const replyRouter = require("express").Router();
let connectDB = require("../database.js"); //database.js 파일 경로
const { ObjectId } = require("mongodb");

connectDB
  .then((client) => {
    console.log("DB연결성공 in write.js");
    db = client.db("forum");
  })
  .catch((err) => {
    console.log(err);
  });

replyRouter.post("/", async (req, res) => {
  try {
    const { content, id } = req.body;
    const col = await db.collection("reply").insertOne({
      content,
      combinedTo: new ObjectId(id),
      username: req.user?.username,
    });

    res.status(201).json({
      success: true,
      message: "Reply added successfully",
      replyId: col.insertedId, // 삽입된 댓글의 ID
    });
  } catch (err) {
    console.log(err);
  }
});

module.exports = replyRouter;

 

 

 

사실 댓글기능 어렵게 생각하진 않았었는데... 백엔드 로직 생각하다가 프론트 로직 생각하고..

이러는게 꽤 힘들다는 것을 알게되었다. 풀스택을 한다는게 꽤 머리가 꼬이는 일이구나 싶다.