<API AJAX> 댓글

김민석·2021년 1월 31일
0

YouTube clone

목록 보기
50/54

댓글을 일반적인 post method 요청으로 추가할수도 있지만 AJAX로 구현하는 이유는 바로 페이지를 새로고침하는걸 보고싶지 않기 때문입니다. 이게 바로 AJAX의 포인트라고 할 수 있습니다.

project

youtube
  |views
   *|videoDetail.pug
 *|routes.js
  |routers
   *|apiRouter.js
  |controllers
   *|videoController.js
  |assets
    |js
     *|main.js
     +|addComment.js

videoDetail.pug

videoDetail page에 댓글을 남길 수 있는 form을 추가해주겠습니다. form의 위치는 각자 원하는 위치에 넣어주면 됩니다.

  • form
    일반적인 form과 달리 action이나 method property를 정의해주지 않았는데요. 그 이유는 우리가 페이지를 리로딩 시키지 않고 댓글을 데이터베이스에 등록하기 위해서 입니다. 대신 AJAX로 처리할 것이구요. 그 방법은 뒷부분에서 자세히 다루겠습니다.
  • input name
    input name도 form property와 같은 이유로 입력해주지 않아도 됩니다.
  • each comment in video.comments
    그대로 사용해도 되지만 댓글 특성상 최신의 댓글이 맨 위에 오도록 하려면 video.comments.reverse()를 사용하면 배열을 뒤집어줍니다.
form.video__add-comment#jsCommentForm
  input(type="text", placeholder="댓글 작성")

ul.video__comments#jsCommentList
  each comment in video.comments
    li #{comment.text}

routes.js

const ADD_COMMENT = '/:id/comment';

const routes = {
  addComment: ADD_COMMENT;
}

export default routes;

videoController.js

  • postAddComment
    작성된 댓글을 받아서 데이터베이스에 추가하는 함수로 postAddComment를 추가했습니다.
  • videoDetail
    video에 저장된 comment들을 탬플릿에 전달하기 위해 video element를 받아오는 과정에서 comment도 populate했습니다. 그리고 user의 avatar나 name을 사용하기 위해 comment에 들어있는 User의 내용도 populate해주어야 합니다.
  • Promise.all
    videoDetail 함수에서 comment 배열에서 comment원소 각각에 들어있는 user의 내용을 populate하기 위해서 Promise.all을 사용하겠습니다. 왜냐하면 Promise.all을 사용하지 않고 async/await만 사용하게 되면 모든 comment에 대해서 기다리지 않기 때문에 빈 배열만 전달하게 됩니다.
import Comment from '../models/Comment';

export const postAddComment = async(req, res) => {
  const {
    params:{id},
    body:{comment},
    user
  }=req;
  
  try{
    const video = await Video.findById(id);
    const newComment = await Comment.create({
 	  text: comment,
      video: video.id,
      creator: user._id
    });
    video.comments.push(newComment.id);
    video.save();
    res.status(200);
  }catch(){
    res.status(400);
  }finally{
    res.end();
  }
}

export const videoDetail = async(req, res) => {
  /*생략했지만 try-catch문에 넣어주세요.*/
  const {params:{id}} = req;
  
  const video = await Video.findById(id)
    .populate("creator")
    .populate("comments");
  const comments = await Promise.all(video.comments.map(async comment => await Comment.findById(comment.id).populate('creator'));

  res.render("videoDetail", {video, commments});
  /*생략*/
}

apiRouter.js

import {postAddComment} from '../controllers/videoController';

apiRouter.post(routes.addComment, postAddComment);

addComment.js

axios 설치

%npm install axios를 터미널에 입력

axios

axios는 fetch와 비슷한 역할을 하는데 아래와 같은 option을 객체로 전달하여 사용할 수 있다.

  • url
    fetch와 달리 전체 주소를 입력하지 않아도 된다.
  • method
    method를 의미하며 'GET' 이나 'POST'를 사용할 수 있다.
  • data
    req.body에 담기게 될 정보를 의미한다. 객체로 name과 value를 서버에 전달한다.

addComment.js

  • addComment 함수
    댓글을 작성하여 데이터베이스에 추가된 댓글이 바로 탬플릿에 적용되면 좋지만 정보는 변경되기 전을 반영하고 있기 때문에 새로고침을 하지 않으면 댓글 추가가 바로 반영되지 않아서 javascript trick을 사용하여 실시간으로 추가되는 것 같은 탬플릿을 만들어보겠습니다. html로만 추가를 해두었다가 나중에 다시 접속했을 때는 실제 서버로부터 받아와서 적용되는 효과를 얻을 수 있습니다.
  • response
    axios에 입력한 주소로부터 받은 response를 받아보면 videoController에서 작성해준 로직에 맞게 status code를 response.status에 담고 있습니다. 200인 경우에는 정상적 처리, 400인 경우에는 에러 발생이었죠. 이 코드를 이용해서 javascript trick에 이용해주겠습니다.아무리 fake data라고 하더라고 실제로는 에러가 나서 등록되지 않은 댓글을 보여주면 안되니까요.
const commentForm = document.querySelector('#jsCommentForm');
const commentList = document.querySelector('#jsCommentList');

const addComment = (comment) => {
  const comment = document.createElement('li');
  comment.innerHTML = comment;
  //댓글이 최신순으로 위에 올라오길 원하면 appendChild가 아니라 prepend method를 사용하시면 됩니다.
  commentList.appendChild(comment);
}

async function handleAddComment (event){
  //페이지의 리로딩 방지
  event.preventDefault();
  const commentInput = commentForm.querySelector('input');
  const comment = commentInput.value;
  commentInput.value = '';
  const videoId = window.location.href.split("/videos/")[1];
  const response = await axios({
    url:`/api/${videoId}/comment`,
    method: "POST",
    data:{
      comment
    }
  });
  
  if(response.status === 200){
    addComment(comment);
  }
}

function init(){
  commentForm.addEventListener('submit', handleAddComment);
}

if(commentForm){
  init();
}

main.js

import './addComment.js';
profile
누구나 실수 할 수 있다고 생각합니다. 다만 저는 같은 실수를 반복하는 사람이 되고 싶지 않습니다. 같은 실수를 반복하지 않기 위해 기록하여 기억합니다.🙃

0개의 댓글