이 게시물에는 코드작성이 포함되어 있습니다. 소스코드를 받으신 후 진행해 주세요. MEAN Stack/개발 환경 구축에서 설명된 프로그램들(git, npm, atom editor)이 있어야 아래의 명령어들을 실행할 수 있습니다.
이 게시물의 소스코드는 주소록 만들기 / 주소록 - 프로젝트 생성 및 mongoose로 DB 연결에서 이어집니다.
contact-book.git 을 clone 한 적이 있는 경우: 터미널에서 해당 폴더로 이동 후 아래 명령어들을 붙여넣기합니다. 폴더 내 모든 코드가 이 게시물의 코드로 교체됩니다. 이를 원치 않으시면 이 방법을 선택하지 마세요.
contact-book.git 을 clone 한 적이 없는 경우: 터미널에서 코드를 다운 받을 폴더로 이동한 후 아래 명령어들을 붙여넣기하여 contact-book.git 을 clone 합니다.
- Github에서 소스코드 보기: https://github.com/a-mean-blogger/contact-book/tree/b6b21108adc9e160e2e92ef24c2cda6ef1eb2890
7 actions 중 index, new, create을 구현해 봅시다.
이름, 이메일 주소, 전화번호를 받는 form을 만들어서 이 정보를 서버로 전달할 수 있게 하고(new)
서버가 이 정보를 사용해서 DB에 정보를 생성하고(create)
생성된 주소록의 이름 목록을 보여 줍니다(index).
주황색은 변경된 파일, 녹색은 새로 생성된 파일, 회색은 변화가 없는 파일입니다.
웹브라우저의 form으로 전송된 data를 서버에서 쉽게 사용하기 위해 body-parser package를 설치합니다.
$ npm install --save body-parser
// index.js var express = require('express'); var mongoose = require('mongoose'); var bodyParser = require('body-parser'); // 1 var app = express(); // DB setting ... // Other settings app.set('view engine', 'ejs'); app.use(express.static(__dirname+'/public')); app.use(bodyParser.json()); // 2 app.use(bodyParser.urlencoded({extended:true})); // 3 // DB schema // 4 var contactSchema = mongoose.Schema({ name:{type:String, required:true, unique:true}, email:{type:String}, phone:{type:String} }); var Contact = mongoose.model('contact', contactSchema); // 5 // Routes // Home // 6 app.get('/', function(req, res){ res.redirect('/contacts'); }); // Contacts - Index // 7 app.get('/contacts', function(req, res){ Contact.find({}, function(err, contacts){ if(err) return res.json(err); res.render('contacts/index', {contacts:contacts}); }); }); // Contacts - New // 8 app.get('/contacts/new', function(req, res){ res.render('contacts/new'); }); // Contacts - create // 9 app.post('/contacts', function(req, res){ Contact.create(req.body, function(err, contact){ if(err) return res.json(err); res.redirect('/contacts'); }); }); // Port setting ...
...으로 표시된 부분은 이전코드와 비교해서 변화가 없는 부분입니다.
새로 추가된 부분들을 각각 살펴봅시다.
var bodyParser = require('body-parser'); // 1
1. body-parser module를 bodyPaser 변수에 담습니다.
app.use(bodyParser.json()); // 2 app.use(bodyParser.urlencoded({extended:true})); // 3
2 & 3. bodyParser를 사용하기 위해 필요한 코드들입니다. 2번은 json 형식의 데이터를 받는다는 설정이고, 3번은 urlencoded data를 extended 알고리듬을 사용해서 분석한다는 설정입니다. 2번을 설정하면, route의 callback함수(function(req, res, next){...}
)의 req.body
에서 form으로 입력받은 데이터를 사용할 수 있습니다.
이 부분이 지금 이해가 안가시면 이렇게 처리를 해 줘야 웹브라우저의 form에 입력한 데이터가 bodyParser를 통해 req.body으로 생성이 된다는 것만 아셔도 괜찮습니다.
// DB schema // 4 var contactSchema = mongoose.Schema({ name:{type:String, required:true, unique:true}, email:{type:String}, phone:{type:String} })
4. mongoose.Schema
함수로 DB에서 사용할 schema를 설정합니다. 데이터베이스에 정보를 어떠한 형식으로 저장할 지를 지정해 주는 부분입니다.
contact라는 형태의 데이터를 DB에 저장할 텐데, 이 contact는 name, email, phone의 항목들을 가지고 있으며 새 항목 모두 String 타입입니다. name은 값이 반드시 입력되어야 하며(required
), 값이 중복되면 안된다(unique
)는 추가 설정이 있습니다.
이 예제에서는String
만 사용했지만, 필요에 따라 Number, Date, Boolean 등등 다양한 타입들을 설정할 수 있습니다. 스키마의 타입과 추가 설정들에 대해서 자세히 알아보려면 공식사이트의 해당 페이지(http://mongoosejs.com/docs/schematypes.html)를 읽어보세요.
var Contact = mongoose.model('contact', contactSchema); //5
5. mongoose.model
함수를 사용하여 contact schema의 model을 생성합니다.
mongoose.model
함수의 첫번째 parameter는 mongoDB에서 사용되는 콜렉션의 이름이며, 두번째는 mongoose.Schema
로 생성된 오브젝트입니다. DB에 있는 contact라는 데이터 콜렉션을 현재 코드의 Contact라는 변수에 연결해 주는 역할을 합니다.
생성된 Contact object는 mongoDB의 contact collection의 model이며 DB에 접근하여 data를 변경할 수 있는 함수들을 가지고 있습니다. 만약 schema와 model이 뭔지 잘 모르겠다면 토막글/ORM(Object relational mapping)과 Model을 먼저 읽어주세요.
**참고로 DB에 contact라는 콜렉션이 미리 존재하지 않더라도 괜찮습니다. Mongoose가 없는 콜렉션을 알아서 생성합니다.
다음으로 route설정입니다.
// Home // 6 app.get('/', function(req, res){ res.redirect('/contacts'); });
6. "/"에 get 요청이 오는 경우 :
/contacts로 redirect
하는 코드입니다.
// Contacts - Index // 7 app.get('/contacts', function(req, res){ Contact.find({}, function(err, contacts){ if(err) return res.json(err); res.render('contacts/index', {contacts:contacts}); }); });
7. "/contacts"에 get 요청이 오는 경우 :
에러가 있다면 에러를 json형태로 웹브라우저에 표시하고, 에러가 없다면 검색 결과를 받아 views/contacts/index.ejs를 render
(페이지를 다이나믹하게 제작)합니다.
Contact.find({}, function(err, contacts){ ... })
를 살펴봅시다. 이 부분을 일반화시키면 모델.find(검색조건, callback_함수)
로 나타낼 수 있습니다.
모델.find(검색조건, 콜백_함수)
를 모델.find
, 검색조건, 콜백_함수 세부분으로 나누어서 자세히 살펴봅시다.
- 모델.find
함수는 DB에서 검색조건에 맞는 모델(이 강의에서는 Contact) data(배열)를 찾고 콜백_함수를 호출하는 함수입니다.
- 모델.find
의 검색조건은 Object 형태로 전달됩니다. 예를들어 {lastName:"Kim"}이라면 모델들 중에 lastName 항목의 값이 "Kim"인 모델들을 찾는 조건이 됩니다. 빈 Object({}
)를 전달하는 경우(=검색조건 없음) DB에서 해당 모델의 모든 data를 return합니다.
- 모델.find
의 콜백_함수는 function(에러, 검색결과)
의 형태입니다(function(err, contacts){ ... }
부분). 첫번째 parameter인 에러(여기서는 err)는 error가 있는 경우에만 내용이 전달됩니다. 즉 if(err)
로 에러가 있는지 없는지를 알 수 있습니다. 두번째 parameter인 검색결과(여기서는 contacts)는 한개 이상일 수 있기 때문에 검색결과는 항상 array이며 심지어 검색 결과가 없는 경우에도 빈 array[]
를 전달합니다. 검색결과가 array임을 나타내기 위해 parameter이름으로 contact의 복수형인 contacts를 사용합니다.
// Contacts - New // 8 app.get('/contacts/new', function(req, res){ res.render('contacts/new'); });
8. "/contacts/new"에 get 요청이 오는 경우 :
새로운 주소록을 만드는 form이 있는 views/contacts/new.ejs를 render
합니다.
// Contacts - create // 9 app.post('/contacts', function(req, res){ Contact.create(req.body, function(err, contact){ if(err) return res.json(err); res.redirect('/contacts'); }); });
9. "/contacts"에 post 요청이 오는 경우 :
"/contacts/new"에서 폼을 전달받는 경우입니다.
모델.create
은 DB에 data를 생성하는 함수입니다. 첫번째 parameter로 생성할 data의 object(여기서는 req.body)를 받고, 두번째 parameter로 콜백 함수를 받습니다.
모델.create
의 callback 함수(여기서는 function(err, contact){ ... }
부분) 는 첫번째 parameter로 error를 받고 두번째 parameter로 생성된 data를 받습니다. 생성된 data는 항상 하나이므로 parameter이름으로 단수형인 contact를 사용하였습니다.
에러없이 contact data가 생성되면 /contacts로 redirect
합니다.
다음으로 view 파일들을 살펴봅시다.
두개의 페이지가 있고 두개의 partial이 있습니다. partial은 여러 페이지들에 중복되는 부분의 코드를 따로 빼 놓은 것입니다.
<!-- views/partials/head.ejs --> <link rel="stylesheet" href="/css/master.css"> <title>Contact</title>
먼저 head tag에 들어갈 부분입니다.
css와 title을 모든 페이지가 공유하게 하였습니다.
<!-- views/partials/nav.ejs --> <nav> <div>Contact Book</div> <ul> <li><a href="/contacts">Index</a></li> <li><a href="/contacts/new">New</a></li> </ul> </nav>
페이지마다 표시될 menu부분입니다. index와 new view로 이동하는 메뉴를 가지고 있습니다.
<!-- views/contacts/index.ejs --> <!DOCTYPE html> <html> <head> <%- include('../partials/head') %> <!-- 1 --> </head> <body> <%- include('../partials/nav') %> <!-- 2 --> <div class="contact contact-index"> <h2>Index</h2> <ul> <% contacts.forEach(function(contact) { %> <!-- 3 --> <li> <%= contact.name %> </li> <% }) %> </ul> </div> </body> </html>
1&2. <%- include %>
를 사용해서 외부 ejs(head와 nav)를 가져왔습니다.
3. contacts object에 forEach를 사용해서 코드를 반복해서 표시하고있습니다.
<!-- views/contacts/new.ejs --> <!DOCTYPE html> <html> <head> <%- include('../partials/head') %> </head> <body> <%- include('../partials/nav') %> <div class="contact contact-new"> <h2>New</h2> <form class="contact-form" action="/contacts" method="post"> <div> <label for="name">Name</label> <input type="text" id="name" name="name" value=""> </div> <div> <label for="email">Email</label> <input type="text" id="email" name="email" value=""> </div> <div> <label for="phone">Phone</label> <input type="text" id="phone" name="phone" value=""> </div> <div> <button type="submit">Submit</button> </div> </form> </div> </body> </html>
새로운 주소를 만드는 form입니다.
form이 submit되면 "/contacts"에 post를 요청하게 됩니다.
/* public/css/master.css */ .contact h2{ color: tomato; } .contact .contact-form label{ display: inline-block; width: 70px; }
CSS 스타일링은 거의 하지 않았습니다. 일단 웹페이지의 기능을 완성한 다음 마지막에 스타일링을 하도록 하겠습니다.
nodemon으로 실행결과를 확인합시다.
$ nodemon
localhost:3000로 접속하면 자동으로 /contacts로 redirect됩니다.
처음 실행하면 생성한 자료가 없으므로 아무런 자료를 보여주지 않습니다. New 링크를 클릭합니다.
주소록을 생성할 수 있는 form이 보입니다. data를 입력하고 submit 버튼을 누릅니다.
/contacts로 redirect되고 입력한 이름이 표시됩니다.
만약 이름을 입력하지 않고 form을 submit하면 에러가 표시됩니다.
동일한 이름을 입력해도 에러가 표시됩니다.
에러의 구조가 다른 것을 볼 수 있는데, 이름이 반드시 필요한 것은 DB로 data를 보내기 전에 mongoose에서 확인해서 error를 낸 것이고, 이름의 중복확인은 DB에서 error를 내는 것이기 때문입니다.
다음 강의에서는 7 actions의 나머지 show, edit, update, destroy를 추가하겠습니다.
댓글
이 글에 댓글을 다시려면 SNS 계정으로 로그인하세요. 자세히 알아보기