오상우
오상우

Categories

  • back-end
  • mongodb
  • mongoose
  • tips
  • web

Mongoose 는 mongodb 를 더욱 편리하게 쓸 수 있도록 도와주는 nodejs 라이브러리이다.
오늘은 Mongoose 의 여러 편리한 기능 중 model hook 에 대해 알아보자.

Mongoose Model

Mongoose model 은 Mongodb 의 collection 을 하나의 객체로 연결해 db 를 쉽게 다룰 수 있게해준다. 나는 지금까지 model 을 사용할 때, 해당 collection 의 스키마 정도만 정의해서 사용했었다.

// models/post.js 파일
...

const postSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true
  },
  date: Date,
  content: String
});

const Post = mongoose.model('Post', postSchema);

module.export = Post;

간단한 어플리케이션에선 문제가 없었지만, 위와 같이 모델 파일이 간단하면 디비가 복잡해질 때, 추가되는 db 관리 로직이 라우팅 파일에 들어간다는 뜻이 된다.
예를 들어, post 디비에 comment 라고하는 디비가 1:n 으로 참조된다고 생각해보자.

// routes/commentRoutes.js 파일
...

// 새로운 comment 생성 라우트
router.post('/posts/:post_id/comments', async (req, res) => {
  try {
    const post = await Post.findById(req.params.post_id); // 우선 생성되는 comment를 참조할 post doc를 찾는다.
    if (!post) { // 존재여부 유효성 검사
      throw new Error('post not found!');
    }
    const createdComment = await Comment.create(req.body); // comment doc 생성
    post._comments.push(createdComment._id); // post doc의 reference field에 생성된 comment doc 참조값 추가
    await post.save();

    res.status(200).send(createdComment); // 성공
  } catch (e) {
    res.status(400).send(e); // 에러 처리
  }
})

단순한 1:n 참조도 위와 같이 코드가 길어지는데, 만약 더 많은 collection 들이 생기고 서로 참조한다고 생각해 보라! 얼마나 많을 로직을 하나의 라우트에서 처리해야 할 지, 생각만으로도 끔찍하다.

하지만 우리에겐 Mongoose Model 이 있다.

// models/comment.js 파일
...

async function validatePostId(next) { //  this binding이 필요하기에 arrow function은 쓸 수 없다.
  const post = await Post.findById(this._post); // 여기서 this는 저장될 doc객체이다.
  if (!post) {
    return next('post not found!'); // next 콜백함수를 실행해야 model 미들웨어가 다음으로 진행한다. next함수에 인자를 주면 에러가 발생했음을 인지한다.
  }
  next();
}

async function updatePost(doc, next) {
  try {
    const post = Post.findById(doc._post);
    post._comments.push(doc._id);
    await post.save();

    next();
  } catch (e) {
    next(e);
  }
}

commentSchema.pre('save', validatePostId); // pre 는 첫번째 인자로 들어온 행동을 하기 전에 두번째 인자 콜백을 실행하게 만든다.
commentSchema.post('save', updatePost); // post 는 첫번째 인자로 들어온 행동을 한 후에 두번째 인자 콜백을 실행하게 만든다.

const Comment = mongoose.model('Comment', commentSchema);

module.exports = Comment;

위 코드는 잠재적으로 수많은 코드와, 에러들과, 유지보수에 드는 노력을 줄일 수 있다. 앞으로 모든 comment model 객체들은 save 를 하기 전/후에 위 콜백들을 실행하며 자동적으로 db 관리를 할 것이다!!