주소록 - Show, Edit, Update, Destroy

소스코드

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

이 게시물의 소스코드는 주소록 만들기 / 주소록 - Index, New, Create에서 이어집니다.

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

git reset --hard
git pull
git reset --hard 89a2e98
git reset --soft c1403b9
npm install
atom .

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

git clone https://github.com/a-mean-blogger/contact-book.git
cd contact-book
git reset --hard 89a2e98
git reset --soft c1403b9
npm install
atom .

- Github에서 소스코드 보기: https://github.com/a-mean-blogger/contact-book/tree/89a2e98659e8fa6fb24c604fc2d6414883eb392a


7 actions 중 나머지 show, edit, update, destroy를 구현해 봅시다.

전체 목록(index)에서 하나를 선택하면 해당 데이터를 보여주고(show)
해당 데이터를 수정할 수 있는 form을 만들어서 이 정보를 서버로 전달할 수 있게 하고(edit)
서버가 이 정보를 사용해서 DB에 정보를 수정하고(update)
해당 데이터를 삭제할 수도 있습니다(destroy).

폴더구조

주황색은 변경된 파일, 녹색은 새로 생성된 파일, 회색은 변화가 없는 파일입니다.

Package 설치

7 actions 중 update과 destroy는 HTTP Methods 중 put과 delete을 사용하는데, 대부분의 브라우저의 form은 get과 post 만을 허용하고 나머지는 허용하지 않습니다. method override package를 설치하여 이를 우회할 수 있습니다.

$ npm install --save method-override

이 package는 다른 일을 하는 것이 아니라 query로 method를 받아서 request의 HTTP method를 바꿔주는 역할을 합니다.

해당 부분은 코드에서 살펴보겠습니다.

코드

var express = require("express");
var mongoose = require("mongoose");
var bodyParser = require("body-parser");
var methodOverride = require("method-override"); // 1 
var app = express();

// DB setting ...

// Other settings
app.set("view engine", "ejs");
app.use(express.static(__dirname+"/public"));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended:true}));
app.use(methodOverride("_method")); // 2 

// DB schema ...

// Routes
// Home ...
// Contacts - Index ...
// Contacts - New ...
// Contacts - create ...
// Contacts - show // 3 
app.get("/contacts/:id", function(req, res){
 Contact.findOne({_id:req.params.id}, function(err, contact){
  if(err) return res.json(err);
  res.render("contacts/show", {contact:contact});
 });
});
// Contacts - edit // 4 
app.get("/contacts/:id/edit", function(req, res){
 Contact.findOne({_id:req.params.id}, function(err, contact){
  if(err) return res.json(err);
  res.render("contacts/edit", {contact:contact});
 });
});
// Contacts - update // 5 
app.put("/contacts/:id", function(req, res){
 Contact.findOneAndUpdate({_id:req.params.id}, req.body, function(err, contact){
  if(err) return res.json(err);
  res.redirect("/contacts/"+req.params.id);
 });
});
// Contacts - destroy // 6
app.delete("/contacts/:id", function(req, res){
 Contact.remove({_id:req.params.id}, function(err, contact){
  if(err) return res.json(err);
  res.redirect("/contacts");
 });
});


// Port setting ...

...으로 표시된 부분은 이전과 변화가 없는 부분입니다.

새로 추가된 부분들을 각각 살펴봅시다.

var methodOverride = require("method-override"); // 1 

1. method-override module을 methodOverride변수에 담습니다.

app.use(methodOverride("_method")); // 2 

2. _method의 query로 들어오는 값으로 HTTP method를 바꿉니다. 예를들어 http://example.com/category/id?_method=delete를 받으면 _method의 값인 delete을 읽어 해당 request의 HTTP method를 delete으로 바꿉니다.

// Contacts - show // 3 
app.get("/contacts/:id", function(req, res){
 Contact.findOne({_id:req.params.id}, function(err, contact){
  if(err) return res.json(err);
  res.render("contacts/show", {contact:contact});
 });
});

3. "contacts/:id"에 get 요청이 오는 경우 :

:id처럼 route에 콜론(:)을 사용하면 해당 위치의 값을 받아 req.params에 넣게 됩니다. 예를 들어 "contacts/abcd1234"가 입력되면 "contacts/:id" route에서 이를 받아 req.params.id에 "abcd1234"를 넣게 됩니다.

Model.findOne은 DB에서 해당 model의 document를 하나 찾는 함수입니다. 첫번째 parameter로 찾을 조건을 object로 입력하고 data를 찾은 후 callback함수를 호출합니다. Model.find와 비교해서 Model.find는 조건에 맞는 결과를 모두 찾아 array로 전달하는데 비해 Model.findOne은 조건에 맞는 결과를 하나 찾아 object로 전달합니다. (검색 결과가 없다면 null이 전달됩니다.)

위 경우에는 {_id:req.params.id}를 조건으로 전달하고 있는데, 즉 DB의 contacts collection에서 _id가 req.params.id와 일치하는 data를 찾는 조건입니다.

에러가 없다면 검색 결과를 받아 views/contacts/show.ejs를 render합니다.

// Contacts - edit // 4 
app.get("/contacts/:id/edit", function(req, res){
 Contact.findOne({_id:req.params.id}, function(err, contact){
  if(err) return res.json(err);
  res.render("contacts/edit", {contact:contact});
 });
});

4. "contacts/:id/edit"에 get 요청이 오는 경우 :

Model.findOne이 다시 사용되었습니다. 검색 결과를 받아 views/contacts/edit.ejs를 render합니다.

// Contacts - update // 5 
app.put("/contacts/:id", function(req, res){
 Contact.findOneAndUpdate({_id:req.params.id}, req.body, function(err, contact){
  if(err) return res.json(err);
  res.redirect("/contacts/"+req.params.id);
 });
});

5. "contacts/:id"에 put 요청이 오는 경우 :

Model.findOneAndUpdate는 DB에서 해당 model의 document를 하나 찾아 그 data를 수정하는 함수입니다. 첫번째 parameter로 찾을 조건을 object로 입력하고 두번째 parameter로 update할 정보를 object로 입력data를 찾은 후 callback함수를 호출합니다.
이때 callback함수로 넘겨지는 값은 수정되기 전의 값입니다. 만약 업데이트 된 후의 값을 보고 싶다면 callback 함수 전에 parameter로 {new:true}를 넣어주면 됩니다.

Data 수정 후 "/contacts/"+req.params.id로 redirect합니다.

// Contacts - destroy // 6
app.delete("/contacts/:id", function(req, res){
 Contact.remove({_id:req.params.id}, function(err){
  if(err) return res.json(err);
  res.redirect("/contacts");
 });
});

6. "contacts/:id"에 delete 요청이 오는 경우 :

Model.remove는 DB에서 해당 model의 document를 하나 찾아 삭제하는 함수입니다. 첫번째 parameter로 찾을 조건을 object로 입력하고 data를 찾은 후 callback함수를 호출합니다.

Data 삭제후 "/contacts"로 redirect합니다.

다음으로 view 파일들을 살펴봅시다.

<!-- views/contacts/index.ejs-->

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

  <div class="contact contact-index">
   <h2>Index</h2>
   <ul>
    <% contacts.forEach(function(contact) { %>
     <li>
      <a href="/contacts/<%= contact._id %>"><%= contact.name %></a> <!-- 1 -->
     </li>
    <% }) %>
   </ul>
  </div>
 </body>
</html>

1. 이름에 a tag를 추가하여 show action과 연결했습니다.

<!-- views/contacts/show.ejs -->

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

  <div class="contact contact-show">
   <div class="contact-menu">
    <a href="/contacts">Back</a>
    <a href="/contacts/<%= contact._id %>/edit">Edit</a> <!-- 1 -->
    <form action="/contacts/<%= contact._id %>?_method=delete" method="post"> <!-- 2 -->
     <a href="#" onclick="confirm('Do you want to delete this?')?this.parentElement.submit():null;">Delete</a> <!-- 3 -->
    </form>
   <div>
   <h2>Show</h2>
   <h3><%= contact.name%></h3>
   <div>
    <label>Email</label><span><%= contact.email %></span>
   </div>
   <div>
    <label>Phone</label><span><%= contact.phone %></span>
   </div>
  </div>
 </body>
</html>

1. a tag로 Edit을 만들어 edit action에 연결했습니다.

2. a tag로는 get만 요청할 수 있기 때문에 Delete은 form으로 처리해야 합니다. form에서도 post밖에 요청할 수 없기 때문에 ?_method=delete이 action에 query로 추가되었습니다. 이 부분은 서버의 method override package에 의해 처리되어 delete 요청으로 변경됩니다.

3. Delete을 눌렀을때 onclick을 사용해서 confirm을 띄우고 yes인 경우 form을 submit하게 합니다.

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

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

  <div class="contact contact-edit">
   <div class="contact-menu">
    <a href="/contacts/<%= contact._id %>">Back</a>
   </div>
   <h2>Edit</h2>
   <form class="contact-form" action="/contacts/<%= contact._id %>?_method=put" method="post">
    <div>
     <label for="name">Name</label>
     <input type="text" id="name" name="name" value="<%= contact.name %>">
    </div>
    <div>
     <label for="email">Email</label>
     <input type="text" id="email" name="email" value="<%= contact.email %>">
    </div>
    <div>
     <label for="phone">Phone</label>
     <input type="text" id="phone" name="phone" value="<%= contact.phone %>">
    </div>
    <div>
     <button type="submit">Submit</button>
    </div>
   </div>
  </form>
 </body>
</html>

edit.ejs는 new.ejs와 거의 같지만 값들이 form에 들어 있고, form action이 update action을 실행하게 한다는 점만 다릅니다.

put 역시 form에서 사용할 수 없으므로 _method=put을 사용하였습니다.

/* public/css/master.css */

.contact h2{
 color: tomato;
}
.contact .contact-form label,
.contact-show label{
 display: inline-block;
 width: 50px;
}
.contact .contact-menu form{
 display: inline-block;
}

CSS 는 따로 설명하지 않겠습니다.

실행결과

nodemon으로 실행결과를 확인합시다.

$ nodemon

contacts/new로 들어가면 데이터를 생성할 수 있는 form이 나옵니다.

새로운 데이터를 생성한 후 submit 합니다.

test가 생성되었습니다.

test를 클릭하면 Edit과 Delete이 새로 생긴 것이 보입니다.

Edit을 클릭하면 값을 수정할 수 있는 form이 나옵니다.

값을 수정한 후 submit합니다.

값이 변경되었습니다.

Delete을 눌러봅시다.

경고가 보입니다. Ok를 누릅니다.

값이 지워졌습니다.

마치며..

이로서 CRUD가 모두 되는 주소록의 기능이 완성되었습니다.
또한 index.js 파일에 제법 코드가 많아졌습니다. 다음 강의에서는 코드가 더 복잡해지기 전에 Node.js Module을 사용하여 코드를 정리하는 법을 알아보겠습니다.

댓글

Deleted Comment
댓글쓰기

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

UP