Node 강의/숙련

2-5 [게시글 프로젝트] 댓글 생성 API

kagan-draca 2024. 9. 9. 12:36

1. 게시글에 댓글 생성 API

댓글 기능은 게시글 기능과 부모 - 자식 간의 관계로 구성된다.

 

일반적인 사이트를 생각해보면, 게시글과 별도로 댓글이 존재하지 않고,

특정 게시글에 대한 하위 개념으로 존재합니다.

 

그렇기 때문에, 댓글 기능을 구현하기 위해서는 게시글이 필수적입니다.

 

즉, 댓글 API의 URL은 게시글 API의 URL을 기반으로 작성됩니다.

ex) api/posts/:postId/comments

 

1) 댓글 생성 API(Router 추가 아래에 존재합니다)

routes/comments.router.js 파일을 생성하고 

import express from "express";
import prisma from "../src/utils/prisma/index.js";
import authMiddleware from "../middlewares/auth.middleware.js";

const CommentsRouter = express.Router();

CommentsRouter.post(
  "/posts/:postId/comments",
  authMiddleware,
  async (req, res) => {
    const { postId } = req.params; // 댓글을 작성할 게시글 찾기
    const { userId } = req.user; // 작성자 Id
    const { content } = req.body; // content
    const post = await prisma.Posts.findFirst({
      where: {
        postId: +postId,
      },
    });
    // 게시글 찾기

    if (!post)
      return res.json(404).json({ message: "게시글이 존재하지 않습니다." });

    const comment = await prisma.Comments.create({
      data: {
        userId: +userId, // 댓글 작성자 ID
        postId: +postId, // 댓글 작성 게시글 ID
        content: content,
      },
    });

    return res.status(201).json({ data: comment });
  }
);

export default CommentsRouter;

위와 같이 작성 합니다.

 

게시글과 마찬가지로 auth.middleware에서 사용자가 로그인 상태인지 확인합니다.

그 후,

 

const { postId } = req.params; // 댓글을 작성할 게시글 찾기

로 게시글이 Id를 가져오고,

 

    const { userId } = req.user; // 작성자 Id
    const { content } = req.body; // content

작성자Id와 content를 가져옵니다.

Insomnia에서 localhost:3018/api/posts/:postId/comments에 접속해 댓글을 생성하면,

위와 같이 댓글이 추가되고, Clinet에게 댓글을 응답으로 보여줍니다.

2) 댓글 조회 API(Router 추가 아래에 존재합니다)

 

// 댓글 전체 조회 API
CommentsRouter.get("/posts/:postId/comments", async (req, res, next) => {
  const { postId } = req.params;

  const post = await prisma.Posts.findFirst({
    where: {
      postId: +postId,
    },
  });

  if (!post)
    return res.json(404).json({ message: "게시글을 찾을 수 없습니다." });

  const comments = await prisma.Comments.findMany({
    where: {
      postId: +postId,
    },
    orderBy: {
      createdAt: "desc",
    },
  });

  return res.status(200).json({ data: comments });
});

 

const {postId} = req.params

로 게시글 Id를 가져와

  const post = await prisma.Posts.findFirst({
    where: {
      postId: +postId,
    },
  });

  if (!post)
    return res.json(404).json({ message: "게시글을 찾을 수 없습니다." });

게시글 존재 유무를 확인합니다.

 

게시글이 존재한다면,

  const comments = await prisma.Comments.findMany({
    where: {
      postId: +postId,
    },
    orderBy: {
      createdAt: "desc",
    },
  });

  return res.status(200).json({ data: comments });

게시글의 댓글 전체를 

    orderBy: {
      createdAt: "desc",
    },

orderBy로 생성일을 기준으로 "desc" 내림차순 정렬해줍니다.

 

Insomnia에서 localhost:3018/api/posts/:postId/comments에 접속해 댓글을 조회하면,

 

댓글을 확인할 수 있고, 만들어진 날짜 및 시간을 바탕으로 내림차순 정렬된 걸 확인할 수 있습니다.

 

3) app.js에 CommentsRouter 추가해주기

import express from "express";
import cookieParser from "cookie-parser";
import UsersRouter from "../routes/users.router.js";
import PostsRouter from "../routes/posts.router.js";
import CommentsRouter from "../routes/comments.router.js";
import LogMiddleware from "../middlewares/log.middleware.js";
import ErrorHandingMiddleware from "../middlewares/errorhanding.middleware.js";

const app = express();
const PORT = 3018;

app.use(LogMiddleware);
app.use(express.json());
app.use(cookieParser());

app.use("/api", [UsersRouter, PostsRouter, CommentsRouter]);

app.use(ErrorHandingMiddleware);
app.listen(PORT, () => {
  console.log(PORT, "프트로 서버가 열렸습니다");
});

위와 같이,

import CommentsRouter from "../routes/comments.router.js";

CommentsRouter를 import 받고,

 

app.use("/api", [UsersRouter, PostsRouter, CommentsRouter]);

"/api/~~~~" 경로를 통해 사용할 예정이라,

 

"/api/" 에 해당하는 경로에 CommentsRouter를 추가해줍니다.

 

전체코드 :

import express from "express";
import prisma from "../src/utils/prisma/index.js";
import authMiddleware from "../middlewares/auth.middleware.js";

const CommentsRouter = express.Router();

CommentsRouter.post(
  "/posts/:postId/comments",
  authMiddleware,
  async (req, res) => {
    const { postId } = req.params; // 댓글을 작성할 게시글 찾기
    const { userId } = req.user; // 작성자 Id
    const { content } = req.body; // content
    const post = await prisma.Posts.findFirst({
      where: {
        postId: +postId,
      },
    });
    // 게시글 찾기

    if (!post)
      return res.json(404).json({ message: "게시글이 존재하지 않습니다." });

    const comment = await prisma.Comments.create({
      data: {
        userId: +userId, // 댓글 작성자 ID
        postId: +postId, // 댓글 작성 게시글 ID
        content: content,
      },
    });

    return res.status(201).json({ data: comment });
  }
);

// 댓글 전체조회 API
CommentsRouter.get("/posts/:postId/comments", async (req, res, next) => {
  const { postId } = req.params;

  const post = await prisma.Posts.findFirst({
    where: {
      postId: +postId,
    },
  });

  if (!post)
    return res.json(404).json({ message: "게시글을 찾을 수 없습니다." });

  const comments = await prisma.Comments.findMany({
    where: {
      postId: +postId,
    },
    orderBy: {
      createdAt: "desc",
    },
  });

  return res.status(200).json({ data: comments });
});

export default CommentsRouter;

 

4) 역할과 책임

 

게시글을 조회한다면, 하나의 게시글과 해당 게시글에 달린 댓글들을 함께 보여줍니다.

이를 구현하는 방법에는 크게 두 가지의 방법이 있습니다.

 

1. 게시글 상세 조회 API 내부에서 댓글 목록도 함께 반환한다.

2. 게시글 조회 API와 댓글 조회 API를 따로 호출하여, 각 정보를 가져온다.

 

두 가지 방법 모두 장단점이 존재합니다.

  게시글 상세 조회 API 내부에서 댓글 목록 반환 게시글 조회 API와 댓글 조회 API를 따로 호출
장점 한 번의 호출로 모든 정보를 가져올 수 있다. 코드 간 간결하고 관리하기 쉬워집니다.
단점 게시글과 댓글이라는 서로 다른 도메인이 하나의
API에서 처리 돼, 코드가 복잡해집니다.
서로 다른 도메인의 데이터를
각각의 API에서 처리해야합니다.

프론트엔드의 두 번의 API 호출로
응답 시간이 길어질 수 있습니다.

 

우리는 2가지 방법 중 2번을 선택해, 명확한 "역할"과 "책임"을 가지고 나눠줬습니다.

 

하나의 코드가 많은 역할과 책임을 가질 때 발생하는 문제와

하나의 코드가 단 하나의 역할과 책임만 가질 때의 장점에 대한

고민은 많은 개발자들이 아직까지도 고민하고 풀고자하는 문제 중 하나입니다.