옮긴이 : 종종 브라우저로 원문 링크에 들어가지지 않는 현상이 생깁니다. 이럴때는 Medium 앱으로 Architecture of Node.js Internal Codebase를 검색하여 들어가시면 됩니다.


첫째로 JavaScript라는 단어에 대해...

StackOverflow 공동 창시자인 Jeff Atwood가 Coding Horror 프로그래밍 블로그에 글을 올렸다.
"JavaScript로 작성될 수 있는 모든 앱들은 결국 JavaScript로 작성될 것이다."

JavaScript는 지난 몇년간 가장 인기있는 프로그래밍 언어 중 하나로 크게 확장되었다. 실제로 2016년 SO 개발자 조사에 따르면, StackOverflow에서 가장 인기있고 가장 기술력있는 언어로 1등을 달성하고, 다른 결과들도 좋은 기록을 세웠다.

Node.js는 바이너리 데이터 조작, 파일 시스템 I/O 오퍼레이션, 데이터베이스 접근, 컴퓨터 네트워킹 등과 같은 중요한 서버사이드 기능을 담은 기초(foundation)로서 JavaScript 환경의 서버사이드이다. 이것은 Django(Python), Laravel(PHP), RoR(Ruby) 등과 같은 현존하는 시도되고 테스트된 프레임워크 사이에서 특출난 특징을 가진다. Node.js는 PayPal, Tinder, Medium, LinkedIn, Netflix와 같은 기술 리더 회사들이 사용한다는 것이다. 몇몇은 Node.js가 1.0버전이 되기도 전에 이미 사용하고 있었다.

나는 최근에 StackOverflow에서 Node.js 내부 코드베이스의 아키텍처에 관한 질문에 답변을 달았었다. 그것이 이 글을 쓰도록 영감을 불어넣어 주었다.


사실 공식적인 문서에서는 Node.js가 무엇인지 설명하기에 크게 도움이 되지 않는다.


"JavaScript 런타임은 크롬의 V8 JavaScript 엔진으로 만들어졌다. Node.js는 event-driven, non-blocking I/O 모델이고..."

이 말을 이해하고 실제 숨어있는 힘을 이해하기 위해 Node.js 요소들을 쪼개어보고, 몇몇 중요 용어들을 상세히  설명 할 것이다. 다음 어떻게 다른 조각들이 서로 소통하여 Node.js를 강력하게 만들 수 있는지 설명할 것이다.

Node.js 아키텍처 (High-Level to Low-Level)

요소들/종속물(COMPONENTS/DEPENDENCIES)
V8 : 구글에의해 오픈소스화된 높은 퍼포먼스를 자랑하는 JavaScript 엔진이며 C++로 구현되어있다. 크롬에서 사용하는 엔진과 동일한 엔진이다. V8은 당신이 JavaScript로 작성한 코드를 받아서, 기계코드로 컴파일한 뒤(이렇기에 빠르다), 실행시킨다. 어떻게 V8이 그냥 빠를 수 있을까? StackOverflow의 답변을 확인해보아라.

libuv : 비동기적 기능을 제공하는 C 라이브러리이다. 이것은 다른 중요한 기능들 사이에서 이벤트 루프, 스레드 풀, 파일 시스템 I/O, DNS 기능, 네트워크 I/O를 관리한다.

다른 C/C++ 요소들/종속물 : c-ares, crypto(OpenSSL), http-parser, zlib. 이 요소들은 서버에서 네트워킹, 압축, 암호화와같은 중요한 기능을 수행하기 위해 low-level로 소통하는 방식을 제공한다. 

앱/모듈 : 이것은 JavaScript 코드가 존재하는 모든 곳이다. 당신의 앱 코드, Node.js의 코어 모듈, npm으로 설치한 모든 모듈, 당신이 작성한 모든 모듈까지 당신은 대부분의 시간을 여기서 보낼 것이다.

바인딩 : 당신은 아마 이 시간을 통해 Node.js가 JavaScript와 C/C++로 작성되었다는 것을 알게 되었을 것이다. 여기엔 수많은 C/C++ 라이브러리가 있는데 그 이유는 간단하다: 빠르기 때문이다. 그러나 어떻게 당신이 JavaScript로 짠 코드가 C/C++로 짠 코드와 자연스럽게 소통할까? 그것은 세개의 서로 다른 언어이지 않는가? 그렇다. 다른 언어로 짜여진 코드는 보통 서로 서통할 수 없다. 바인딩 없이는 안된다. 이름에서 이야기하듯 바인딩은 한 언어를 다른 언어에 '묶어(bind)' 코드를 접합시킨다. 그러면 서로 의사소통을 할 수 있게 된다. 이 경우(Node.js) 바인딩은 단순히 C/C++로 작성된(c-ares, zlib, OpenSSL, http-parser 등) Node.js 내부 코어 라이브러리들을 JavaScript에 연결한다. 바인딩을 작성하는데 첫번째 이유는 재사용이다: 만약 필요한 기능들이 이미 구현되있다면, 그것을 가져다 사용하면 된다. 단지 언어가 다르다는 이유로 그것을 다시 짤 필요는 없다. 왜 그냥 연결시켜 사용하지 않겠는가? 두번째 이유는 퍼포먼스이다: C/C++ 같은 시스템 프로그래밍 언어들은 일반적으로 고수준언어(Python, JavaScript, Ruby등) 보다 빠르다. 그러므로 CPU-집약 오퍼레이션 코드는 C/C++로 작성하는 것이 현명할 것이다.

C/C++ Addon : 바인딩은 단지 zlib, OpenSSL, c-ares, http-parser와 같은 Node.js 내부 라이브러리를 접합하는 기능만 한다. 만일 당신이 써드파티의 C/C++ 라이브러리(외부 C/C++ 라이브러리)를 넣고 싶으면, 스스로 라이브러리를 접합시켜야한다. 당신의 코드를 접합해주는 코드가 addon이라 불리는 것이다. 바인딩과 addon은 당신의 JavaScript 코드와 Node.js의 C/C++ 코드를 연결하는 다리라고 생각하면 편할 것이다.

용어들(TERMINOLOGIES)
I/O : Input/Output의 약자이다. 이것은 기본적으로 시스템 I/O의 하위 시스템에의해 주로 다뤄지는 모든 컴퓨터 오퍼레이션을 나타낸다. I/O 바운드( https://en.wikipedia.org/wiki/I/O_bound)오퍼레이션은 디스크/드라이브와의 상호작용을 포함한다. 예를들어 데이터베이스 접근과 파일 시스템 오퍼레이션을 포함한다. 연관된 다른 개념에는 CPU 바운드, 메모리 바운드 등이 있다. 오퍼레이션이 I/O 바운드에 속하는지 CPU 바운드에 속하는지 아니면 다른데 속하는지 구별하는 좋은 방법은, 특정 오퍼레이션이 더 많은 퍼포먼스를 내고 리소스를 많이 잡아먹는지 체크하면 된다. 예를들어 한 오퍼레이션이 눈에 띄게 빨라지고 CPU 파워가 증가했다면 그것은 CPU 바운드 이다. 

Non-blocking/비동기적 : 보통 리퀘스트가 들어오면 앱은 그 리퀘스트를 다루고, 그 리퀘스트의 작업이 끝날때까지 다른 모든 오퍼레이션을 멈춘다. 다음은 실제 일어날 수 있는 문제이다: 만일 한꺼번에 수많은 리퀘스트가 들어오면, 각 리퀘스트는 이전 리퀘스트의 작업이 끝날때까지 기다려야한다. 다른 말로는, 이전 오퍼레이션이 다음 것을 블럭(block)시킬 것이다. 최악의 상황은,  이전 리퀘스트가 긴 응답시간(eg. 먼저 1000개의 계산 후, DB로부터 3GB 데이터를 읽는 경우) 이라면, 다른 리퀘스트들은 긴 시간동안 멈춤/블럭이 되 있을 것이다. 이 문제를 해결하기 위한 방법에는 각 리퀘스트에 대해 멀티 프로세싱 혹은/그리고 멀티 스레딩이 있을 수 있다. Node.js는 이것을 좀 다르게 다룬다. 모든 새 요청에 대해 새 스레드를 만드는 것 대신, 요청들은 한 메인스레드에서 다뤄지며, 그것이하는 일의 대부분이다.

리퀘스트들을 다루기 : 리퀘스트에 포함된 모든 오퍼레이션(eg. 파일 시스템 접근, 데이터베이스 읽기/쓰기)은 백그라운드에서 (위에서 언급한)libuv에의해 관리되는 일꾼 스레드로 보내진다. 즉, 리퀘스트 안에 있는 I/O 오퍼레이션들은 메이스레드가 아닌 곳에서 비동기적으로 다뤄진다. 이 방법으로 무거운 것을 다른곳으로 보냄으로써, 메인스레드가 블럭되지 않게 해준다. 당신의 앱 코드는 한 순간에 한가지 일만 하고 있고, 그것은 메인스레드 안에 있을 것이다.  libuv의 스레드풀에 있는 모든 일꾼 스레드는 당신이 접근할 수 없게 만들어 놓았다. 여러분은 그들에게 직접 접근할 수 없으므로 그것에대해 걱정할 필요도 없다. Node.js가 당신을 위해 관리해주기 때문이다. 이러한 아키텍처는 I/O오퍼레이션을 특히 효율적이게 만들어준다. 그러나 이것에 단점이 없는 것은 아니다. 오퍼레이션에는 I/O 바운드 하나만 있는게 아니라 CPU바운드, 메모리 바운드 등이 더 있다. 조금만 더 이야기해보자면 Node.js는 I/O 작업을 위한 비동기 기능만을 제공한다. CPU 집약 오퍼레이션을 수행하는 방법이 있지만, 이 글의 논지가 아니니 넘어가도록 하자.

이벤트기반(Event-Driven) : 일반적으로 현대의 대부분 시스템은 메인 앱이 꺼져야 들어오는 리퀘스트에의해 프로세스가 초기화된다. 그러나 그들이 다르게 흘러간다면? 전형적인 구현은 리퀘스트를 다음 순서처럼 다룬다: 리퀘스트를 위한 스레드를 하나 만들고, 다른 오퍼레이션이 끝난뒤 그 오퍼레이션을 수행하며, 오퍼레이션이 느려지면 그 스레드에 있는 모든 오퍼레이션이 멈추거나 느려진다. 모든 오퍼레이션이 완료되면 리스폰을 돌려준다. 그러나 Node.js에서는 메인 앱에의한 것이든 리퀘스트에의한 것이든 모든 오퍼레이션이 트리거(trigger)를 기다리는 Node.js 이벤트로 등록된다.

런타임(시스템) : Node.js 런타임은 고수준, 저수준 둘 다 Node.js 앱 실행을 같이 도와주는 전체 코드베이스(위에서 언급한 요소들)이다.

모든것을 섞어보자. (PUTTING EVERYTHING TOGETHER)
이제 Node.js 요소의 고수준 관점에서 보자. 우리는 그것의 아키텍처를 더 잘 알기위해 그것의 워크플로우(workflow)를 알아보고, 어떻게 다른 요소들이 서로 소통하는지도 알아볼 것이다.

Node.js 앱이 실행되면, V8엔진이 당신의 앱 코드를 실행시킨다. 당신 앱의 객체들은 옵저버(이벤트로 등록할 수 있는 기능)의 목록에 넣어둔다. 이 옵저버는 각 이벤트들이 발생할 때 알림을 받는다.

한 이벤트가 발생되면, 그것의 콜백 함수가 이벤트 큐(event-queue)에 들어간다. 큐에 이벤트가 한개라도 남아있으면 이벤트 루프(event loop)는 큐에서 이벤트를 빼어 콜스택(call stack)에 집어넣는다. 이전 이벤트가 처리가 되면 이벤트 루프는 다음 이벤트를 콜스택에 넣는다는 점만 유의하면 된다.

콜스텍에서 우연히 I/O 오퍼레이션을 만나면, 처리하기위해 libuv에게 넘어간다. 기본적으로, libuv는 4개의 일꾼 스레드를 유지하고 있다. 물론 더 많은 스레드를 만들 수도 있다. 만약 요청이 파일 시스템 I/O나 DNS 관련이면, 처리를 위해 스레드 풀에 할당될 것이다; 다른경우, 네트워킹과 같은 다른 리퀘스트들에서는 특정 플랫폼의 매커니즘이 그 리퀘스트들을 알아서 배치할 것이다.

스레드 풀을 사용하게 만드는 I/O 오퍼레이션들(파일 I/O, DNS 등)은 데이터베이스 트렌젝션이나 파일 시스템 접근과 같은 오퍼레이션을 수행하기 위해 Node.js의 저수준 라이브러리로 일꾼 스레드가 작용할 것이다. 처리가 끝나고 libuv는 이벤트가 다시 메인스레드에서 처리될 수 있게 이벤트 큐에 다시 집어넣는다. 이 동안에는 libuv가 비동기의 I/O 오퍼레이션을 다루고 있고, 메인스레드는 마냥 앉아서 처리 결과를 기다리는 것이 아니라 자신의 할 일을 하고 있는다. libuv에의해 돌려받는 이벤트는 이벤트 루프에의해 콜스택으로 돌아왔을때, 다시 메인스레드에서 처리될 수 있다. 이것이 Node.js에서 이벤트 생명주기이다.

mbg는 기가막히게 Node.js와 레스토랑을 비유해 놓았다. 나는 Node.js 사이클을 더 쉽게 설명하기 위해 그의 이야기를 빌려 설명하겠다.

Node.js 앱을 스타벅스 카페라 생각해보자. 높은 효율과 숙련이 잘 된 웨이터(한 명이고 메인스레드라 할 수 있다)가 주문을 받는다. 만약 같은 시간대에 많은 고객이 들이닥치면 웨이터의 서비스를 받기위해 줄을 서게 된다(이벤트 큐에 넣는다). 한 고객이 웨이터로부터 서비스를 받으면 웨이터는 메니저(libuv)에게 주문을 넘겨준다. 각 주문을 어떤 바리스타(일꾼 스레드 혹은 플랫폼-특정 매커니즘)에게 주문을 할당할지 정한다. 바리스타는 다른 다른 재료들과 기계를 써서(저수준 C/C++ 요소들) 고객이 요청한 것들의 음료를 만든다. 보통 4명의 바리스타가 특정 라떼(파일 I/O, DNS 등)를 돌아가면서 만든다. 그러나 주문이 피크치에 달할 때는 더 많은 바리스타를 일시적으로 고용할 수 있다(개업한 날의 이야기이지, 점심시간대의 이야기는 아니다). 한 번 웨이터가 메니저에게 주문을 넘기면, 웨이터는 다른 고객을 서브하기 위해 커피 만드는 것을 기다리지 않는다. 대신에 다음 고객을 부른다(이벤트 루프에의해 다음 이벤트를 큐에서 꺼네어 콜스택에 넣는다). 이벤트가 콜스택 안에 있는 것은, 고객이 카운터에서 서브를 받으며 있는 것과 비슷하다. 커피가 다 만들어지면, 커피는 고객 줄의 끝으로 보내진다. 커피가 카운터로 나오면 웨이터는 고객의 이름을 부를 것이고, 고객은 주문한 커피를 받게된다. (비유가 다소 현실세계에서는 이상할 수 있지만, 프로그램 관점에서 프로세스를 고려할 때는 딱 들어 맞을 것이다)


고수준 관점의 전반적인 Node.js 내부 코드 베이스와 그것의 일반적인 이벤트 생명주기에대해 설명이 끝났다. 그러나 이 총람은 굉장히 일반적인 상황의 이야기이고 많은 이슈와 세부적인 요소에는 들어맞지 않을 수 있다는 점을 유의하길 바란다. 예를들어 CPU 바운드 핸들링이나 Node.js 디자인 패턴 등등이 있다. 더 많은 다른 글들의 토픽들이 그 부분을 채워줄 것이다. 



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

Node.js 쓰다보면 메모리를 점점 잡아먹는 현상을 발견할 있다. 이것은 굉장히 일반적인 현상이고, Node.js에서 쓰는 V8엔진은 기본으로 1.4GB 메모리를 한계로 잡아놓고(기본적으로 64비트는 1.4GB, 32비트는 512MB 한계로 잡는다.) 안에서 메모리를 점점 사용하게된다. 나의 경우는 아마존 프리티어를 사용하기때문에 메모리가 1GB 밖에 되지않아서 메모리 제한을 낮추어야했다.


--max-old-space-size=512

메모리 사용을 512MB 제한하라는 옵션이다. 아래와같이 사용할 있다.

혹은 forever 모듈을 사용하고있다면 아래와같이 사용하면 된다.


--expose-gc

가비지 컬랙터를 수동으로 전환한다. 내가 자바스크립트 코드에서 gc(); 호출하면된다. 적어도 30초에 한번씩은 가비지 컬랙팅을 하기를 추천 한다고 한다.


--max-new-space-size=2048

만약 서버 멈춤이 짧은것이 중요하다면 --max-new-space-size=2048 플래그를 사용하면 된다. 이것은 피크 퍼포먼스를 줄여버리기도하지만, 100ms정도로 서버 멈춤이 짧아질 것이다


--noincremental-marking

반면 피크 퍼포먼스가 중요하고 서버 멈춤이 오래 걸려도 괜찮다면 --noincremental-marking 플래그를 사용하면 된다. 플래그를 사용하면 1GB 1 정도의 서버 멈춤을 예상할 있다. 따라서 주로 작은 (heaps)이나 일괄 처리 테스크(순차 처리 테스크) 유용하게 사용된다.



참고

  • StackOverFlow : NodeJS / ExpressJS Memory Leak
    http://stackoverflow.com/questions/22507667/nodejs-expressjs-memory-leak
  • StackOverFlow : Is anybody making a Node-optimized V8?
    http://stackoverflow.com/questions/8263210/is-anybody-making-a-node-optimized-v8
  • 600k concurrent websocket connections on AWS using Node.js
    http://www.jayway.com/2015/04/13/600k-concurrent-websocket-connections-on-aws-using-node-js/
  • How does Node.js do memory allocation?
    https://www.quora.com/How-does-Node-js-do-memory-allocation



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,


종종 좋은 것들은 한곳에 머무르지 않고, 그래서 모카(Mocha)와 Superagent를 이용한 테스트와 Mongoskin과 Express.js를 사용하여 Node.js와 MongoDB를 이용한 JSON REST API 서버를 만드는 튜토리얼을 Express.js 4버전이 출시됨과 함께 만들게 되었다. 최신의 Express.js 4, Node.js, MongoDB를 다루는 튜토리얼을 만나보자.
새 튜토리얼을 위한 소스코드는 github.com/azat-co/rest-api-express (master branch)에서 확인할 수 있다. 이전의 Express 3.x버전을 위한 튜토리얼 코드는  아직 작동하고 express3 branch에 있다.



Express.js4와 MongoDB REST API Tutorial은 아래 파트로 나뉘어 구성되어있다.
1. Node.js와 MongoDB REST API 개요
2. 모카와 Superagent를 이용한 REST API 테스트
3. NPM-ing Node.js Server Dependencies
4. Express.js 4.x Middleware Caveat
5. Express.js와 MongoDB (Mongoskin) 구현
6. Express.js 4 랩을 실행하고 모카를 이용한 MongoDB 테스팅
7. Express.js와 Node.js의 결론 및 확장성

만약 당신이 자장소와 그것이 무엇을 동작하는지로부터 코드의 동작에 관심이 있다면, 여기 REST API server가 어떻게 다운로드되고 동작하는지 간단한 설명이 여기있다.
$ git clone git@github.com:azat-co/rest-api-express.git
$ npm install
$ node express.js


$ mongod 와 함께 MongoDB를 시작한다. 그다음, 새 터미널창을 띄워서 모카 테스트를 실행한다. :

$ mocha express.test.js


아니면, 모카를 전역으로 설치하지 않은 경우. :

$ ./node_modules/mocha/bin/mocha express.test.js



1. Node.js와 MongoDB REST API 개요
Node.js, Express.js, MongoDB(Mongoskin) 튜토리얼은 모카와 SuperAgent를 사용해 테스트를 해나갈것이다. 이것은 Node.js의 JSON REST API 서버를 만들면서 테스트 주도 개발(Test-Driven Development)을 필요로 한다. 
서버 응용프로그램은 Express.js 4버전대 프레임워크와 MongoDB를 위한 Mongoskin 라이브러리를 필요로한다. 이 REST API 서버에서 우리는 CRUD(create, read, update and delete)기능을 실행하고 app.param( ), app.use( )와 같은 Express.js middleware 방식의 메소드를 실행할 것이다.

가장 처음에 할 것은, MongoDB를 설치하는 것이다. 이 링크를 따라가면 할 수 있을 것이다.
우리는 아래의 버전의 라이브러리를 사용하게 될 것이다.
  • express: ~4.1.1
  • body-parser: ~1.0.2
  • mongoskin: ~1.4.1
  • expect.js: ~0.3.1
  • mocha: ~1.18.2
  • superagent: ~0.17.0

만약 버전이 맞지 않다면 코드가 동작하지 않을 수도 있다 :-(



2. 모카와 Superagent를 이용한 REST API 테스트
시작하기 전에, 우리가 만들게될 REST API 서버에 HTTP 요청을 만드는 기능 테스트를 한번 적어보자. 만약 모카를 사용할 줄 알거나, 바로 Express.js 앱 구현을 해보고 싶은 사람들은 마음대로 해도 된다. 당신은 물론 터미널에서 CRUL테스트를 할 수도 있다.
우리는 이미 Node.js, npm, MongoDB를 설치했다고 가정하고, 새 폴더를 만들어보자.
$ mkdir rest-api
$ cd rest-api


우리는 모카, Expect.js, SuperAgent 라이브러리를 사용하게 될 것이다. 그것들을 설치하고, 프로젝트폴더에 들어가서 이 명령을 실행해라.

$ npm install mocha@1.18.2 --save-dev
$ npm install expect.js@0.3.1 --save-dev 
$ npm install superagent@0.17.0 --save-dev


Note: 당신은 물론 (명령에 -g를 넣고) 모카를 전역으로 설치할 수도 있다. 
이제 아까 그 폴더에 express.test.js파일을 만들자. 아래 6가지를 진행할 것이다.
  • 새 객체를 생성한다.
  • 객체의 ID를 가져온다.
  • 객체의 모든 정보를 가져온다.
  • ID를 이용해 객체를 업데이트한다.
  • ID를 이용해 객체를 제거한다.
HTTP 요청은 Super Agent’s와 연관된 (단지 테스트케이스를 넣기만 하면되는) 함수들을 사용하면 굉장히 식은죽먹기이다. 
이 강좌는 Express.js 4, MongoDB, Mocha를 사용하여 REST API를 만드는것이 목적이기 때문에 테스트케이스(test suits)에 대해 깊게 들어가지는 않겠다. 코드를 복붙하시오!

아래 코드는 express.test.js파일이다.
var superagent = require('superagent')
var expect = require('expect.js')

describe('express rest api server', function(){
  var id

  it('post object', function(done){
    superagent.post('http://localhost:3000/collections/test')
      .send({ name: 'John'
        , email: 'john@rpjs.co'
      })
      .end(function(e,res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(res.body.length).to.eql(1)
        expect(res.body[0]._id.length).to.eql(24)
        id = res.body[0]._id
        done()
      })    
  })

  it('retrieves an object', function(done){
    superagent.get('http://localhost:3000/collections/test/'+id)
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(typeof res.body).to.eql('object')
        expect(res.body._id.length).to.eql(24)        
        expect(res.body._id).to.eql(id)        
        done()
      })
  })

  it('retrieves a collection', function(done){
    superagent.get('http://localhost:3000/collections/test')
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(res.body.length).to.be.above(0)
        expect(res.body.map(function (item){return item._id})).to.contain(id)        
        done()
      })
  })

  it('updates an object', function(done){
    superagent.put('http://localhost:3000/collections/test/'+id)
      .send({name: 'Peter'
        , email: 'peter@yahoo.com'})
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(typeof res.body).to.eql('object')
        expect(res.body.msg).to.eql('success')        
        done()
      })
  })
  it('checks an updated object', function(done){
    superagent.get('http://localhost:3000/collections/test/'+id)
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(typeof res.body).to.eql('object')
        expect(res.body._id.length).to.eql(24)        
        expect(res.body._id).to.eql(id)        
        expect(res.body.name).to.eql('Peter')        
        done()
      })
  })    
  
  it('removes an object', function(done){
    superagent.del('http://localhost:3000/collections/test/'+id)
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(typeof res.body).to.eql('object')
        expect(res.body.msg).to.eql('success')    
        done()
      })
  })      
})

테스트를 하기위해, $ mocha express.test.js 커맨드를 날릴 것이다. (모카를 전역으로 설치하지 않은 경우 $ ./node_modules/mocha/bin/mocha espress.test.js)


3. NPM-ing Node.js Server Dependencies
이 튜토리얼에서 우리는 Mongoskin을 사용할 것이다. 또한, Mongoskin은 Mongoose나 shema-less보다 훨씬 가볍다. 자세한 내용은 Mongoskin comparison blurb를 확인해보기 바란다.
Express.js는 Node.js HTTP module 핵심 객체로 감싸져있다. Express.js 프레임워크는 Connect middleware의 상위층을 기반으로 만들어져있고, 어마어마하게 많은 편리함을 제공한다. 몇몇 사람들은 ...

만약 당신이 이전 섹션(Text Converage)에서 rest-api 폴더를 만들었다면, 어플리케이션 모듈을 설치하기 위해 아래 명령만 입력하면 된다. :
$ npm install express@4.1.1 --save
$ npm install mongoskin@1.4.1 --save


4. Express.js 4.x Middleware Caveat

슬프게도 NPM express만 하는 것으로는 Express.js와 함께 최소한의 REST API 서버를 구축하는게 불가능하다. 왜냐하면 4.버전대의 middlewares은 번들이 아니기 때문이다!(the middlewares are not bundled) 개발자들은 Express.js 4.x.왼쪽에 있는 express.static를 제외한 분리된 모듈들을 설치해야한다. 그리고 들어오는 정보를 파싱하기위해 body-parser를 추가해야한다:
$ npm install body-parser@1.0.2 --save


5. Express.js와 MongoDB (Mongoskin) 구현

제일 처음에 우리의 express.js 안에 우리의 dependencies를 정의해야한다. :
var express = require('express'),
  mongoskin = require('mongoskin'),
  bodyParser = require('body-parser')


3.x대 이후 버전(물론 v4도 마찬가지), Express.js은 앱 인스턴스의 객체를 간소화해서 가져온다, 아래 라인은 서버객체를 우리에게 제공할 것이다.(번역자:그냥 import정도로 생각하면 될듯):

var app = express()


요청의 바디로부터 파람들을 추출하기위해, 우리는 아래와같은 모양의 bodyParser() middleware를 사용할 것이다.:

app.use(bodyParser())


Middleware(여기, 다른 포럼)는 Express.js에서 강력하고 편리한 패턴이고 구성요소를 연결하며 코드의 재사용을 증진시킨다.

HTTP 요청의 바디객체 파싱의 넘사벽으로부터 구해주는 bodyParser()메소드와 같이, Mongoskin은 딱 한줄의 코드로 MongoDB 데이터베이스에 접속하는게 가능하다.:
var db = mongoskin.db('mongodb://@localhost:27017/test', {safe:true})


Note: 만약 당신이 원격으로 데이터베이스에 접속하고 싶다면(MongoHQ와 같은 것들..), 당신의 username, password, host and port의 값들을 스트링으로 치환하라. 여기 URI 스트링의 포맷이 있다:
mongodb://[username:password@]host1[:port1][, host2[:port2], …[, hostN[:portN]]][/[database][?options]].

app.param() 메소드는 또다른 Express.js middleware이다. 이것은 기본적으로 “요청 핸들러의 URL페턴에 어떤 값이 있으니 매 시간마다 뭔갈 처리해라”는 것을 말하고 있다. 우리의 경우 요청 패턴이 collectionName 스트링에 콜론이 점두사로 있을 때, 우리는 특정 콜랙션을 선택한다. 그러면 다음 요청 핸들러에서 사용할 수 있는 요청 객체(widespreadreq)의 프로퍼티(콜랙션이나 다른것일 수도 있다.)로써 콜랙션을 저장한다. (번역자:뭔소린지 모르겠다)
app.param('collectionName', function(req, res, next, collectionName){
  req.collection = db.collection(collectionName)
  return next()
})


단지 유저지향적으로, 메시지와 함께 루트 라우트를 넣자.:

app.get('/', function(req, res) {
  res.send('please select a collection, e.g., /collections/messages')
})


이제 진짜 할 일을 시작한다. 다수의 요소들중에 리스트를 어떻게 가져오는지 있다 (첫번째 파라메터는 빈 오브젝트{}이고 임의의 라는 뜻이다). 이 결과는 _id에(두번째 파라메터) 의해 정렬된 10개 제한으로 낼 것이다. find()메소드는 커서를 반환하고 우리는 toArray()를 불러 JavaScript/Node.js용 배렬로 만든다. :

app.get('/collections/:collectionName', function(req, res, next) {
  req.collection.find({} ,{limit:10, sort: [['_id',-1]]}).toArray(function(e, results){
    if (e) return next(e)
    res.send(results)
  })
})


URL 패턴 파라메너에서 :collectionName 스트링에대해 언급한적이 있나? 이것과 이전 app.param() middleware는  req.collection 객체를 우리에게 준다. 이 객체는 우리의 데이터베이스에서 특정 콜랙션을 가르키고 있다.

우리는 단지 MongoDB에서 전체적인 페이로드를 지나온 이후로 마지막 시점에 만들어진 이 객체는 조금 이해하기 쉽게 해준다. 이 메소드는 서버나 다른 것들의 데이터베스가 어떠한 데이터 스트럭쳐도 받아드릴수 있기 때문에 종종 free JSON REST API라 불린다. Parse.com과 다른 백엔드 서버 제공자는 free JSON 접근을 만들어낸다. 우리 Express.js 앱에서는 이것을 위해 req.body를 사용할 것이다.
app.get('/collections/:collectionName', function(req, res, next) {
  req.collection.find({} ,{limit:10, sort: [['_id',-1]]}).toArray(function(e, results){
    if (e) return next(e)
    res.send(results)
  })
})


함수들을 구하는 findByIdfindOne과같이 생긴 단일 객체는 find()보다 빠르다. 그러나 그것들은 조금 다른 인터페이스를 사용한다 (그것들은 커서 대신에 진짜 오브젝트를 반환한다). 그러므로 그것을 기억하고 있어라. 추가적으로, 우리는 Express.js 마법에 의해 req.params.id 경로의 :id 부분으로부터 ID를 가져올 것이다.

app.get('/collections/:collectionName/:id', function(req, res, next) {
  req.collection.findById(req.params.id, function(e, result){
    if (e) return next(e)
    res.send(result)
  })
})


PUT 요청 핸들러는 update()가 증가된 객체를 반환하기때문에  더 흥미로운 것을 가져온다.
또한 {$set:req.body}는 값을 저장하는 기능을 가진 특별한 MongoDB 기능이 (보통 달러표시로 시작한다).

두번째 {safe:true, multi:false} 파라메터는 MongoDB에 callback 함수가 실행되기 전까지 동작을 멈추고 오직 한가지(첫번째) 아이탬만 처리하라고 알리는 옵션을 가진 객체이다.
app.put('/collections/:collectionName/:id', function(req, res, next) {
  req.collection.updateById(req.params.id, {$set:req.body}, {safe:true, multi:false}, function(e, result){
    if (e) return next(e)
    res.send((result===1)?{msg:'success'}:{msg:'error'})
  })
})


마지막으로 DELETE HTTP 함수는 app.del()에 의해 실행된다. 요청 핸들러에서, 우리는 그것이 그 동작을 하는 것처럼 보이는 removeById()를 사용한다. 그리고 커스텀 JSON success 메시지를 제거과정에서 내보낼것이다.:

app.del('/collections/:collectionName/:id', function(req, res, next) {
  req.collection.remove({_id: req.collection.id(req.params.id)}, function(e, result){
    if (e) return next(e)
    res.send((result===1)?{msg:'success'}:{msg:'error'})
  })
})


Note: delete는 JavaScript의 연산자이고 대신에 Express.js는 app.del을 사용한다.

아래의 경우 서버 3000포트를 시작하는 마지막라인이다.
app.listen(3000)


단지 이 경우 뭔가 잘 실행되지 않을 수 있다. 여기 express.js 파일의 풀 소스가 있다.

var express = require('express') , mongoskin = require('mongoskin') , bodyParser = require('body-parser') var app = express() app.use(bodyParser()) var db = mongoskin.db('mongodb://@localhost:27017/test', {safe:true}) app.param('collectionName', function(req, res, next, collectionName){ req.collection = db.collection(collectionName) return next() }) app.get('/', function(req, res, next) { res.send('please select a collection, e.g., /collections/messages') }) app.get('/collections/:collectionName', function(req, res, next) { req.collection.find({} ,{limit:10, sort: [['_id',-1]]}).toArray(function(e, results){ if (e) return next(e) res.send(results) }) }) app.post('/collections/:collectionName', function(req, res, next) { req.collection.insert(req.body, {}, function(e, results){ if (e) return next(e) res.send(results) }) }) app.get('/collections/:collectionName/:id', function(req, res, next) { req.collection.findById(req.params.id, function(e, result){ if (e) return next(e) res.send(result) }) }) app.put('/collections/:collectionName/:id', function(req, res, next) { req.collection.updateById(req.params.id, {$set:req.body}, {safe:true, multi:false}, function(e, result){ if (e) return next(e) res.send((result===1)?{msg:'success'}:{msg:'error'}) }) }) app.del('/collections/:collectionName/:id', function(req, res, next) { req.collection.removeById(req.params.id, function(e, result){ if (e) return next(e) res.send((result===1)?{msg:'success'}:{msg:'error'}) }) }) app.listen(3000)

코드를 저장하고 당신의 에디터를 닫아라, 우리의 소박한 Express.js REST API 서버가 완성되었다.


6. Express.js 4 랩을 실행하고 모카를 이용한 MongoDB 테스팅

이제 MongoDB가 설치되고 실행됬다는 가정($ mongod)하에 터미널에서 실행시켜볼 수 있다(modgod와 다른 창을 띄워라).
$ node express.js


그리고 다른 창에서 아래 명령을 쳐라(처음 창은 닫으면 안된다):

$ mocha express.test.js

혹은 모카를 전역으로 설치 하지 않은 경우.:

$ ./node_modules/mocha/bin/mocha express.test.js


만약 모카나 BDD 사용을 원치 않다면, CURL는 언제나 당신을 위해 있다. :-)

예를들어 POST 요청을 만들기 위해 CURL데이터이다. :
$ curl -X POST -d "name=azat" http://localhost:3000/collections/test13


그리고 결과는 아래처럼 나오게 될 것이다.:

{"name":"azat","_id":"535e180dad6d2d2e797830a5"}]


우리는 REST API 서버를 사용하기때문에 쉽게 이 객체를 확인할 수 있다.:

$ curl http://localhost:3000/collections/test13
Using CURL with Express 4 and MongoDB REST API


GET요청 또한 브라우저에서 동작할 수 있다. 예를들어, http://localhost:3000/collections/test13 링크를 당신의 로컬 서버가 포트 3000에서 돌아가고 있을때 열 수 있다.
혹은 서버의 결과를 신뢰하지 못한다면, MongoDB($ mongo)를 이용하여 데이터베이스를 확인할 수도 있다.:
> db.test13.find()


Note: 만약 데이터베이스의 이름을 test라는 이름 대신에 다른 이름으로 바꾸고 싶으면, 명령 앞에 > use your_database_name 을 써라.

이 튜토리얼에서, 우리 테스트들은 실제 동작하는 어플리케이션 코드보다 길다. 몇몇에게는 테스트-기반-개발(Test-Driven Development)을 포기하고싶게 만들지도 모르겠지만, 당신이 어떠한 크고 복잡한 응용프로그램을 개발할 때에도 좋은 TDD 습관이 당신의 개발 시간을 줄여줄것이라 믿는다. 



7. Express.js와 Node.js의 결론 및 확장성
Express.js 4와 MongoDB/Mongoskin 라이브러리는 간단하게 몇 줄 만에 REST API 서버를 구축하기에 정말 좋다. 후에, 당신이 라이브러리를 확장하고자 한다면, 그것들은 또한 당신의 코드를 구성하는 방법을 제공할 것이다.
NoSQL 데이터베이스는 MongoDB와같이 스키마를 정의할 필요 없고 어떠한 다양한 형태의 데이터를 넘기거나 저장할 수 있는 좋은 free-REST APIs이다.
express.test.js, express.js and package.json의 풀소스코드는 github.com/azat-co/rest-api-exrpess에 있다.
Express.js나 다른 자바스크립트 라이브러리에대해 더 공부하고 싶다면, 아래 Azat의 책들을 보라.
  • Practical Node.js: Building Real-world Scalable Web Apps
  • Express.js Guide: The Comprehensive Book On Express.js
..
..
..

연관 글

[Node.js, MongoDB] Node.js 설치 및 실행

[Node.js, MongoDB] MongoDB 리눅스에 설치 및 실행

[Node.js, MongoDB] MongoDB 돌리기 (+백그라운드에서 돌리기)


> (번역) Express.js 4, Node.js and MongoDB REST API 강좌


WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,