게시판 - Post Error 처리

소스코드

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

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

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

git reset --hard
git pull
git reset --hard 0a4b23b
git reset --soft 4eac806
npm install
atom .

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

git clone https://github.com/a-mean-blogger/board.git
cd board
git reset --hard 0a4b23b
git reset --soft 4eac806
npm install
atom .

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


게시판 - 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;
}

module.exports = util;

원래 routes/users.js에 있던 parseError함수를 module로 만들어 util.js 파일로 분리했습니다. 이처럼 여러 곳에서 공통으로 쓰게 될 함수들은 앞으로 util.js파일에 기록합니다

// routes/users.js

var express = require('express');
var router = express.Router();
var User = require('../models/User');
var util = require('../util'); // 1

...

// 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');
  });
});

...

// update
router.put('/:username', function(req, res, next){
  User.findOne({username:req.params.username})
    .select('password')
    .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));
          return res.redirect('/users/'+req.params.username+'/edit'); // 1
        }
        res.redirect('/users/'+user.username);
      });
  });
});

...

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

// model/Post.js

var mongoose = require('mongoose');

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

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

1. post schema의 커스텀 에러메세지들이 추가되었습니다.

// routes/posts.js

var express  = require('express');
var router = express.Router();
var Post = require('../models/Post');
var util = require('../util'); // 1

...

// 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');
  });
});

...

// 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);
  });
});

...

module.exports = router;

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

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

코드 - ejs

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

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

...

      <form action="/posts/<%= post._id %>?_method=put" method="post">

        <div class="form-group">
          <label for="title">Title</label>
          <input type="text" id="title" name="title" value="<%= post.title %>" class="form-control <%= (errors.title)?'is-invalid':'' %>">
          <% if(errors.title){ %>
            <span class="invalid-feedback"><%= errors.title.message %></span>
          <% } %>
        </div>

        <div class="form-group">
          <label for="body">Body</label>
          <textarea id="body" name="body" rows="5" class="form-control <%= (errors.body)?'is-invalid':'' %>"><%= post.body %></textarea>
          <% if(errors.body){ %>
            <span class="invalid-feedback"><%= errors.body.message %></span>
          <% } %>
        </div>

        <% if(errors.unhandled){ %>
          <div class="invalid-feedback b-block">
            <%= errors.unhandled %>
          </div>
        <% } %>

        <div>
          <a class="btn btn-primary" href="/posts">Back</a>
          <button type="submit" class="btn btn-primary">Submit</button>
        </div>

      </form>

...

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

...

      <form action="/posts" method="post">

        <div class="form-group">
          <label for="title">Title</label>
          <input type="text" id="title" name="title" value="<%= post.title %>" class="form-control <%= (errors.title)?'is-invalid':'' %>">
          <% if(errors.title){ %>
            <span class="invalid-feedback"><%= errors.title.message %></span>
          <% } %>
        </div>
        
        <div class="form-group">
          <label for="body">Body</label>
          <textarea id="body" name="body" rows="5" class="form-control <%= (errors.body)?'is-invalid':'' %>"><%= post.body %></textarea>
          <% if(errors.body){ %>
            <span class="invalid-feedback"><%= errors.body.message %></span>
          <% } %>
        </div>

        <% if(errors.unhandled){ %>
          <div class="invalid-feedback d-block">
            <%= errors.unhandled %>
          </div>
        <% } %>

        <div>
          <a class="btn btn-primary" href="/posts">Back</a>
          <button type="submit" class="btn btn-primary">Submit</button>
        </div>

      </form>

...

실행결과

마치며...

이번 강의는 error 처리의 복습편이였고, 다음강의에는 다시 새로운 내용을 배워보겠습니다!

댓글

이상민 2020.05.07
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');   });    });
이 route함수에서 return을 빼면 오류가 생기는데, 이 오류가 생기는 이유가 res.redirect('/posts/new')를 실행한 다음에 바로 끝나지 않고, 다음 줄로 넘어가서 res.redirect('/posts');를 또 실행하기 때문인가요? 이 부분은 계속 헷갈려서... 어떻게 이해해야 할까요? 1. return을 빼면 왜 바로 '/posts/new'경로로 이동하지 않고 다음 줄로 넘어가나요? 2. res.redirect함수가 정확히 어떤 방식으로 동작하나요?
I
Ian H 2020.05.08
@이상민,
맞습니다. res.redirect에서 return을 해주지 않으면 다음 줄의 코드가 계속 실행되는데, 다시 res.redirect를 만나서 에러를 냅니다.
Post.create(req.body, function(err, post){...});  를 보시면. Post.create이라는 함수에 인자(parameter)를 두개 전달하고 있는 형태로, 첫번째 인자는 생성할 오브젝트 두번째 인자는 생성후 결과를 처리하는 함수입니다. 
function(err, post){   if(err) {     req.flash('post', req.body);     req.flash('errors', util.parseError(err));     return res.redirect('/posts/new');   }   res.redirect('/posts'); } 
위 함수가  Post.create 함수에 인자로 전달되었는데, return 키워드는 함수내에서 함수를 종료시키고 값을 반환하는 역할을 합니다. 
위 함수에서는  return res.redirect('/posts/new') 를 반환하고 있는데, 사실 이 반환 차제는 의미가 없고 함수를 종료시키는 역할로 쓰였습니다. 그러므로
res.redirect('/posts/new'); return;
와 같이 쓰는 것이 의미적으로는 더 정확하다고 할 수 있죠.
1. return을 빼면 왜 바로 '/posts/new'경로로 이동하지 않고 다음 줄로 넘어가나요? 위에서 설명한 것 처럼 return이라는 키워드 자체가 함수를 종료시키기 때문입니다.
2. res.redirect함수가 정확히 어떤 방식으로 동작하나요? res.redirect 함수가 호출되는 시점에 전달받은 url로 브라우저 주소를 변경하는 것과 같은 효과를 냅니다. (더 자세히 알고 싶다면 http://expressjs.com/en/5x/api.html#res.redirect 를 읽어보세요) res.redirect 함수의 작동 원리를 질문하신 거라면 저는 모르고, 알 필요도 없습니다. Express 프레임워크를 만든 사람이 알겠죠. 프레임워크를 사용하는 개발자들은 해당 함수의 코드를 알 필요가 없습니다.
이해가 잘 안되시면 다시 질문해주세요^^
이상민 2020.05.08
@Ian H,
자세하게 답변해주셔서 감사합니다. 계속 저 부분에 대해서 애매하게 이해하고 있었는데, 자세하게 설명해주신 덕분에 이해했네요. 그리고 어떤 프레임워크를 사용하기 위해서는 그 프레임워크에 대한 사용법을 익히는 것이지, 내부의 동작원리를 낱낱이 파헤치는 건 아니라는 말씀이네요...
그러니까.. 프레임워크는 개발자들이 좀 더 생산성 있고, 효율적이게 개발할 수 있도록 만들어진 것인데 굳이 배우는 입장에서 동작 원리나 "전부 내것으로 만들겠어!!"라는 마인드는 비효율적이고, 지금 당장 내가 필요한 부분들만 골라서 활용하면 되는 거예요?? 예를들면 종이에 "안녕하세요"를 적으라고 연필을 줬더니, 연필이 언제,어떻게,왜 만들어지는지에 대해 알려고 하는 것과 마찬가지겠네요... 적으면서 생각해보니 정말 비효율적이네요... 덕분에 새로운걸 알고 갑니다. 감사합니다.
I
Ian H 2020.05.08
@이상민,
정확합니다. 물론 그쪽에 흥미가 있고 관심이 있다면 깊게 파고들어 공부하실 수도 있습니다만 그건 제가 몰라서 도와드릴 수 없는 영역입니다 ㅠ
댓글쓰기

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

UP