이 게시물에는 코드작성이 포함되어 있습니다. 소스코드를 받으신 후 진행해 주세요. MEAN Stack/개발 환경 구축에서 설명된 프로그램들(git, npm, atom editor)이 있어야 아래의 명령어들을 실행할 수 있습니다.
이 게시물의 소스코드는 주소록 만들기 / 주소록 - Index, New, Create에서 이어집니다.
contact-book.git 을 clone 한 적이 있는 경우: 터미널에서 해당 폴더로 이동 후 아래 명령어들을 붙여넣기합니다. 폴더 내 모든 코드가 이 게시물의 코드로 교체됩니다. 이를 원치 않으시면 이 방법을 선택하지 마세요.
contact-book.git 을 clone 한 적이 없는 경우: 터미널에서 코드를 다운 받을 폴더로 이동한 후 아래 명령어들을 붙여넣기하여 contact-book.git 을 clone 합니다.
- Github에서 소스코드 보기: https://github.com/a-mean-blogger/contact-book/tree/1107dbb0f79178175fbae334ff24399ec3bce5d5
7 actions 중 나머지 show, edit, update, destroy를 구현해 봅시다.
전체 목록(index)에서 하나를 선택하면 해당 데이터를 보여주고(show)
해당 데이터를 수정할 수 있는 form을 만들어서 이 정보를 서버로 전달할 수 있게 하고(edit)
서버가 이 정보를 사용해서 DB에 정보를 수정하고(update)
해당 데이터를 삭제할 수도 있습니다(destroy).
주황색은 변경된 파일, 녹색은 새로 생성된 파일, 회색은 변화가 없는 파일입니다.
7 actions 중 update과 destroy는 HTTP Methods 중 put과 delete을 사용하는데, 대부분의 브라우저의 form은 get과 post 만을 허용하고 나머지는 허용하지 않습니다. 브라우저에서 허용하진 않지만 나중에 API로 연결할 때는 문제가 없기 때문에 HTTP를 올바르게 사용하는 법을 익히는 것이 더 중요합니다. 지금은 일단 method override라는 package를 설치하여 이를 우회하도록하겠습니다.
$ npm install --save method-override
이 package는 query로 method 값을 받아서 request의 HTTP method를 바꿔주는 역할을 합니다.
해당 부분은 코드에서 살펴보겠습니다.
// index.js 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.deleteOne({_id:req.params.id}, function(err){ 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를 찾은 후 콜백 함수를 호출합니다. 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함수로 넘겨지는 값은 수정되기 전의 값입니다. 만약 업데이트 된 후의 값을 보고 싶다면 콜백 함수 전에 parameter로 {new:true}를 넣어주면 됩니다.
Data 수정 후 "/contacts/"+req.params.id로 redirect합니다.
// Contacts - destroy // 6 app.delete('/contacts/:id', function(req, res){ Contact.deleteOne({_id:req.params.id}, function(err){ if(err) return res.json(err); res.redirect('/contacts'); }); });
6. "contacts/:id"에 delete 요청이 오는 경우 :
Model.deleteOne
은 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"> <h2>Show</h2> <div> <h3><%= contact.name%></h3> <label>Email</label><span><%= contact.email %></span> <br> <label>Phone</label><span><%= contact.phone %></span> </div> <div class="contact-menu"> <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> </form> </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"> <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에 해당 data의 값들이 미리 들어 있고, form action이 다릅니다.
put 역시 form에서 사용할 수 없으므로 _method=put을 사용하였습니다.
/* public/css/master.css */ .contact h2{ color: tomato; } .contact .contact-form label, .contact-show label{ display: inline-block; width: 70px; } .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을 사용하여 코드를 정리하는 법을 알아보겠습니다.
댓글
이 글에 댓글을 다시려면 SNS 계정으로 로그인하세요. 자세히 알아보기