[mongoDB] 관계된 데이터 관리하기

9rganizedChaos·2021년 7월 17일
0
post-thumbnail

🙌🏻 해당 글은 김시훈님의 mongoDB 기초부터 실무까까지의 강의 노트입니다.

Blog 모델 만들기

const { Schema, model, Types } = require("mongoose");

const BlogSchema = new Schema(
  {
    title: { type: String, required: true },
    content: { type: String, required: true },
    islive: { type: Boolean, required: true, default: false },
    user: { type: Types.ObjectId, required: true, ref: "user" },
  },
  { timestamps: true }
);

const Blog = model("blog", BlogSchema);

module.exports = { Blog };

mongoose에서는 {ref: "user"}와 같은 방식으로 레퍼런스 키를 설정한다!

Blog 관련 API 만들기

블로그 포스팅 추가

blogRouter.post("/", async (req, res) => {
  try {
    let { title, content, islive, userId } = req.body;
    if (typeof title !== "string")
      return res.status(400).send({ err: "title is required" });
    if (typeof content !== "string")
      return res.status(400).send({ err: "content is required" });
    if (islive && typeof islive !== "boolean")
      return res.status(400).send({ err: "islive must be a boolean" });
    if (!isValidObjectId(userId))
      return res.status(400).send({ err: "userId is invalid" });
    let user = await User.findById(userId);
    if (!user) return res.status(400).send({ err: "user does not exist" });

    let blog = new Blog({ ...req.body, user });
    await blog.save();
    return res.send({ blog });
  } catch (err) {
    console.log(err);
    res.status(500).send({ err: err.message });
  }
});

(post 요청의 경우 모델 인스턴스화 하는 것 잊지 말기!)

블로그 전체 읽어오기

blogRouter.get("/", async (req, res) => {
  try {
    let blogs = await Blog.find({});
    return res.send({ blogs });
  } catch (err) {
    console.log(err);
    res.status(500).send({ err: err.message });
  }
});

특정 블로그 읽어오기

blogRouter.get("/:blogId", async (req, res) => {
  try {
    let { blogId } = req.params;
    if (!isValidObjectId(blogId))
      return res.status(400).send({ err: "blogId is invalid" });
    let blog = await Blog.findById(blogId);
    if (!blog) return res.status(400).send({ err: "blog does not exist" });

    return res.send({ blog });
  } catch (err) {
    console.log(err);
    res.status(500).send({ err: err.message });
  }
});

블로그 전반 정보 갱신 (내용 및 제목 수정)

blogRouter.put("/:blogId", async (req, res) => {
  try {
    let { blogId } = req.params;
    if (!isValidObjectId(blogId))
      return res.status(400).send({ err: "blogId is invalid" });
    let blog = await Blog.findById(blogId);
    if (!blog) return res.status(400).send({ err: "blog does not exist" });

    let { title, content } = req.body;
    if (typeof title !== "string")
      return res.status(400).send({ err: "title is required" });
    if (typeof content !== "string")
      return res.status(400).send({ err: "content is required" });

    let updatedBlog = await Blog.findOneAndUpdate(
      { _id: blogId },
      { title, content },
      { new: true }
    );
    return res.send({ updatedBlog });
  } catch (err) {
    console.log(err);
    res.status(500).send({ err: err.message });
  }
});

블로그 부분 정보 갱신 (임시저장/발행 여부 전환)

blogRouter.patch("/:blogId/live", async (req, res) => {
  try {
    console.log(req.params);
    let { blogId } = req.params;
    if (!isValidObjectId(blogId))
      return res.status(400).send({ err: "blogId is invalid" });
    let blog = await Blog.findById(blogId);
    if (!blog) return res.status(400).send({ err: "blog does not exist" });

    let { islive } = req.body;
    if (typeof islive !== "boolean")
      return res.status(400).send({ err: "boolean islive required" });

    let updatedBlog = await Blog.findByIdAndUpdate(
      blogId,
      { islive },
      { new: true }
    );
    return res.send({ updatedBlog });
  } catch (err) {
    console.log(err);
    res.status(500).send({ err: err.message });
  }
});

Comment 모델 만들기

사실 이번 챕터 레슨의 주목할만한 점은 Comment 모델 만들기 부분이다. blog 모델과 같은 경우 직전 레슨에서 다루었던 user 모델과 크게 다를 바가 없었다. comment 모델이 blog, user모델과 다른 점은 단독으로 호출되지 않는다는 점이다. comment는 항상 특정 유저의 comment 혹은 특정 blog 포스팅에 대한 comment로 호출된다! 즉, 이번에 다룰 때 comment는 blog의 자식이나 마찬가지라는 것이다. 그렇다면 이런 경우 어떻게 모델을 생성하는지 살펴보도록 하자!

우선 모델 자체는 똑같이 만들어준다.
스키마 작성한 후, 모델로 엮어준다.

const { Schema, model, Types } = require("mongoose");

const CommentSchema = new Schema(
  {
    content: { type: String, required: true },
    user: { type: Types.ObjectId, required: true, ref: "user" },
    blog: { type: Types.ObjectId, required: true, ref: "blog" },
  },
  { timestamps: true }
);

const Comment = model("comment", CommentSchema);

module.exports = { Comment };

그러나 이것을 라우팅해줄 때 주의해주어야 한다!

// server.js
    app.use("/user", userRouter);
    app.use("/blog", blogRouter);
    app.use("/blog/:blogId/comment", commentRouter);

항상 블로그와 함께 호출되므로 위 코드의 마지막 줄처럼 작성해준다! 그리고 애초에 blog에 종속적이기 때문에 server.js에서 처리하지 않고, 아래와 같이 blogRoute.js에서 미들웨어를 적용해주어도 좋다.

blogRouter.use("/:blogId/comment", commentRouter);

Comment 관련 API 작성하기

Comment 작성하기

commentRouter.post("/", async (req, res) => {
  try {
    const { blogId } = req.params;
    const { content, userId } = req.body;
    if (!isValidObjectId(blogId))
      return res.status(400).send({ err: "blogId is invalid" });
    if (!isValidObjectId(userId))
      return res.status(400).send({ err: "userId is invalid" });
    if (typeof content !== "string")
      return res.status(400).send({ err: "content is required" });

    const [blog, user] = await Promise.all([
      Blog.findByIdAndUpdate(blogId),
      User.findByIdAndUpdate(userId),
    ]);
    // const blog = await Blog.findByIdAndUpdate(blogId);
    // const user = await User.findByIdAndUpdate(userId);
    if (!blog || !user)
      return res.status(400).send({ err: "blog or user does not exist" });
    if (!blog.islive)
      return res.status(400).send({ err: "blog is not available" });
    const comment = new Comment({ content, user, blog });
    await comment.save();
    return res.send({ comment });
  } catch (err) {
    return res.status(400).send({ err: err.message });
  }
});

blogId, userId, content를 사용하고 있는데, 어떤 것은 req.body를 통해 넘어오고 어떤 것은 req.params로 넘어온다는 점에 유의하자.

Promise.all 사용하기!

    const blog = await Blog.findByIdAndUpdate(blogId);
    const user = await User.findByIdAndUpdate(userId);

처음에는 위와 같은 방식으로 코드를 작성하였다. 그러나 blog와 user를 조회해오는 작업을 굳이 하나씩 처리해줄 필요가 없다. 아래와 같이 promise.all을 사용하니 180ms 정도 걸리던 것이 90ms 정도로 처리 시간이 줄어들었다.

    const [blog, user] = await Promise.all([
      Blog.findByIdAndUpdate(blogId),
      User.findByIdAndUpdate(userId),
    ]);

Comment 조회하기

commentRouter.get("/", async (req, res) => {
  const { blogId } = req.params;
  if (!isValidObjectId(blogId))
    return res.status(400).send({ err: "blogId is invalid" });

  const comments = await Comment.find({ blog: blogId });
  return res.send({ comments });
});

const comments = await Comment.find({ blog: blogId }); 이 부분에 유의하자, _id가 아니라 blog를 기준으로 조회해야 한다. 늘 _id로 조회하는 것이 아니다.

그리고 항상 return을 해주자! 유효성 검사를 하던 중 res.send로 에러를 보내고 끝내버릴 때 return을 해주지 않으면, 유효성 검사를 통과하지 못했음에도 아래 코드가 실행되어 버리는 경우가 생긴다.

Postman 사용팁

위와 같이 콜렉션을 만들어 저장해서 각 API 테스팅을 더욱 편리하게 테스트해볼 수 있다.

refactoring tip

module.exports = {
  ...require("./Blog"),
  ...require("./Comment"),
  ...require("./User"),
};
// 하나의 객체로 모든 라우트를 합쳐서 다시 익스포트해주는 작업!

module.exports = {
  ...require("./blogRoute"),
  ...require("./commentRoute"),
  ...require("./userRoute"),
};
// 하나의 객체로 모든 라우트를 합쳐서 다시 익스포트해주는 작업!

라우터와 모델들을 일일이 import 해주는 작업이 번거롭다면 위와 같이 index.js 파일을 생성해, 모듈을 좀 더 간결하게 가져올 수 있다.

profile
부정확한 정보나 잘못된 정보는 댓글로 알려주시면 빠르게 수정토록 하겠습니다, 감사합니다!

0개의 댓글