Node API 서버 만들기
NodeJS에 대해
브라우져 안의 JS
- V8 (Chrome)
- Webkit (Safari)
- SpikerMonky (Firefox)
브라우져 밖의 JS
- V8
- Event I/O
- CommonJS
NodeJS로 할 수 있는 것
- Network Programming
- Development Tool
- Desktop Application
- Mobile Application
- ...
API Server
- NodeJS
- ExpressJS
- REST API
Hello world!
https://nodejs.org/ko/
node --version
npm --version
npm init
const http = require('http');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World\n');
});
server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});https://nodejs.org/dist/latest-v6.x/docs/api/synopsis.html
node app.js
{
  "scripts": {
    "start": "node app.js"
  }
}package.json
npm start
브라우져로 127.0.0.1:3000 접속
curl -X GET '127.0.0.1:3000'
curl -X GET '127.0.0.1:3000' -v
git checkout helloWorld
Hello world 코드 살펴보기
const
function foo() {
  if (false) {
    var a = 1;
  }
  console.log(a);
}
foo(); // undefinedfunction scope
function foo() {
  var a;
  if (false) {
    a = 1;
  }
  console.log(a);
}
foo(); // undefinedfunction foo() {
  if (false) {
    let a = 1;
  }
  console.log(a);
}
// ReferenceError: 
// a is not defined
foo(); block scope
function foo() {
  if (false) {
    let a;
    a = 1;
  }
  console.log(a);
}
// ReferenceError: 
// a is not defined
foo(); const a = 1;
// TypeError: 
// Assignment to constant variable.
a = 2; 
// SyntaxError: 
// Missing initializer in const declaration
const b; const
var
let
const
Arrow Function
var foo = function(param) {
  return true;
}
const foo = (param) => {
  return true;
}
const foo = param => true;
require()
foo.js
bar.js
require('foo')
function sum(a, b) {
  return a + b;
}
// sum()을 외부 노출 -> 모듈로 만듬
module.exports = sum;sum.js
const sum = require('./sum.js');
console.log(sum(1, 2)); // 3app.js
ExpressJS
ExpressJS를 사용하는 이유
HTTP
client
Server
웹 프레임웍
적은 코드로 많은 일을
npm i express --save
Express Hello world!
익스프레스 주요 개념
- Application
- Request
- Response
- Router
1. Application
- 익스프레스 객체
- 미들웨어 설정
- 요청 대기 상태
- (간단한) 라우팅
2. Request
- req.params
- req.query
- req.body
3. Response
- res.send()
- res.json()
- res.status()
4. Router
- Router()
- 구조적인 라우팅 구현
- API 서버의 핵심 로직
REST API
요청
사용자 목록을 조회하는 요청
/getUsers
/users
/users/1
/users?limit=5&offset=10
GET /users?limit=&offset
GET /users/1
| CRUD | Method | 
|---|---|
| Create | POST | 
| Read | GET | 
| Update | PUT | 
| Delete | DELETE | 
응답
사용자 목록을 응답
헤더
Status Code
Content-Type: text/json
Content-Length: 13
...
바디
배열이나 객체 
[{
  id: 1, 
  name: 'Alice'
}, {
  id: 2, 
  name: 'Bek'
}, {
  id: 3, 
  name: 'Chris'
}]2xx
4xx
5xx
| 상태 코드 | 의미 | 
|---|---|
| 200 | Success | 
| 201 | Created | 
| 204 | No Content | 
| 상태 코드 | 의미 | 
|---|---|
| 400 | Bad Request | 
| 401 | Unauthorized | 
| 404 | Not Found | 
| 409 | Conflict | 
| 상태 코드 | 의미 | 
|---|---|
| 500 | Internel Error | 
HTTP
Query
Server
DB
Client
GET /users
GET /users/:id
POST /users
DELETE /users/:id
PUT /users/:id
GET /users
전체 사용자 목록 조회
임시 데이터
let users = [
  {
    id: 1,
    name: 'alice'
  },
  {
    id: 2,
    name: 'bek'
  },
  {
    id: 3,
    name: 'chris'
  }
]라우팅 설정
app.get('/users', (req, res) => {
  // 여기에 라우팅 로직을 작성하면 됩니다.
});JSON
- 
	데이터 표현 방법
- 
	AJAX 에서 주로 사용
- 
	자바스크립트 객체와 유사
res.json(users);
curl -X GET '127.0.0.1:3000/users' -v
git checkout getUsers
GET /users/:id
아이디로 사용자 조회
GET /users/1
app.get('/users/1', (req, res) => /* ... */);
GET /users/2
app.get('/users/2', (req, res) => /* ... */);
GET /users/:id
app.get('/users/:id', (req, res) => {
  console.log(req.params.id); 
});
아이디 값 처리
app.get('/users/:id', (req, res) => {
  const id = parseInt(req.params.id, 10);
});
파라매터 검증
app.get('/users/:id', (req, res) => {
  const id = parseInt(req.params.id, 10);
  if (!id) {
    return res.status(400)
        .json({error: 'Incorrect id'});
  }
});함수 체이닝
function User(_name) {
  this.name = _name;
}
User.prototype.greeting = function() {
  console.log('Hello! ');
  return this;
};
User.prototype.introduce = function() {
  console.log(`I am ${this.name}`);
  return this;
};
var chris = new User('chris');
chris.greeting().introduce();let user = users.filter(user => {
  return user.id === id;
});
console.log(user); // [{id: 1, name: 'alice'}]Array.prototype.filter()
app.get('/users/:id', (req, res) => {
  let user = users.filter(user => user.id === id)[0]
  if (!user) {
    return res.status(404)
        .json({error: 'Unknown user'});
  }
});404 에러 처리
app.get('/users/:id', (req, res) => {
  // return res.status(200).json(user);
  // or 
  return res.json(user);
});200 성공 응답
curl -X GET '127.0.0.1:3000/users/1' -v
curl -X GET '127.0.0.1:3000/users/4' -v
curl -X GET '127.0.0.1:3000/users/alice' -v
git ckeckout getUserById
DELETE /users/:id
사용자 삭제
app.delete('/users/:id', 
배열에서 유저 삭제
Array.prototype.findIndex()
Array.prototype.splice()
const userIdx = users.findIndex(user => {
    return user.id === id;
});findIndex()
if (userIdx === -1) {
    return res.status(404)
        .json({error: 'Unknown user'});
}404 에러 처리
users.splice(userIdx, 1);
응답
방법 1: 전체 유저 배열을 응답
방법 2: 204. No Content
res.status(204).send();
curl -X DELETE '127.0.0.1:3000/users/1' -v
curl -X GET '127.0.0.1:3000/users/1' -v
curl -X GET '127.0.0.1:3000/users' -v
git checkout deleteUserById
POST /users
사용자 추가
app.post('/users',
요청 데이터를 보내는 방법
쿼리문자열 (Query string)
바디 (Body)
https://www.google.co.kr/newwindow&q=chris
바디(body)는?
body-parser!
npm i body-parser --save
const name = req.body.name || "";
400 에러 처리
if (!name.length) {
  return res.status(400)
      .json({error: 'Incorrenct name'});
}새로운 아이디 만들기
Array.prototype.reduce()
배열에 새로운 유저 추가
const newUser = {
  id: id,
  name: name
};
users.push(newUser);
201 응답
return res.status(201).json(newUser);
curl -X POST '127.0.0.1:3000/users' -d "name=daniel" -v
git checkout postUser
git checkout putUserById
익스프레스 Router
const express = require('express');
const router = express.Router();
router.get('/users', (req, res) => {
  // ...
})
module.exports = router;
api/users/index.js
const router = express.Router();
// Database Mockup
let users = [/* ... */];
router.get('/:id', (req, res) => {/* ... */});
router.get('/', (req, res) => {/* ... */});
router.delete('/:id', (req, res) => {/* ... */});
router.post('/:id', (req, res) => {/* ... */});
module.exports = router;
app.use('/users', require('./api/user'));
git checkout router1
api/user/user.controller.js
// Database Mockup
let users = [/* ... */];
exports.index = (req, res) => {/* ... */});
exports.get = (req, res) => {/* ... */});
exports.destroy = (req, res) => {/* ... */});
exports.create = (req, res) => {/* ... */});
const router = express.Router();
const ctrl = require('./user.controller');
router.get('/:id', ctrl.show);
router.get('/', ctrl.index);
router.delete('/:id', ctrl.destroy);
router.post('/:id', ctrl.create);
module.exports = router;
app.js: 익스프레스로 서버 설정 및 구동
api/user/index.js: User API에 대한 라우팅 설정
api/user/user.controller.js: User API에 대한 로직
git checkout router2
테스트
Mocha
Should
SuperTest
1. Mocha
npm i mocha --save-dev
Test suite: describe()
Test: it()
api/user/user.spec.js
const assert = require('assert');
describe('test suite', () => {
  it('true is true', (done) => {
    assert.equal(true, true);
  });
});node_modules/.bin/mocha api/user/user.spec.js
{
  "scripts": {
    "test": "node_modules/.bin/mocha api/user/user.spec.js"
  }
}
2. Should
npm i should --save-dev
const should = require('should');
describe('test suite', () => {
  it('true is true', (done) => {
    (true).should.be.euqal(true);
  });
});3. SuperTest
npm i supertest --save-dev
module.exports = app;
const request = require('supertest');
const app = require('../../app');
describe('GET /users', () => {
  it('should return 200 status code', (done) => {
    request(app)
        .get('/users')
        .expect(200)
        .end((err, res) => {
          if (err) throw err;
          console.log(res.body); 
            // [{id: 1, name: 'Alice'} ... ]
          done();
        })
  });
});res.body.should.be.an.instanceof(Array)
    .and.have.length(3);
res.body.map(user => {
  user.should.have.properties('id', 'name');
  user.id.should.be.a.Number();
  user.name.should.be.a.String();
});git checkout unitTest
git checkout unitTest1
git checkout unitTest2
git checkout unitTest3
git checkout unitTest4
데이터베이스
HTTP
Query
Server
DB
Client
mysql -u root -h localhost
mysql> CREATE DATABASE node_api_codlab;
mysql> USE node_api_codlab;
mysql> SHOW TABLES;
ORM
(Object Relational Mapping)
Table1
Table2
Object1
{
  id: 1
  name: 1
}
Object2
{
  id: 1
  name: 1
}
Object3
{
  id: 1
  name: 1
}
Table3
Sequelize (ORM)
npm i sequelize mysql --save
모델(Model)
models.js
const Sequelize = require('sequelize');
const sequelize = new Sequelize('node_api_codelab', 'root', 'root')
const User = sequelize.define('user', {
  name: Sequelize.STRING
});
module.exports = {
  sequelize: sequelize,
  User: User
}데이터베이스 동기화
app.listen(3000, () => {
  console.log('Example app listening on port 3000!');
  require('./models').sequelize.sync({force: true})
      .then(() => {
        console.log('Databases sync');
      });
});mysql> SHOW TABLES;
mysql> describe users;
git checkout sequelizeModel
컨트롤러에 디비 연동하기
const models = require('../../models');
exports.create = (req, res) => {
  const name = req.body.name || '';
  if (!name.length) {
    return res.status(400).json({error: 'Incorrenct name'});
  }
  models.User.create({
    name: name
  }).then((user) => res.status(201).json(user))
};create()
exports.index = (req, res) => {
  models.User.findAll()
      .then(users => res.json(users));
};findAll()
exports.show = (req, res) => {
  models.User.findOne({
    where: {
      id: id
    }
  }).then(user => {
    if (!user) {
      return res.status(404).json({error: 'No User'});
    }
    return res.json(user);
  });
};findOne()
exports.destroy = (req, res) => {
  models.User.destroy({
    where: {
      id: id
    }
  }).then(() => res.status(204).send());
};destroy()
git checkout sequelizeInCtrl
환경의 분리
process.env.NODE_ENV
development
test
production
config/environment.js
const environments = {
  development: {
    mysql: {
      username: 'root',
      password: 'root',
      database: 'node_api_codelab_dev'
    }
  },
  test: {
    mysql: {
      username: 'root',
      password: 'root',
      database: 'node_api_codelab_test'
    }
  },
  production: {
  }
}
const nodeEnv = process.env.NODE_ENV || 'development';
module.exports = environments[nodeEnv];노드 환경에 따라 디비 연결하기
const Sequelize = require('sequelize');
const config = require('./config/environment');
const sequelize = new Sequelize(
    config.mysql.database, 
    config.mysql.username, 
    config.mysql.password);git checkout env
테스트에 디비 연동하기
bin/sync-databse.js
const models = require('../models');
module.exports = () => {
  return models.sequelize.sync({force: true})
}
bin/www.js
const app = require('../app');
const syncDatabase = require('./sync-database');
app.listen(3000, () => {
  console.log('Example app listening on port 3000!');
  syncDatabase().then(() => {
    console.log('Database sync');
  });
});package.json
{
  "scripts": {
    "start": "node bin/www"
  }
}테스트 데이터 추가
before('sync database', (done) => {
    syncDatabase().then(() => done());
});
before('insert seed user data', (done) => {
    models.User.bulkCreate(users).then(() => done());
})테스트 데이터 삭제
after('delete seed user data', (done) => {
  models.User.destroy({
    where: {
      name: {
        in: users.map(user => user.name)
      }
    }
  });
});NODE_ENV=test ./node_modules/.bin/mocha
api/**/*.spec.js
package.json
{
  "scripts": {
    "test": "NODE_ENV=test node_modules/.bin/mocha api/user/user.spec.js"
  }
}git checkout unitTestWithDb
폴더 정리
/app: 서버 기능
    /api: api 로직을 담당
    /config: 서버가 구동하기 위한 환경 변수 정의 
    /models: 데이터베이스 모델링 
/bin: 서버 구동을 위한 코드
    /www.js: 서버 구동
    /sync-database: 디비 싱크
git checkout refoldering
실습
PUT /users/:id를 만들어 보세요
Hints
- router.put()
- models.User.update()
- models.User.findOne()
- 400, 404, 200
Copy of Node API 서버 만들기 (웹프레임웍스)
By BYUN Kyuhyun
Copy of Node API 서버 만들기 (웹프레임웍스)
NodeJS를 이용한 모바일 API 서버 만들기 입니다.
- 684
 
  