on1ystar 머릿속을 정리, 기록하는 곳

Node.js/Express Mongoose(몽구스)-CRUD-Detail/Update/Delete



의문점이나 지적 등의 관심 및 조언을 위한 댓글이나 메일은 언제나 환영이고 감사합니다.

디렉토리 구조나 파일 구조 등은 이전 포스트에서 이어짐


☄ Video Detail



몽구스가 생성한 video.id가 24자리 16진수로 이루어져 있기 때문에 라우팅 파라미터의 id 정규표현식을 바꿔 줘야 함

  • regex : /[0-9a-f]{24}/

그래야 /video/upload로 접속 시 uploadid 파라미터로 매핑되는 일이 없어짐.

// routers/videoRouter.js

// ... 생략

videoRouter.get("/:id([0-9a-f]{24})", watch);
videoRouter.route("/:id([0-9a-f]{24})/edit").get(getEdit).post(postEdit);
videoRouter.route("/upload").get(getUpload).post(postUpload);

// ... 생략

수정하는 김에 edit도 같이 수정

그 다음 req.params가 받아온 id값을 이용해서 Video.findById 쿼리 실행(async await 빼먹지 않게 조심)

// controllers/videoController.js

// ... 생략

export const watch = async (req, res) => {
  const { id } = req.params;
  const video = await Video.findById(id);
  return res.render("watch", { pageTitle: video.title, video });
};

// ... 생략

Video.findById().exec()promise 객체를 리턴해 주는데, async await를 사용하면 없어도 됨.

마지막으로 존재하지 않는 Video에 대한 예외 처리를 해줘야 한다.

간단하게 404.pug 페이지를 만든 뒤, if문으로 예외처리

// views/404.pug

extends base
// controllers/videoController.js 위 내용 수정

if (!video) {
  return res.render("404", { pageTitle: "Video Not Found" });
}
return res.render("watch", { pageTitle: video.title, video });


☄ Edit Video



watch 컨트롤러와 마찬가지로 비디오 수정 페이지를 렌더링하는 getEdit 컨트롤러 수정

// controllers/videoController.js

// ... 생략

export const getEdit = async (req, res) => {
  const { id } = req.params;
  const video = await Video.findById(id);
  if (!video) {
    return res.render("404", { pageTitle: "Video Not Found" });
  }
  return res.render("videoEdit", { pageTitle: `Edit, ${video.title} `, video });
};

// ... 생략

추가적으로 videoEdit.pug도 수정

// views/videoEdit.pug
extends base

block content
    form(method="post")
        input(name="title", placeholder="Video Title", value=`${video.title}`, required)
        input(type="text", name="description", placeholder="New discription", required, maxlength="100", value=`${video.description}`)
        input(type="text", name="hashtags", placeholder="hashtags, seperated by ,(comma)", required, style="width: 50%", value=`${video.hashtags.join()}`)
        input(value="save", type="submit")

post request로 전달된 req.body 데이터를 DB에 저장하기

가장 간단한 방법은 model 객체의 각 필드 값에 대입한 후 save 메서드 호출이다.

// controllers/videoController.js

// ... 생략

export const postEdit = async (req, res) => {
  const { id } = req.params;
  const { title, description, hashtags } = req.body;
  const video = await Video.findById(id);
  if (!video) {
    return res.render("404", { pageTitle: "Video Not Found" });
  }
  video.title = title;
  video.description = description;
  video.hashtags = hashtags
    .split(",")
    .map((word) =>
      word.trim()[0] === "#" ? `${word.trim()}` : `#${word.trim()}`
    );
  await video.save();
  return res.redirect(`/videos/${id}`);
};

또 다른 방법은 Model.findByIdAndUpdate() 메서드 호출이다.

// controllers/videoController.js

// ... 생략

export const postEdit = async (req, res) => {
  const { id } = req.params;
  const { title, description, hashtags } = req.body;
  const video = await Video.exists({ _id: id }); // 변경
  if (!video) {
    return res.render("404", { pageTitle: "Video Not Found" });
  }
  // 변경
  await Video.findByIdAndUpdate(id, {
    title,
    description,
    hashtags: hashtags
      .split(",")
      .map((word) =>
        word.trim()[0] === "#" ? `${word.trim()}` : `#${word.trim()}`
      ),
  });
  return res.redirect(`/videos/${id}`);
};

Model 객체는 다양한 쿼리를 다룰 수 있는 메서드들을 제공한다.

https://mongoosejs.com/docs/api/model.html

Parameters

  • id «Object/Number/String» value of _id to query by
  • [update] «Object»
  • [options] «Object» optional see Query.prototype.setOptions()
  • 첫 번째 파라미터는 iddocument 객체를 구분할 수 있는 필드 값이다.
  • 두 번째 파라미터는 Object로 업데이트할 내용들을 객체로 전달하면 된다.
  1. 그러면 Mongoose는 DB에서 Model에 해당하는 collection을 찾고,
  2. 전달된 id값과 일치하는 document를 조회한 뒤,
  3. 전달된 Object 객체의 필드 값을 매칭시켜 내용을 업데이트 한 후
  4. save까지 호출해 저장한다.

그리고 Model.exists라는 메서드도 사용했는데,

Parameters

  • filter «Object»
  • [callback] «Function» callback

필터 Object와 일치하는 document를 찾아 true or false를 반환한다.

다양한 필터 조건을 편라하게 Object로 구성해 document 객체 존재 여부를 알 수 있다.


☄ Delete Video



비디오를 삭제하는 쿼리 함수는 매우 간단하다. findOneAndDelete() 함수나 id를 이용해 더 간단하게 줄인 findByIdAndDelete()를 사용하면 된다.

// controllers/videoController.js

// ... 생략

export const deleteVideo = async (req, res) => {
  const { id } = req.params;
  await Video.findByIdAndDelete(id);
  return res.redirect("/");
};

사실 데이터를 삭제하는 쿼리 함수는 findOneAndDelete() 외에 remove()가 하나 더 있다(있었다). 그런데 이제 거의 모든 경우에 findOneAndDelete()를 사용하도록 대체됐다.

findOneAndDelete()에 대한 몽구스 docs 설명

This function differs slightly from Model.findOneAndRemove() in that findOneAndRemove() becomes a MongoDB findAndModify() command, as opposed to a findOneAndDelete() command. For most mongoose use cases, this distinction is purely pedantic. You should use findOneAndDelete() unless you have a good reason not to.


Reference

https://nomadcoders.co/wetube

https://mongoosejs.com/docs/api/model.html

https://mongoosejs.com/docs/api.html#model_Model.findOneAndDelete