게시판 - Post Error 처리

소스코드

이 게시물에는 코드작성이 포함되어 있습니다. 소스코드를 받으신 후 진행해 주세요. MEAN Stack/개발 환경 구축에서 설명된 프로그램들(git, npm, atom editor)이 있어야 아래의 명령어들을 실행할 수 있습니다.

이 게시물의 소스코드는 게시판 만들기 / 게시판 - Login 기능 추가에서 이어집니다.

board.git 을 clone 한 적이 있는 경우: 터미널에서 해당 폴더로 이동 후 아래 명령어들을 붙여넣기합니다. 폴더 내 모든 코드가 이 게시물의 코드로 교체됩니다. 이를 원치 않으시면 이 방법을 선택하지 마세요.

git reset --hard
git pull
git reset --hard d4b9478
git reset --soft f2cf2d9
npm install
atom .

board.git 을 clone 한 적이 없는 경우: 터미널에서 코드를 다운 받을 폴더로 이동한 후 아래 명령어들을 붙여넣기하여 board.git 을 clone 합니다.

git clone https://github.com/a-mean-blogger/board.git
cd board
git reset --hard d4b9478
git reset --soft f2cf2d9
npm install
atom .

- Github에서 소스코드 보기: https://github.com/a-mean-blogger/board/tree/d4b94788d062d6e535a46fab906b8436a9dffa5f


게시판 - User Error 처리에서처럼 post의 error를 처리해 봅시다.
또한 routes/user.js 에서 사용했던 parseError함수를 post에도 그대로 사용할텐데, 이처럼 여러 파일에서 사용하게 될 함수들을 하나의 module로 분리하겠습니다.

폴더구조

주황색은 변경된 파일, 초록색은 추가된 파일, 회색은 변화가 없는 파일입니다.

코드 - js

// util.js

var util = {};

util.parseError = function(errors){
 var parsed = {};
 if(errors.name == 'ValidationError'){
  for(var name in errors.errors){
   var validationError = errors.errors[name];
   parsed[name] = { message:validationError.message };
  }
 } else if(errors.code == "11000" && errors.errmsg.indexOf("username") > 0) {
  parsed.username = { message:"This username already exists!" };
 } else {
  parsed.unhandled = JSON.stringify(errors);
 }
 return parsed;
}

util.getDate = function(dateObj){
 if(dateObj instanceof Date)
  return dateObj.getFullYear() + "-" + get2digits(dateObj.getMonth()+1)+ "-" + get2digits(dateObj.getDate());
}

util.getTime = function(dateObj){
 if(dateObj instanceof Date)
  return get2digits(dateObj.getHours()) + ":" + get2digits(dateObj.getMinutes())+ ":" + get2digits(dateObj.getSeconds());
}

module.exports = util;

// private functions
function get2digits (num){
 return ("0" + num).slice(-2);
}

원래 routes/users.js에 있던parseError와 model/Post.js에 있던 시간관련 함수들을 module로 만들어 util.js로 분리했습니다.

// routes/users.js

//...
var util  = require("../util"); // 1

// Index ...
// New ...

// create
router.post("/", function(req, res){
 User.create(req.body, function(err, user){
  if(err){
   req.flash("user", req.body);
   req.flash("errors", util.parseError(err)); // 1
   return res.redirect("/users/new");
  }
  res.redirect("/users");
 });
});

// show ...
// edit ...

// update
router.put("/:username",function(req, res, next){
 User.findOne({username:req.params.username})
 .select({password:1})
 .exec(function(err, user){
  if(err) return res.json(err);

  // update user object ...

  // save updated user
  user.save(function(err, user){
   if(err){
    req.flash("user", req.body);
    req.flash("errors", util.parseError(err)); // 1
    return res.redirect("/users/"+req.params.username+"/edit");
   }
   res.redirect("/users/"+req.params.username);
  });
 });
});

//...

1. utilrequire됬고, 기존에parseErrorutil.parseError로 바뀌었습니다.

// model/Post.js

var mongoose = require("mongoose");
var util  = require("../util"); // 1

// schema
var postSchema = mongoose.Schema({
 title:{type:String, required:[true,"Title is required!"]}, // 2
 body:{type:String, required:[true,"Body is required!"]}, // 2
 createdAt:{type:Date, default:Date.now},
 updatedAt:{type:Date},
},{
 toObject:{virtuals:true}
});

// virtuals
postSchema.virtual("createdDate")
.get(function(){
 return util.getDate(this.createdAt); // 1
});

postSchema.virtual("createdTime")
.get(function(){
 return util.getTime(this.createdAt); // 1
});

postSchema.virtual("updatedDate")
.get(function(){
 return util.getDate(this.updatedAt); // 1
});

postSchema.virtual("updatedTime")
.get(function(){
 return util.getTime(this.updatedAt); // 1
});

// model & export
var Post = mongoose.model("post", postSchema);
module.exports = Post;

1. 기존에 있던 시간 관련 함수들이 지워졌고utilrequire된 후util로 부터 각 함수들을 호출합니다.

2. validation과 에러메세지들이 추가되었습니다.

// routes/posts.js

// ...
var util  = require("../util");

// Index ...

// New
router.get("/new", function(req, res){
 var post = req.flash("post")[0] || {};
 var errors = req.flash("errors")[0] || {};
 res.render("posts/new", { post:post, errors:errors });
});

// create
router.post("/", function(req, res){
 Post.create(req.body, function(err, post){
  if(err){
   req.flash("post", req.body);
   req.flash("errors", util.parseError(err));
   return res.redirect("/posts/new");
  }
  res.redirect("/posts");
 });
});

// show ...

// edit
router.get("/:id/edit", function(req, res){
 var post = req.flash("post")[0];
 var errors = req.flash("errors")[0] || {};
 if(!post){
  Post.findOne({_id:req.params.id}, function(err, post){
   if(err) return res.json(err);
   res.render("posts/edit", { post:post, errors:errors });
  });
 } else {
  post._id = req.params.id;
  res.render("posts/edit", { post:post, errors:errors });
 }
});

// update
router.put("/:id", function(req, res){
 req.body.updatedAt = Date.now();
 Post.findOneAndUpdate({_id:req.params.id}, req.body, {runValidators:true}, function(err, post){
  if(err){
   req.flash("post", req.body);
   req.flash("errors", util.parseError(err));
   return res.redirect("/posts/"+req.params.id+"/edit");
  }
  res.redirect("/posts/"+req.params.id);
 });
});

// destroy ...

module.exports = router;

게시판 - User Error 처리에서 변경된 것과 동일하게 변경되었습니다. 상세한 설명은 생략합니다.

한가지 눈여겨 볼 점은 update의 Post.findOneAndUpdate{runValidators:true}이 추가된 것인데요, findOneAndUpdate는 기본설정이 schema에 있는 validation을 작동하지 않도록 되어 있기때문에 이 option을 통해서 validation이 작동하도록 설정해 주어야 합니다.

코드 - ejs

ejs 역시 게시판 - User Error 처리와 동일하게 변경되었기 때문에 설명은 생략합니다.

<!-- views/posts/edit.ejs -->

<!DOCTYPE html>
<html>
 <head>
  <% include ../partials/head %>
 </head>
 <body>
  <% include ../partials/nav %>

  <div class="container post post-edit">
   <% include ./partials/header %>

   <div class="buttons">
    <a class="btn btn-default" href="/posts">Back</a>
   </div>

   <form class="post-form form-horizontal" action="/posts/<%= post._id %>?_method=put" method="post">
    <div class="contentBox">
     <h3 class="contentBoxTop">Edit Post</h3>
     <fieldset>
      <div class="form-group <%= (errors.title)?'has-error':'' %>">
       <label for="title" class="col-sm-2 control-label">Title</label>
       <div class="col-sm-10">
        <input class="form-control" type="text" id="title" name="title" value="<%= post.title %>">
        <% if(errors.title){ %>
         <span class="help-block"><%= errors.title.message %></span>
        <% } %>
       </div>
      </div>
      <div class="form-group <%= (errors.body)?'has-error':'' %>">
       <label for="body" class="col-sm-2 control-label">Body</label>
       <div class="col-sm-10">
        <textarea class="form-control" id="body" name="body" rows="5"><%= post.body %></textarea>
        <% if(errors.body){ %>
         <span class="help-block"><%= errors.body.message %></span>
        <% } %>
       </div>
      </div>
     </fieldset>
    </div>
    <div class="buttons">
     <button type="submit" class="btn btn-default">Submit</button>
    </div>
    <% if(errors.unhandled){ %>
     <div class="alert alert-danger">
      <%= errors.unhandled %>
     </div>
    <% } %>
   </form>

  </div> <!-- container end -->
 </body>
</html>


<!-- views/posts/new.ejs -->

<!DOCTYPE html>
<html>
 <head
  <% include ../partials/head %>
 </head>
 <body>
  <% include ../partials/nav %>

  <div class="container post post-new">
   <% include ./partials/header %>

   <div class="buttons">
    <a class="btn btn-default" href="/posts">Back</a>
   </div>

   <form class="post-form form-horizontal" action="/posts" method="post">
    <div class="contentBox">
     <h3 class="contentBoxTop">New Post</h3>
     <fieldset>
      <div class="form-group <%= (errors.title)?'has-error':'' %>">
       <label for="title" class="col-sm-2 control-label">Title</label>
       <div class="col-sm-10">
        <input class="form-control" type="text" id="title" name="title" value="<%= post.title %>">
        <% if(errors.title){ %>
         <span class="help-block"><%= errors.title.message %></span>
        <% } %>
       </div>
      </div>
      <div class="form-group <%= (errors.body)?'has-error':'' %>">
       <label for="body" class="col-sm-2 control-label">Body</label>
       <div class="col-sm-10">
        <textarea class="form-control" id="body" name="body" rows="5"><%= post.body %></textarea>
        <% if(errors.body){ %>
         <span class="help-block"><%= errors.body.message %></span>
        <% } %>
       </div>
      </div>
     </fieldset>
    </div>
    <div class="buttons">
     <button type="submit" class="btn btn-default">Submit</button>
    </div>
    <% if(errors.unhandled){ %>
     <div class="alert alert-danger">
      <%= errors.unhandled %>
     </div>
    <% } %>
   </form>

  </div> <!-- container end -->
 </body>
</html>

마치며

원래 post error 처리는 숙제로만 남기고 강의로 만들지 않을려고 했는데, 하지 않고 넘어가면 뒤에 이어질 강좌에서 코드가 지저분해 지게 되어서 강의로 추가하게 되었습니다.
다음은 post와 user 간의 관계 설정입니다.

댓글

김남현 2017.11.12
routes/post.js에 있던 parseError ->  routes/users.js 에 있던 parseError 가 되어야 하는 건가요~. 초보는 언제나 확신이 없죠! ㅠ
I
Ian H 2017.11.13
@김남현,
와.. 강의에 오류가 너무 많아서 부끄럽네요ㅠ. 수정했습니다!
김남현 2017.11.20
@Ian H,
아닙니다 :) 엄청 도움이 되고 있습니다. 
H
Hyunsung Kim 2017.12.29
공부한 내용을 정리할 겸 처음부터 다시 만들어 보고 있는데 회원가입 시 하단 <div class="alert alert-danger>....</div> 부분에 {} <- 요렇게만 에러가 뜨는데 이건 뭔지 알 수 있을까요? 에러 내용이라도 있으면 검색해보겠는데;;
I
Ian H 2018.01.03
@Hyunsung Kim,
에러를 console log로 출력해 보세요~ 정 못찾으시면 github에 소스코드를 올려주시면 확인해 보겠습니다^^
댓글쓰기

이 글에 댓글을 다시려면 SNS 계정으로 로그인하세요. 자세히 알아보기

UP