Node.js REST API DB Sync


*데이터베이스 연동

일반적인 백엔드 구조


지금 까지 했던 작업을 Client, Server, Database 중에 Server부분을 만들었음. Client에서 요청하는 것은 curl 사용하거나 모카 테스트로 진행했음.


남은 것은 가장 오른쪽에 있는 database 부분이다. 백에드에서 database 직접 만드는 것은 아님.

다양한 데이터베이스 프로그램이 있는데 그중 MySQL 사용할 것이다. MySQL 우리 컴퓨터에 개발용으로 설치하고 Server에서는 단지 데이터베이스에 연결하는 것이다.


*MySQL

설치


$ sudo apt-get install mysql-server


mysql 설치한 서버를 구동해야 한다. mysql 서버를 데몬이라고도 하는데 리눅스에서 말하는 데몬 혹은 서비스와 같은 것이다. 서버에서 백그라운드에서 실행중인 프로세스를 데몬이라고 한다. 로컬에서도 개발용으로 MySQL데몬을 실행해야 한다.


$ mysql.server start

반대로 종료할때는 

$ mysql.server stop


우분투에서는


이렇게 한다.


접속명령어는


$ mysql -u root -h localhost -p


mysql 명령어의 -u옵션은 서버 접속 계정을 넣을 사용하는데 기본 값인 root 사용했다. -h 옵션은 서버 접속주소를 설정하는 옵션인데 우리는 로컬 컴퓨터에 구동중인 mysql서버에 접속하므로 localhost 사용했다. 마지막 -p 옵션을 비밀번호를 입력하기 위한 기능이다. 명령어를 실행하면 비밀번호를 입력하도록하는데 root 입력하고 접속한다. 




DB목록 조회


DB 생성


DB선택



여기서 부터는 mysql명령어를 사용하지 않아도 된다.


*Sequelize

노드 코드에서 mysql 접속해서 이런 저런 쿼리문을 실행할 때는 노드 코드로 만든 mysql 패키지가 ㅣㄹ요하다. 그것이 ode-mysql이다. 이것을 우리 프로젝트에 추가해서 쿼리문을 직접 실행시킬수 있음.

이를 ORM(object relational mapping)이라고 하는데, ORM 사용하면 쿼리문을 모르더라도 자신이 사용하는 프로그래밍 언어로 데이터베이스에 명령을 내릴 있다. 노드에는 sequelize라는 orm라이브러리가 있음.


$ npm i sequelize —save


*Model

서버에서 하나의 자원을 정의할 그것을 모델이라고 한다. 우리는 지금까지 User라는 자원을 사용했다. 이것이 User모델이다. 모델은 데이터베이스의 테이블과 1:1 매칭된다고 보면 된다. 그러면 데이터베이스에 User테이블이 있어야 . User테이블을 만들기 위해, 다시말하면 User모델을 만들기 위해 Sequelize 도움을 받아야 한다.


모델을 만드는 역할을 하는 models.js 파일을 만들자.




 sequelize모듈을 가져와 Sequelize상수에 할당했다. 그다음엔 Sequelize객체를 하나 만들어 sequelize 상수에 할당했다.

Sequelize객체를 만들 때는 3개의 파라미터가 필요한데 데이터베이스 이름, 접속 계정명, 비밀번호 순이다. 순서대로 문자열로 파라미터를 넘겨주면 sequelize객체를 얻을 있다.

객체가 제공하는 메소드중 define()함수를 이용해 모델을 만들 있음.



define() 함수의 첫번째 파라미터가 데이터베이스에 만들어질 테이블 이름이다.


다음에는 테이블의 세부사항을 객체 형식으로 정의해야한다. 이전에 유저 객체에는 id name 있었는데 둘을 여기서 정의하면 된다.

name: Sequelize.STRING name컬럼을 정의하는 코드이다.Sequelize.STRING 상수를 이용해 name값이 문자열임을 정의했다.

그럼 id 어디에?

-> sequelize 기본적으로 id 만들어 준다.


게다가 createdAt, updatedAt 이라는 칼럼도 자동으로 만들어 준다. 컬럼들의 역할은

테이블안에 데이터를 row라고 하는데 row 생성될 마다 createAt 시간정보가 기록된다. 그리고 row 변경될 때마다 updatedAt컬럼값이 변경된다.


마지막으로 module.export해준다.



*DB Sync

sequelize객체가 제공하는 메소드 중에는 모델을 정의하는 define()외에도  sync()라는 메소드가 있다. 함수를 실행하면 sequelize객체에 연결된 데이터베이스에 우리가 정의한 모델들을 테이블로 생성하는 기능이다.

이러한 작업은 서버가 구동될 한번만 호출되면 된다. 그래서 서버의 시작점인 app.js 만들도록 한다.


  • 다음과 같은 오류가 출력되는 경우



$ npm i install mysql2 —save

  • 다음과 같은 오류가 출력되는 경우


$ sudo mysql -u root -h localhost -p
> use mysql
> update user set authentication_string=password(‘’), plugin=‘mysql_native_password’ where user=‘root’;


성공


app.listen()으로 서버가 구동된 다음에 콜백함수가 동작하면 ‘Example app listening on port 3000!’라는 메시지를 콘솔에 출력하게 된다.

그리고 나서 방금 만들었던 models 모듈의 sequelize객체를 가져와서 sync() 함수를 실행한다. 데이터베이스에 테이블을 만들기 위해서다.


그런데 sync()함수에 {force: true}옵션을 넘겨주었다. force라는 속성에는 불리언 데이터를 설정할 있는데 true 경우 sync()함수가 실행되면 무조건 테이블을 새로 만드는 옵션이다. 반대로 force 값이 false 경우에는 데이터 베이스에 테이블이 있을 경우 다시 만들지 않는 기능이다. 지금은 개발용이기 때문에 {force: true} 설정했지만 실제 운영중인 서버라면 반드시 {force: false} 옵션으로 실행해야 한다.



방금 만들었던 users 테이블이 생겼다. 그런데 define()함수로 테이블명을 user 했는데 위의 사진은 users 되어있다. sequelize에서 자동으로 변환해서 만들어 준것이다.


테이블 정보 확인


id 값은 sequelize 자동으로 생성한 컬럼이다. 프라이머리 키로 설정되어 row 생성될 마다 자동으로 id값이 증가한다.

name 우리가 정의한 컬럼이다. sequelize.STRING 값으로 정의했는데 varchar(255) 설정된 것을 확인 해볼 있다.

그리고 createdAt updateAt sequelize 자동으로 만들어준 컬럼이다. datetime형식으로 되어있음.


*컨트롤러에 데이터베이스 연동


Sequelize 로컬에 구동중인 데이터베이스와 api서버를 연결했다. 그리고 우리가 모델링한 테이블까지 데이터베이스 안에 만들었음.


이제는 테이블에 데이터를 넣거나 조회하거나 삭제 그리고 업데이트하는 작업을 차례이다. 이것은 CRUD기능이라고 한다. create, read, update, delete  기능을 말하는 . api 기능이 데이터베이스의 데이터를 crud하는 것이라 보면 된다.

지금까지는 데이터를 users  배열에 넣고 개발했는데 이제는 데이터베이스를 이용한다.


*create

먼저 user컨트롤러에서 models 가져온다.

/api/users/user.controller.js



테이블이 아직은 비어있기 때문에 먼저 테이블에 넣는 create() 사용해보자


name파라미터를 검증하는 부분까지는 이전코드와 같다. name값이 확보되었다면 models모듈을 이용해 테이블에 데이터를 추가하는 것이 남았음. 

models.User객체는 crud 해당하는 메소드 들을 제공하는데 그중 create() 메소드는 테이블에 데이터를 추가하는 기능을 한다. 파라미터로 넣은 데이터를 객체 형식으로 넘겨준다. 

name컬럼에 name상수값을 넣어줬다.

그리고 then함수가 동작하면 콜백함수의 user파라미터로 테이블에 생성된 row 나온다. 이것을 요청한 클라이언트에 그대로 전달해 주면 된다.


*read

데이터를 조회하는  api 컨트롤러는 index(), show() 메소드가 있다.


index() 모든 사용자 목록을 조회하는 것이기 때문에 테이블 전체 데이터를 불러와야한다.

models.User 객체의  findAll() 메소드를 사용하면 테이블의 전체 데이터를 불러올 있다.




show() 특정사용자를 id 조회하는 역할을 한다. models.User 모델은 findOne() 함수로 조건을 줘서 데이터를 조회한다. where 라는 부분에 id컬럼의 조건값을 설정하여 넘겨준다. 함수가 실행되면 콜백함수의 파라미터로 조회한 user객체가 응답된다. 만약 user값이 비어있다면 테이블에서 id 해당하는 데이터를 찾지 못한것. 이경우 404 상태코드로 응답한다. 성공한경우에는 json()함수로 응답해준다.



*delete

삭제할 때는 destory()메소드를 사용한다. id기준으로 삭제하는 것이므로 where 이용해 파라미터를 넘겨준다.



*update

update 조금 특별하다. 테스트 코드를 먼저 작성하고 컨트롤러 함수를 만들 이다. 이렇게 코딩하는 방법을 tdd(test-driving develop) 개발방법론이라고 한다.

테스트 코드를 api/user/user.sepc.js 추가하자.



update api 호출하면 200 상태코드가 응답되는지 체크하는 코드이다. 테스트를 돌려보면 당연히 실패가 나올 것이다.

  • 블로그에서는 실패가 출력된다고 했는데 나는 이상하게 passing 출력된다. 이유가 뭘까?


밑에 댓글을 보니 이런 내용이 있다.





다시 블로그의 내용으로 돌아가서 

404에러 메세지가 나오는데 라우팅 설정을 하지 않았기 때문 그럼 테스트를 통과할 있도록 코드를 추가해 볼것이다. 먼저 /api/user/index.js 파일에 해당 api 대한 라우팅 설정을 추가한다.


PUT /users/:id 요청이 들어올 경우 update 컨트롤러 함수가 동작하도록 설정했다.

api/users/user.controller.js파일로 이동하고 update 함수를 정의하자.


update()함수에서는 요청이 들어오면 send()함수를 이용해 200 상태코드만 응답하도록 변경하였다. 그리고 나서 다시 테스트를 돌려보면 테스트에 통과한다.



*중간점검   구조 정리

./

./app.js: 서버 구동 db sync
./models.js: db table row 정의

./api/users/

index.js: rest api  함수 초기화

user.controller.js: 실제 rest  api함수들의 동작을 정의

user.spec.js: test 구동을 위한 코드


  • 실행 CRUD 기능 동작 여부

    -GET /users


-GET /users/:id


-GET /users/:name

(구현 아직 안함)

-POST body={name: string}



-DELETE /users/:id




- PUT /users/:id 




-> update(PUT) 기능 동작

출처:  http://webframeworks.kr/tutorials/expressjs/expressjs_orm_two/




  • npm test 기능 동작 여부
    -GET, PUT


GET, PUT 이렇게 2개의 passing 떠야 하는데 현재 PUT 검사하는중 게다가 PUT 200 status response 중임.


-> 해결


원래 it.only( … ) 되어있던 코드를 

it( … )으로 변경함



-DELETE POST

DELETE POST TABLE length 변경하므로 코드 리펙토링 구현하도록 한다.


지금까지 만든 것은 https://github.com/kiryun/NodeJS_REST-API_tutorial 의 Second Commit에 올려놨다.


출처: http://webframeworks.kr/tutorials/nodejs/api-server-by-nodejs-04/


*Mocha
노드에서 가장 유명한 테스트 툴은 Mocha이다.
Mocha 테스트 코드를 구동시켜주는 테스트 러너이다.Mocha역시 노드로 작성된 패키지중 하나로 npm으로 설치 가능

$ npm i mocha —save-dev

—save-dev 옵션은 개발환경을 위한 패키지 의존성을 설정하기 위한 것이다. 명령을 실행하고 나면 package.json devDependencies속성에 모카 설치정보가 추가된다.


  • 패키지파일의 의존성에 대해
    npm
    프로젝트에 사용되는 노드 패키지 모듈들을 모두 package.json 기록한다.
    대부분은 dependencies 속성에 패키지명과 버전이 적힌다. 서버에 코드를 배포한뒤 서버에서 npm install —production 으로 노드 패키지들을 설치하게 되는데 이때 노드는 package.json dependencies 참고해서 여기에 있는 것들을 설치한다.

    그럼 devDependencies 언제 사용할까?
    ->
    이것은 순전히 개발자를 위한 정보이다. 코드를 서버에 배포하지 않고 다른 개발자가 코드를 코드 저장소에서 다운로드했을
    개발자는 npm install 필요한 노드 패키지를 설치한다. 이때 노드는 똑같이 dependencies속성에 있는 패키지들을 설치한다.
    그리고 —production 없기 때문에 devDependencies속성에 있는 패키지들도 추가로 설치하는 것이다.

    다시 모카로 돌아와서
    테스트 코드는 test suite test 구분할 있다. 수트는 테스트들을 모아놓은 하나의 환경이라고 생각하면 되고, 테스트가 실제 테스트를 수행하는 코드이다.
    모카에서는 각각 describe() it()함수로 기능을 제공한다.
    -describe():
    테스트 수트
    -it():
    테스트
    함수를 이용하여 테스트 코드를 작성해보자. User API 대한 테스트 코드는 api/users/user.spec.js 작성한다.(보통 파일명에 spec이라는 단어가 들어가면 테스트 코드이다.)


모카로 작성한 테스트 코드는 기본적으로 이러한 구조를 가짐.
describe()
함수의 첫번째 파라미터로 테스트 수트의 설명을 서술형 문자열로 넣고 

두번째 파라미터로 함수를 입력함. 비동기 로직의 콜백 형식으로 넣는 것이다.

안에는 it()함수를 이용해 실제 테스트 코드를 작성한다.

설치한 모카 패키지는 node_modules폴더에 있음 npm 패키지 중에 실행 파일이 있는 경우 모두 node_moules/.bin 폴더에 링크된 파일이 위치한다. 여기에 저장된 mocha 명령어를 이용해서 테스트를 실행한다.

$ node_modules/.bin/mocah api/users/user.spec.js


npm start 사용했던것 처럼 npm test 대한 스크립트를 package.json  추가해서 테스트 명령어를 간편하게 한다.


package.json




*Should

이번에 추가할 것은 검증 로직이다. describe(), it() 이용해서 테스트를 작성했지만 진짜 테스트 코드( 결과가 맞는지 확인) 아직 없음. 노드 기본 모듈중 assert 모듈을 이용해 보자.


assert모듈의 equal()함수는 개의 파라미터를 받는데 값이 같은 경우 지나가고 그렇지 않을 경우 에러를 던진다. npm test 이용해 테스트를 실행해보자.


assert.equal() 실행결과 에러를 던지면서 모카에서는 위와 같은 결과를 리포팅 한다. 만약 테스트가 통과한다면 성공 메세지를 보여준다.



assert 노드 기본 모듈이긴 하지만 테스트에서는 이것 말고 다른 모듈을 사용하라고 . 노드 공식 페이지에서 그렇게 이야기하고 있음.

가장 많이 쓰는 모듈중 하나가 should이다. 이것은 서술식의 검증을 코드로 작성할 있게해줌.(코드 쓰는게 마치 서술식 문장같음)


$ npm i should —save-dev



(true) 같은가(should be equal) (true)



*supertest

마지막으로 api  테스트를 가능하게 해주는 노드 패키지가 있는데 바로 supertest이다. supertest 우리가 만든 express 서버를 구동한 http요청을 보내고 응답받는 구조인데 응답을 should 검증하면 되는 것이다.


supertest 설치한다.


$ npm i supertest —save-dev


supertest 사용하려면 서버 역할을 하는 express객체를 가져와야 한다. supertest 실행 함수 파라미터로 사용하기 위함이다. app.js파일에서 우리가 만든 app변수를 외부 노출하여 모듈로 만들어 줘야하는데 module.exporets 키워드로 app 노출해 주면 된다.


//…

module.exports = app;


이제 테스트 파일인 api/users/user.spec.js파일에서 모듈을 가져와 슈퍼테스트와 결합해 사용해 본다.




실행할때 원래 켜져있는 서버가 있으면 안된다. 테스트와 동시에 서버 실행이기 때문이다.


request 상수에 슈퍼테스트 모듈을 할당했음. 그리고  app모듈을 request() 함수의 파라미터로 넣었는데 이것은 우리가 만든 express  서버인 app 슈퍼테스트로 테스트하겠다는 의도이다.


슈퍼테스트는 함수 체이닝을 이용해 get()함수로  api요청을 보낸다. expect()함수로 응답 코드를 설정한 실제 요청을 보내고 응답되면 end()함수에 파라미터로 넣은 콜백함수가 동작하게 되는 구조이다. 콜백함수는 err res  개파라미터를 받는데 요청에 실패하면 err 객체가 활성화되고 그렇지 않으면 res.body 통해 응답 바디에 접근할 있음. 여기서는 api 상태코드 200 리턴하는가만 체크하는 코드이다.

그리고 마지막에 done()함수를 호출 했다. 이것은 it()함수의 두번째 파라미터인 콜백함수의 파라미터인데 슈퍼테스트가 http요청을 하는 비동기 로직이기 때문에 모카측에서 it()함수가 종료되는 시점을 알기위해 사용되는 함수이다.


바디를 점검하는 코드이다.



should.be.an.instanceof() 함수를 이용해 응답 바디의 타입이 배열인지 체크했다. 그리고 함수 체이닝을 이용해 배열의 길이가 3인것을 확인했음.


다음 코드는 res.body 배열임을 확인했으므로 배열 메소드인 map() 이용해 배열의 요소를 점검한다. 배열의 요소 user id name프로퍼티를 가지고 있고 가각 숫자형, 문자형임을 확인했음.


모든 검증을 마치면 모카에게 it()함수가 종료됨을 알리는done()함수를 호출하고 테스트를 종료한다.

이와 같은 형태로 나머지 api(get, post, /users/:id ) 테스트코드를 작성하도록 하자.


test-driven development https://github.com/kiryun/NodeJS_REST-API_tutorial 이곳에 올려놨다.


Node.js REST API 코드 리펙토링
출처: http://webframeworks.kr/tutorials/nodejs/api-server-by-nodejs-03/#


*Router

app.js 파일을 보면 거의 100줄이 되었다. 처음에 비해 코드가 많이 길어졌음. 이번에는 코드 리펙토링 하는 것을 해보려고한다.


express 크게 네부분으로 나눌 있다.

- Application

- Request

- Response

- Router

처음 가지는 모두 설명했고 코드로 구현해 봤음.


  • express Router
    express 객체는 기본적으로 get(), post() 따위의 라우팅 설정 함수가 있음. 하지만 우리가 작성했던 방식으로 코드를 작성하게 되면 코드는 파일 안에서 길어지게 되고 결국 가독성이 떨어지게 것임.
    express Router 클래스를 제공하는데 이를 이용하면 라우팅 코드를 모듈화 있다.그렇다 노드의 모듈을 얘기하는 것임. 결국 라우팅 로직을 모듈화하면 이를  require()함수로 불러서 사용할 있는 장점이 있음.

    const express = require(‘express’);
    const router = express.Router();

    router.get(‘/users’. (req, res) => {
    // …
    });

    //delete, post ….

    module.exports = router;

    express 모듈의 Router 클래스로 객체를 만들어 router상수에 할당한다. 그리고 router객체에서 제공하는 get(), delete(), post()  따위의 함수로 라우팅 로직을 구현한다.
    이것은 우리가 express 객체 app 이용한 것과 매우 똑같다. 마지막으로 module.exports 이용해 노드 모듈로 만들었음.

  • User 라우팅 모듈 만들기
    api/users/index.js 라우팅 모듈을 만들어 볼것이다. app.js 있는 라우팅 코드 부분을 이쪽으로 옮긴다. 그리고 app 상수를 모두 router상수로 변경한다.


  • User 라우팅 모듈 사용하기
    app.js 에는 user 라우팅 코드가 없어졌음. 모두  api/users/index.js  파일로 모듈화 되어 이동되었기 때문이다.
    이제는 모듈을 app.js에서 불러와 사용해야 한다. 여기서 중요한 점은
    express Router  클래스로 만든 User  모듈은 express 미들웨어가 것이다. 그렇기 때문에 express 객체 app use() 함수로 미들웨어를 사용할 있게 되었음.

    app.use(‘/users’, require(‘./api/users’));

    다른 미들웨어를 추가하는 것과 다른 점은 파라미터가 두개라는 것이다. use()에서 파라미터를 개사용하는 경우는 라우팅 모듈을 설정할때임.
    코드의 의미는모든 리퀘스트중 경로가 ‘/users’ 시작되는 요청에 대해서는 두번째 파라미터로 오는 미들웨어가 담당하도록 한다.
    그렇기 때문에 api/users/index.js 코드의 /users 시작되는 파라미터를 수정해줘야함.
    -> router.get(‘/:id’, (req, res) => { /* … */ }); 처럼 수정

  • 라우팅 컨트롤러 만들기
    api/users/user.controller.js 파일 생성


index(), show(), create(), destory() 라는 함수를 만들어 모듈로 만들었다. 이제 외부에서 모듈을 require()  함수로 불러서 사용할 있음.
4개의 함수는 4개의  api 연관된 것이다.

-index() : GET /users

-show() : GET /users/:id

-delete() : DELETE /users/:id

-create() : POST /users


함수와 연결된 api 로직. 그러니깐 get(), delete()  따위의 라우팅 함수 두번째 파라미터를 각각의 함수로 이동한다. 그리고 이컨트롤러 모듈을 api/users/index.js 파일에 불러와 사용한다.

코드는 https://github.com/kiryun/NodeJS_REST-API_tutorial 에 올려놨다.

지금까지 작성한 파일을 정리해보면 다음과 같다.

-app.js : express로 서버 설정 및 구동

-api/users/index.js: User API에 대한 라우팅 설정

-api/users/user.controller.js: User API에 대한 실제 로직



+ Recent posts