주소록 - Index, New, Create

소스코드

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

이 게시물의 소스코드는 주소록 만들기 / 주소록 - 프로젝트 생성 및 mongoose로 DB 연결에서 이어집니다.

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

git reset --hard
git pull
git reset --hard b6b2110
git reset --soft d7606be
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 b6b2110
git reset --soft d7606be
npm install
atom .

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


7 actions 중 index, new, create을 구현해 봅시다.
이름, 이메일 주소, 전화번호를 받는 form을 만들어서 이 정보를 서버로 전달할 수 있게 하고(new)
서버가 이 정보를 사용해서 DB에 정보를 생성하고(create)
생성된 주소록의 이름 목록을 보여 줍니다(index).

폴더 구조

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

Package 설치

웹브라우저의 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 형태로 전달되는데 빈 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를 추가하겠습니다.

댓글

오오구 (오구) 2016.10.13
따라하다가 보니 문서상단에 폴더구조 이미지에 나와있는 view/partials/header.ejs 가 view/partials/nev.ejs였네요.. 보고 파일부터 만든 후 코드를 따라가니 계속 오류가나서 좀 헤맸습니다..ㅎㅎ mean은 처음 공부해보는데 따라할 수 록 재밋네요
I
Ian H 2016.10.17
@오오구 (오구),
앗, 그렇네요! 수정하도록 하겠습니다. nodejs 재밌죠 ㅋ 제가 제일 좋아하는 언어입니다.
김남현 2016.10.15
ㅎㅎ, 네이버에서 게시판 강좌 잘 보고 이곳으로 넘어왔습니다. 네이버에서 한번 말씀드린바 있는데(제 네이버 아이디는 sonsory입니다) 다음주 중으로 아주 단순한 형태의 게임이지만, 재미있는 알고리즘이 적용된 숫자게임을 오픈합니다. ㅎㅎ. 오픈하게 되면 사이트주소 남길게요 ㅋㅋ
I
Ian H 2016.10.17
@김남현,
반갑습니다^^ 오. 게임 사이트 만드시는 군요! 기대하겠습니다!
박수진 2017.10.02
res.render("contacts/index", {contacts:contacts}); 이부분에서 두번째 인자로 받는게 어떤건지 알려주실 수 있나요? api를 찾아봐도 이해가 잘 가지않아서요 ㅜㅜ
I
Ian H 2017.10.05
@박수진,
함수(첫번째 인자, 두번째 인자, 세번째 인자, ...) 이므로 res.render("contacts/index", {contacts:contacts})에서 두번째 인자로 받는 것은 {contacts:contacts}입니다.
index.js 코드 7번 부분  말씀하시는거죠? 해당부분 설명이 좀 애매한것 같아서 수정했습니다. 다시 한번 확인해 주시고 그래도 이해가 되지 않으면 다시 질문해 주세요^^
박수진 2017.10.07
@Ian H,
앗 다시보니 제가 질문을 너무 요상하게 썼네요! 두번째 인자{contacts:contacts}이 부분이 어디서 나온 건 지 이해가 안됩니다. 제 생각으론 DB안에 key와 values 개념같긴 한데 맞나요? 저게 무슨역할을 하는거죠?
I
Ian H 2017.10.07
@박수진,
res.render는 ejs파일을 html로 만들어 client(브라우저)로 return하는 함수입니다. 첫번째 인자는 ejs파일의 위치, 두번째 인자는 ejs에서 사용할 수 있는 object입니다. 
{contacts:contacts}에서 첫번째 contacts는 ejs에서 사용할 key이고 두번째 contacts는 Contact.find({}, function(err, contacts){ ... })에서 콜백함수(function(err, contacts){ ... })로 넘겨진 DB에서 읽어온 contact 리스트입니다.
즉 contacts/index.ejs에서 DB에서 읽어온 contact 리스트를 사용하기 위해서 {contacts:contacts}를 넘겨주는 것입니다.
또한 contacts/index.ejs의 <% %> 안의 코드에서 사용되고 있는 contacts가 이때 넘겨받은 contact 리스트를 사용하는 부분입니다.
박수진 2017.10.09
@Ian H,
저같은 초보에게 설명을 너무 잘해주세요 감사합니다!
J
Jake Lyu 2020.03.18
@Ian H,
독학하면서 정말 궁금했었는데... 이렇게 자세히 설명해주시다니 진심으로 감사합니다 .ㅋㅋ 개꿀!
I
Ian H 2020.03.18
@Jake Lyu,
재밌게 읽어주셔서 감사합니다^^
김성환 2017.10.05
지금까지는 잘 따라서 하고 있는데 events.js에서 에러가 나는데 에러 난 부분을 찾을 수가 없네요ㅠ 힌트 주실 수 있나요?
[nodemon] starting `node index.js` events.js:160       throw er; // Unhandled 'error' event       ^
Error: listen EADDRINUSE :::3000     at Object.exports._errnoException (util.js:1020:11)     at exports._exceptionWithHostPort (util.js:1043:20)     at Server._listen2 (net.js:1262:14)     at listen (net.js:1298:10)     at Server.listen (net.js:1394:5)     at EventEmitter.listen (C:\workspace\contact-book\contact-book\node_modules\express\lib\application.js:618:24)     at Object.<anonymous> (C:\workspace\contact-book\contact-book\index.js:55:5)     at Module._compile (module.js:570:32)     at Object.Module._extensions..js (module.js:579:10)     at Module.load (module.js:487:32)     at tryModuleLoad (module.js:446:12)     at Function.Module._load (module.js:438:3)     at Module.runMain (module.js:604:10)     at run (bootstrap_node.js:383:7)     at startup (bootstrap_node.js:149:9)     at bootstrap_node.js:496:3 [nodemon] app crashed - waiting for file changes before starting...
I
Ian H 2017.10.05
@김성환,
Error: listen EADDRINUSE :::3000 는 해당 port(여기서는 3000이 되겠죠)가 이미 사용중일때 표시되는 에러입니다.
아마 다른 프로그램이 3000을 이미 쓰고 있는 것 같은데 해당 프로그램을 찾아서 꺼주거나 app.listen(3000, ...) 이 부분에 3000이 아닌 다른 숫자를 넣어서 이 프로그램의 실행 port를 바꿔 주시면 됩니다. 물론 웹사이트 접근도 http://localhost:3000 대신 바꾼 port 번호를 넣어주어야 접속됩니다.
d
dombegi 2017.10.17
@김성환,
저도 같은 문제로 골치 아팠습니다. 아마 전에 켰던 node.exe가 계속 돌아가서 그런 것 같습니다.  항상 포트 바꿔주기 귀찮아서 찾아봤더니 (윈도우 기준)
git-bash에 taskkill //f //im node.exe 라는 명령어로 열려있는 모든 node.exe를 끌 수가 있네요. 리눅스용은 stackoverflow에 엄청 많은 결과가 있어서 금방 찾으실 수 있습니다. 도움 되셨으면 좋겠습니다. 
문지상 2018.05.13
Server On! (node:22586) UnhandledPromiseRejectionWarning: Error: URL malformed, cannot be parsed     at module.exports (/Users/crea/Documents/workspace/git/meanstack/contact-book/node_modules/mongodb/lib/url_parser.js:17:21)     at connect (/Users/crea/Documents/workspace/git/meanstack/contact-book/node_modules/mongodb/lib/mongo_client.js:880:3)     at connectOp (/Users/crea/Documents/workspace/git/meanstack/contact-book/node_modules/mongodb/lib/mongo_client.js:269:3)     at executeOperation (/Users/crea/Documents/workspace/git/meanstack/contact-book/node_modules/mongodb/lib/utils.js:420:24)     at MongoClient.connect (/Users/crea/Documents/workspace/git/meanstack/contact-book/node_modules/mongodb/lib/mongo_client.js:260:10)     at Promise (/Users/crea/Documents/workspace/git/meanstack/contact-book/node_modules/mongoose/lib/connection.js:427:12)     at new Promise (<anonymous>)     at NativeConnection.Connection.openUri (/Users/crea/Documents/workspace/git/meanstack/contact-book/node_modules/mongoose/lib/connection.js:424:19)     at Mongoose.connect (/Users/crea/Documents/workspace/git/meanstack/contact-book/node_modules/mongoose/lib/index.js:207:15)     at Object.<anonymous> (/Users/crea/Documents/workspace/git/meanstack/contact-book/index.js:9:10)     at Module._compile (internal/modules/cjs/loader.js:678:30)     at Object.Module._extensions..js (internal/modules/cjs/loader.js:689:10)     at Module.load (internal/modules/cjs/loader.js:589:32)     at tryModuleLoad (internal/modules/cjs/loader.js:528:12)     at Function.Module._load (internal/modules/cjs/loader.js:520:3)     at Function.Module.runMain (internal/modules/cjs/loader.js:719:10) (node:22586) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id:2) (node:22586) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that 
자꾸 디비를 연결하는데 오류가 나네요ㅠㅠ 환경변수 설정도 했고, $ printenv로 설정도 확인했는데 이러네요. 환경변수를 쓰지 않고 직접 url을 이용해서 연결하면 잘 되는데, 환경변수 설정이 안먹는 듯 싶네요ㅠㅠ 그리고 쉘을 다시 시작하면 처음에는 MONGO_DB라는 변수가 등록이 안되어 있는데, $ source ~/.bash_profile 명령어를 입력해서 등록을 해주어야 환경변수가 적용이 되는 것 같습니다. 아무리 찾아도 이유를 알 수가 없네요 혹시 아신다면 답변 부탁드립니다.
I
Ian H 2018.05.14
@문지상,
구글링해보니 이런 글이 있네요.  https://stackoverflow.com/questions/42392644/mongoose-connection-url-showing-malformed-error
username, password에 특수문자가 있으면 encoding을 해줘야 한다고 합니다. 한번 해보세요^^
K
Kin Dunq 2018.07.13
회사에서 보고 작업중인데  환경이 다른가봐요 많이... 회사에선 pm2로 log도 볼수있었는데ㅎㅎ 집에서 하니까 pm2 로 하면 오류가 뜨네요..
C:\Users\kindunqHome\Desktop\contact>pm2 log [TAILING] Tailing last 15 lines for [all] processes (change the value with --lin es option) C:\Users\kindunqHome\.pm2\pm2.log last 15 lines: PM2        | [2018-07-13T23:47:37.993Z] PM2 log: App [index] with id [0] and pid  [13068], exited with code [1] via signal [SIGINT] PM2        | [2018-07-13T23:47:38.096Z] PM2 log: pid=13068 msg=process killed PM2        | [2018-07-13T23:48:12.800Z] PM2 log: [Watch] Start watching index PM2        | [2018-07-13T23:48:12.817Z] PM2 log: Starting execution sequence in -fork mode- for app name:index id:0 PM2        | [2018-07-13T23:48:12.821Z] PM2 log: App name:index id:0 online PM2        | [2018-07-13T23:48:29.805Z] PM2 error: Change detected on path index .js for app index - restarting PM2        | [2018-07-13T23:48:29.806Z] PM2 log: Stopping app:index id:0 PM2        | [2018-07-13T23:48:29.920Z] PM2 log: App [index] with id [0] and pid  [17388], exited with code [1] via signal [SIGINT] PM2        | [2018-07-13T23:48:30.023Z] PM2 log: pid=17388 msg=process killed PM2        | [2018-07-13T23:48:30.023Z] PM2 log: Starting execution sequence in -fork mode- for app name:index id:0 PM2        | [2018-07-13T23:48:30.027Z] PM2 log: App name:index id:0 online PM2        | [2018-07-13T23:49:08.224Z] PM2 log: [Watch] Stop watching index PM2        | [2018-07-13T23:49:08.225Z] PM2 log: Stopping app:index id:0 PM2        | [2018-07-13T23:49:08.304Z] PM2 log: App [index] with id [0] and pid  [16748], exited with code [1] via signal [SIGINT] PM2        | [2018-07-13T23:49:08.406Z] PM2 log: pid=16748 msg=process killed
C:\Users\kindunqHome\.pm2\logs\index-error.log last 15 lines: 0|index    |     at Mongoose.connect (c:\Users\kindunqHome\Desktop\contact\node_ modules\mongoose\lib\index.js:229:15) 0|index    |     at Object.<anonymous> (c:\Users\kindunqHome\Desktop\contact\ind ex.js:6:10) 0|index    |     at Module._compile (module.js:652:30) 0|index    |     at Object.Module._extensions..js (module.js:663:10) 0|index    |     at Module.load (module.js:565:32) 0|index    |     at tryModuleLoad (module.js:505:12) 0|index    |     at Function.Module._load (module.js:497:3) 0|index    |     at Object.<anonymous> (C:\Users\kindunqHome\AppData\Roaming\npm \node_modules\pm2\lib\ProcessContainerFork.js:75:21) 0|index    |     at Module._compile (module.js:652:30) 0|index    |     at Object.Module._extensions..js (module.js:663:10) 0|index    |     at Module.load (module.js:565:32) 0|index    |     at tryModuleLoad (module.js:505:12) 0|index    |     at Function.Module._load (module.js:497:3) 0|index    |     at Function.Module.runMain (module.js:693:10) 0|index    | (node:13068) DeprecationWarning: current URL string parser is depre cated, and will be removed in a future version. To use the new parser, pass opti on { useNewUrlParser: true } to MongoClient.connect.
C:\Users\kindunqHome\.pm2\logs\index-out.log last 15 lines: 0|index    |     at MongoClient.connect (c:\Users\kindunqHome\Desktop\contact\no de_modules\mongodb\lib\mongo_client.js:168:10) 0|index    |     at Promise (c:\Users\kindunqHome\Desktop\contact\node_modules\m ongoose\lib\connection.js:487:12) 0|index    |     at new Promise (<anonymous>) 0|index    |     at NativeConnection.Connection.openUri (c:\Users\kindunqHome\De sktop\contact\node_modules\mongoose\lib\connection.js:484:19) 0|index    |     at Mongoose.connect (c:\Users\kindunqHome\Desktop\contact\node_ modules\mongoose\lib\index.js:229:15) 0|index    |     at Object.<anonymous> (c:\Users\kindunqHome\Desktop\contact\ind ex.js:7:10) 0|index    |     at Module._compile (module.js:652:30) 0|index    |     at Object.Module._extensions..js (module.js:663:10) 0|index    |     at Module.load (module.js:565:32) 0|index    |     at tryModuleLoad (module.js:505:12) 0|index    |     at Function.Module._load (module.js:497:3) 0|index    |     at Object.<anonymous> (C:\Users\kindunqHome\AppData\Roaming\npm \node_modules\pm2\lib\ProcessContainerFork.js:75:21) 0|index    |   name: 'MongoParseError', 0|index    |   message: 'URI malformed, cannot be parsed', 0|index    |   [Symbol(mongoErrorContextSymbol)]: {} }
이 오류가 뭔지 알수 있을까요? ㅠㅠ
K
Kin Dunq 2018.07.13
nodemon 으로 하면 오류없이 잘됩니다.. ㅎㅎㅎ 그리고 집에서 하니까 아톰 터미널에서 npm 코드가 전혀 안먹히네요 ㅎㅎ 그것도 혹시... 알려주실수 있을까요?
I
Ian H 2018.07.16
@Kin Dunq,
PM2나 아톰 터미널은 제가 몰랐던 것들인데, 이렇게 배우게 되네요 ㅋ 특정한 시스템에서 해당 package들 자체에 에러가 있는 것같은데, 아마 제작자에게 오류를 보고해야 고칠 수 있을 것 같습니다.
김정규 2018.07.14
오늘은 오류가 없네요 ㅎㅎ 근데 궁금한게 routes는 경로를 잡아주는 부분이 있는데  views는 경로를 잡아주는 부분이 없는데 그 부분은 자동으로 잡히는건가요? 약속된 무언가가 있나요?
I
Ian H 2018.07.16
@김정규,
Express가 기본으로 views 폴더에서 view를 찾게 됩니다. app.set('views','./folder/my_view') 를 사용해서 해당 경로를 변경할 수 있습니다
J
Junny : Coder 2019.04.22
강좌 잘보고 있습니다. 몽고db사이트에서 알려준 mongodb+srv://yejunnyko17:<password>@cluster0-i17ai.mongodb.net/test?retryWrites=true를 환경변수에 넣고, 예제를 실행해보니 다음과 같이 나옵니다. 뭐가 문제일까요??
server on! http://localhost:3000 DB ERROR :  { MongoNetworkError: connection 3 to cluster0-shard-00-01-i17ai.mongodb.net:27017 closed     at TLSSocket.<anonymous> (/Users/yejunko/workspace/contact-book/node_modules/mongodb-core/lib/connection/connection.js:276:9)     at Object.onceWrapper (events.js:277:13)     at TLSSocket.emit (events.js:194:15)     at _handle.close (net.js:597:12)     at TCP.done (_tls_wrap.js:388:7)   name: 'MongoNetworkError',   errorLabels: [ 'TransientTransactionError' ],   [Symbol(mongoErrorContextSymbol)]: {} }
I
Ian H 2019.04.22
@Junny : Coder,
mongodb.net인가요?  주소가 좀 이상하긴 한데.. mongodb+srv://이라..  mlab.com에서 db만든 후 한번 해보세요.
J
Junny : Coder 2019.04.23
@Ian H,
db가 아니라, Clusters라고 나와서, 그걸 만들었습니다. db라는 것을 찾을 수가 없어서요.
김대호 2019.04.23
@Junny : Coder,
현재 mlab.com 으로 들어가서 가입을 하면 clusters로 들어가게 되는게 맞구요 mongodb+srv: 쭉 그 url을 카피해서 환경변수에 넣어주시고 사용하시면 되요.
박종한 2019.12.22
ejs의 버전이 바뀐 탓인지.. 3.0.1버전에서는 <% include ../partials/head %>이부분이 SyntaxError: Unexpected token . in C:\Users\xxxx(제 계정명입니다.)\xxxx(제 폴더 명입니다.)\contact-clone\views\contacts\index.ejs while compiling ejs 라고 뜨네요 계속.. 인터넷에 쳐보니 <%- include ('../partials/head') %> 이런식으로 하라네요..
I
Ian H 2019.12.23
@박종한,
아 그러네요. 강의를 업데이트할 때가 된 거 같네요.. 시간이 되면 ㅠㅠ 제보주셔서 감사합니다!
I
Ian H 2019.12.28
@박종한,
주소록 강의 까지는 코드를 업데이트를 했는데... 게시판 강의는 아마 새로 만들어야 할 것 같아요.
차누누 2020.01.31
페이지가 바뀔경우 render와 redirect를 사용하는데요, 두 메서드의 차이가 무엇인지 알 수 있을까요?? (구글링으로 조금 이해한 결과로는 -render는 사용자가 사이트에 어떠한 요구가 있을경우..?-    가 전부인데 잘 못찾겠네요 ㅜㅜ)
+교재보다 훨씬 상세히 설명해주셔서 요 사이트 없었으면 힘들뻔했습니다ㅜ ㅎㅎ 감사합니다!
I
Ian H 2020.01.31
@차누누,
안녕하세요^^ render는 view 코드(제 강의에서는 ejs파일의 코드)를 이용해서 html 코드를 생성한 후 client(웹브라우저)에 넘기는 함수이고, redirect는 현재 route을 종료하고 다른 route으로 진행을 바꾸는 함수입니다.
위 강의 코드에서 index route을 예로 들어 봅시다. localhost:3000/contacts가 브라우저에 입력되면, [GET] /contacts route, 즉 contacts의 index route 코드가 실행됩니다. Contact 모델로 DB에서 데이터를 찾고, res.render('contacts/index', {contacts:contacts}) 함수를 통해 /view/contacts/index.ejs 파일을 html로 재구성하여 client에 넘기게 됩니다. 
반면 localhost:3000가 입력되면 [GET] /,  즉 home route의 코드가 실행됩니다. res.redirect('/contacts');를 사용해서 [GET] /contacts route으로 살포시 토스해버립니다. [GET] /contacts route으로 넘어간 후에는 [GET] /contacts route에 의해 코드가 진행됩니다. 참고로 res.redirect는 항상 GET으로만 route을 넘깁니다.
최대한 쉽게 설명한다고 했는데 이해가 잘 되셨는지.. 잘 모르시겠으면 또 질문해 주세요!
차누누 2020.02.01
@Ian H,
아.. 어느정도 윤곽이 잡히고 있습니다. 감사해요!! ㅎㅎ 그리구 위 다른댓글 내용처럼 {contacts:contacts} 에서 왼contacts 가 key라고 하신게 ejs에서 오른쪽contacts 를 사용하기위한 변수명?? 이라고 이해하면 되는걸까요??
(만약 { A : B } 라고한다면, B를 ejs에서 A라고 사용한다.. 이런??)
I
Ian H 2020.02.01
@차누누,
네 맞습니다. 
정확히는 { key1: value1, key2: value2 } 이러한 형태를 자바스크립트에서 'object'라고 하고, render함수에 object를 넣어주면 ejs에서 해당 object의 key를 사용해서 해당 key의 value를 가져오는 것입니다.
차누누 2020.02.04
@Ian H,
감사합니다!!!! 선생님 덕분에 노드 재미있게 배우고 있습니다 ㅎㅎ
I
Ian H 2020.02.05
@차누누,
소통해 주셔서 감사합니다^^ 질문있으시면 언제든지 남겨주시고 제 블로그 다른 사람들에게도 많이 알려주세요!
-
-21 2020.02.17
안녕하세요! 주소록 만들기를 변형해서 제 웹사이트에 적용 시키고 싶은데 계속 reference errorr가 뜨네요... ReferenceError     9|       <h2>Index</h2>     10|       <ul>  >> 11|         <% papers.forEach(function(paper) { %>     12|           <li>     13|             <%= paper.name %>     14|           </li>
papers is not defined
라고 뜨는데 왜 그럴까요? 똑같이 라우팅 설정과 디비 설정을 해주고 이름만 contact에서 paper로 바꾼건데 그래요ㅠㅠ 이름 안 바꾸고 그대로 가져와도 contacts is not defined라고 뜨더라고요.. 똑같은 코드 복붙해서 가져온건데 혹시 왜 그런지 아실까요?
I
Ian H 2020.02.17
@-21,
안녕하세요! https://www.a-mean-blog.com/ko/blog/단편강좌/_/node-js-디버깅-방법 글을 참고하셔서 route에서 papers의 값이 제대로 생성되는지 확인해 보세요. 디버깅도 직접하셔야 실력이 늡니다!
정 안되시면 작성중인 코드를 github에 올리시고 github주소 알려주시면 제가 한번 볼게요^^
2020.05.14
DB ERROR :  MongoParseError: Invalid connection string     at parseConnectionString (C:\workspace\contact-book\node_modules\mongodb\lib\core\uri_parser.js:544:21)     at connect (C:\workspace\contact-book\node_modules\mongodb\lib\operations\connect.js:291:3)     at ConnectOperation.execute (C:\workspace\contact-book\node_modules\mongodb\lib\operations\connect.js:202:5)     at executeOperation (C:\workspace\contact-book\node_modules\mongodb\lib\operations\execute_operation.js:83:26)     at MongoClient.connect (C:\workspace\contact-book\node_modules\mongodb\lib\mongo_client.js:219:10)     at C:\workspace\contact-book\node_modules\mongoose\lib\connection.js:666:12     at new Promise (<anonymous>)     at NativeConnection.Connection.openUri (C:\workspace\contact-book\node_modules\mongoose\lib\connection.js:663:19)     at Mongoose.connect (C:\workspace\contact-book\node_modules\mongoose\lib\index.js:332:15)     at Object.<anonymous> (C:\workspace\contact-book\index.js:11:10)     at Module._compile (internal/modules/cjs/loader.js:1144:30)     at Object.Module._extensions..js (internal/modules/cjs/loader.js:1164:10)     at Module.load (internal/modules/cjs/loader.js:993:32)     at Function.Module._load (internal/modules/cjs/loader.js:892:14)     at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)     at internal/main/run_main_module.js:17:47 {   name: 'MongoParseError',   [Symbol(mongoErrorContextSymbol)]: {} } (node:1928) UnhandledPromiseRejectionWarning: MongoParseError: Invalid connection string     at parseConnectionString (C:\workspace\contact-book\node_modules\mongodb\lib\core\uri_parser.js:544:21)     at connect (C:\workspace\contact-book\node_modules\mongodb\lib\operations\connect.js:291:3)     at ConnectOperation.execute (C:\workspace\contact-book\node_modules\mongodb\lib\operations\connect.js:202:5)     at executeOperation (C:\workspace\contact-book\node_modules\mongodb\lib\operations\execute_operation.js:83:26)     at MongoClient.connect (C:\workspace\contact-book\node_modules\mongodb\lib\mongo_client.js:219:10)     at C:\workspace\contact-book\node_modules\mongoose\lib\connection.js:666:12     at new Promise (<anonymous>)     at NativeConnection.Connection.openUri (C:\workspace\contact-book\node_modules\mongoose\lib\connection.js:663:19)     at Mongoose.connect (C:\workspace\contact-book\node_modules\mongoose\lib\index.js:332:15)     at Object.<anonymous> (C:\workspace\contact-book\index.js:11:10)     at Module._compile (internal/modules/cjs/loader.js:1144:30)     at Object.Module._extensions..js (internal/modules/cjs/loader.js:1164:10)     at Module.load (internal/modules/cjs/loader.js:993:32)     at Function.Module._load (internal/modules/cjs/loader.js:892:14)     at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)     at internal/main/run_main_module.js:17:47 (node:1928) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 3) (node:1928) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
전 강의때부터 왜 자꾸 nodemon만 실행하면 이런 오류가 뜰가요..ㅠㅠㅠ 시스템에도 개인에도 환경변수 설정을 다 하고 재부팅까지 했는데ㅜㅜㅜ
I
Ian H 2020.05.14
@진,
에러메세지 첫줄을 보시면 DB ERROR :  MongoParseError: Invalid connection string 라고 되어 있죠. DB connection string이 이상하다고 합니다.
에러가 나는 위치는 나머지 에러메세지를 살펴 보면 알 수 있는데,  잘 보다 보면 우리가 아는 파일이 나옵니다.     at Object.<anonymous> (C:\workspace\contact-book\index.js:11:10)
참고로 이 줄 아래는 어떻게 이 코드가 호출되게 되었는지, 이 줄 위에는 이 코드 이후 어떻게 에러가 처리되었는지를 나타냅니다. 
C:\workspace\contact-book\index.js 파일의 11번째 줄에 가면 DB connection string을 담고 있는 변수가 있을 텐데,  https://www.a-mean-blog.com/ko/blog/단편강좌/_/node-js-디버깅-방법 글을 참고해서 그 값을 확인해 보세요.
만약 값이 없다면 환경변수 설정이 잘 안된 것이고, 값이 있다면 그 값으로 DB접속이 안되는 것이니 그 값이 정확한지 살펴보세요. 
2020.05.16
@Ian H,
server on! http://localhost:3000 DB ERROR :  Error: querySrv ENOTFOUND _mongodb._tcp.cluster0-jfwrx.mongodb.net     at QueryReqWrap.onresolve [as oncomplete] (dns.js:206:19) {   errno: undefined,   code: 'ENOTFOUND',   syscall: 'querySrv',   hostname: '_mongodb._tcp.cluster0-jfwrx.mongodb.net' } (node:9116) UnhandledPromiseRejectionWarning: Error: querySrv ENOTFOUND _mongodb._tcp.cluster0-jfwrx.mongodb.net     at QueryReqWrap.onresolve [as oncomplete] (dns.js:206:19) (node:9116) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1) (node:9116) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
선생님! 확인해보니 생성한 MONGO_DB 환경변수에 제 아이디와 비밀번호를 틀렸었네요...ㅜㅜ 변경 후에 재 시작해서 실행시켰을때 위와같은 에러가 떠서 inspect로 req.params 해보았으나 req is not defined네요.. 제 몽고db를 못찾는걸가요..?
I
Ian H 2020.05.16
@진,
네 몽고db 주소가 유효하지 않은 것같습니다. https://www.a-mean-blog.com/ko/blog/단편강좌/_/mongoDB-Atlas-가입-방법-무료-mongo-DB-클라우드-서비스 로 주소를 생성해 보세요 
s
seongjoon yoon 2020.05.22
안녕하세요.  친절한 블로깅 덕분에 한 단계씩 따라해보고 있는 1ㅅ 입니다. 다른게 아니고 궁금한 점이 있어서요 app.get('/contacts/new',function(req, res){   res.render('contacts/new'); }); 이 코드가  < A경우 > =============================================== index.js 파일 문서시작 ...
app.get('/contacts/new',function(req, res){   res.render('contacts/new'); });
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 port = 3000; app.listen(port, function(){   console.log('server on! http://localhost:'+port);
문서끝 ===============================================
< B경우 > =============================================== index.js 파일 문서시작 ... var contactSchema = mongoose.Schema({   name:{type:String, required:true, unique:true},   email:{type:String},   phone:{type:String} }); var Contact = mongoose.model('contact',contactSchema);
app.get('/',function(req, res){   res.redirect('/contacts'); });
app.get('/contacts/new',function(req, res){   res.render('contacts/new'); });
app.get('/contacts',function(req, res){   Contact.find({},function(err, contacts){     if(err) return res.json(err);     res.render('contacts/index',{contacts:contacts});   }); }); ... 문서끝 ===============================================
첫화면에서 
NEW 눌러서 새 정보를 입력하려고 하면 
< A 경우 >  문제없음  < B 경우 >  {"message":"Cast to ObjectId failed for value \"new\" at path \"_id\" for model \"contact\"","name":"CastError","stringValue":"\"new\"","kind":"ObjectId","value":"new","path":"_id","reason":{}} 다음과 같은 오류가 납니다.
해당 코드 위치에 따라서 문제가 발생하는데 이유가 뭐일까요?
I
Ian H 2020.05.22
@seongjoon yoon,
안녕하세요^^ a의 경우는 위 강의대로 routes 코드가 진행되고 있고, b의 경우는 app.get('/contacts/new',가 app.get('/', 바로 다음에 오는 경우잖아요?
제가 방금 실험해 봤는데요 둘다 문제없이 잘 실행됩니다. 이론적으로도 겹칠일이 없는 route들의 순서들은 바뀌어도 상관이 없구요. (겹치는 경우의 예시: app.get('/contacts/:id' app.get('/contacts/all'의 경우 app.get('/contacts/all'이 항상 먼저 작성되어야 함)
에러 메세지 자체는 Cast to ObjectId failed for value "new" at path "_id" for model "contact" 로 mongoDB 데이터에 _id는 자동으로 생성되는 아이디가 들어가는 자리인데 이 곳에 "new"라는 문자열이 들어와서 오류가 나고 있습니다.
위 강의의 코드에서 
// Contacts - create app.post('/contacts', function(req, res){   req.body._id='new'; //<----------------------------------------------------추가된 코드   Contact.create(req.body, function(err, contact){     if(err) return res.json(err);     res.redirect('/contacts');   }); }); 와 같이 변경하면 인위적으로 해당 에러를 낼 수 있습니다.
한번더 코드를 체크해보시고, 그래도 에러가 난다면 가지고 계신 코드를 github에 올려주시면 제가한번 살펴보겠습니다^^
u
utoru80 2020.07.07
var Contact = mongoose.model('contact', contactSchema); // 5 ------------------위코드가 실행되면 atlas의 컬렉션에 contact 스키마가 생성되는거 맞나요??
I
Ian H 2020.07.07
@utoru80,
정확한 컬랙션 생성 시점은 첫 데이터가 생성될 때로 알고 있습니다! 한번 테스트 해보시고 저에게도 알려주세요^^
a
ams 2020.07.14
head.ejs에서 css 링크를 /css/master.css로 달아도 되는 이유는 무엇이죠 
I
Ian H 2020.07.14
@ams,
상대경로, 절대경로와 관련된 질문을 주셨어요.
<link rel="stylesheet" href="/css/master.css"> 에서 "/css/master.css"는 상대경로지만, 상대경로가 "/"로 시작하는 경우에 그 "/"는 "루트"를 의미합니다. 즉 어느파일, 어느 위치에서 "/css/master.css"를 호출하더라도 "서버루트/css/master.css"의 파일이 호출됩니다.
궁금증이 해결되셨나요? 더 자세한 것은 구글링(https://www.google.com/search?q=상대경로+절대경로 )을 통해 알아보시기 바랍니다.
a
ams 2020.07.15
@Ian H,
실제로 css파일은 "/public/css/master.css"에도 불구하고 경로를 "/css/master.css"로 설정해도 되는 이유는 index.js 에서 app.use(express.static(__dirname+'/public'))를 해줬기 때문인가요?
I
Ian H 2020.07.15
@ams,
정확합니다!
-
-21 2020.08.31
안녕하세요 질문 하나만 드릴게요! MEAN 블로그 참조해서 홈페이지를 하나 만들어서 localhost 로는 굉장히 잘 돌아가는데 이거를 맥서버에 올려서 domain 설정 후 띄우려고 하는데요, 잘 안되서 여쭤봅니다 서버는 맥 아파치 서버인데 default로 root에 있는 index.html 파일을 열더라구요, 근데 MEAN 블로그에서 만드신 코드들은 index.html이 있는게 아니라 view/home/main.ejs 가 첫 시작페이지여서 이걸 못 잡고 못 열더라고요.. root에 샘플로 index.html 파일을 만들어두면 그건 열리긴해서 서버는 잘 작동하는거 같은데.. view/home/main.ejs 파일을 메인 페이지로 설정하게끔 하려면 어떻게 해야 할까요? 또한 html 파일이 아니라 ejs 파일인데 서버에서 별도로 설정해야할 것이 있을까요?
I
Ian H 2020.08.31
@-21,
안녕하세요 nodeJS에서는 파일을 찾아서 실행시키는 것이 아니라 node.js app이 route을 분석하고, route에 맡은 템플릿(ejs)를 찾아 html code를 만든 후 페이지를 표시하게 됩니다. 즉 아파치서버가 view/home/main.ejs를 못찾는게 문제가 아닙니다. ejs는 그냥 템플릿파일일 뿐입니다.
아파치서버가 설정된 포트로 request가 오면 실행중인 node.js app으로 연결을 해주어야 하는데, 이 부분은 제가 아파치서버를 써본 적이 없으므로 https://www.google.com/search?q=노드js+아파치+연동 과 같이 검색하셔서 해결하시면 되겠습니다!
댓글쓰기

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

UP