[nodejs] Node.js 개발에 필요한 기본 소양

류명운

·

2016. 10. 14. 21:49

반응형

[nodejs] Node.js 개발에 필요한 기본 소양

취준을 하다가 nodejs 관련 개발 과제를 하게되었다. 따라서 이에 필요한 기본 소양들에 대해 공부하는 겸 포스팅을 남기면서 정리하려고 한다.

본격적인 개발을 하기에 앞서 기본적으로 알아야할 사항들이며 이에 대한 목차는 다음과 같다.

  1. Node.js와 NPM(Node Package Manager) 설치하기
  2. Node.js 스크립트 실행하기
  3. Node.js 기초와 문법
  4. Node.js 프로그램 디버깅
  5. Node.js IDE와 코드 변집기
  6. 파일 변경 감시하기

그럼 이제 목차에 있는 사항들에 대한 세부적인 내용들을 학습해보도록 하겠다.

1. Node.js와 NPM 설치하기

  • 다음은 Node.js를 설치할 수 있는 다양한 방법들이다.
  • 원클릭 인스톨러 : 플랫폼을 시작할 수 있는 가장 쉽고 빠른 방법이다.
  • 홈브류(HomeBrew)나 맥포트(MacPorts)를 이용한 설치 : 맥(Mac) OS X 사용자를 위한 간단한 설치 방법이다.
  • tar 파일을 이용한 설치 : 압축 파일을 이용한 대안적인 설치 방법이다.
  • sudo 없이 설치 : node와 npm 커맨드를 사용할 때 sudo(admin 권한) 없이도 사용할 수 있는 가장 좋은 방법이다.
  • Git 저장소로부터 설치 : 최신 버전 필요 및 프로젝트에 기여한 고급 개발자를 위한 옵션이다.
  • Nave를 이용한 멀티버전 설정 : 여러 버전의 Node.js를 사용하는 프로젝트에 기여하는 개발자를 위한 필수 항목이다.
  • NVM(Node Version Manager)을 이용한 멀티버전 설정 : Nave의 대안(이전 항목 확인)이 된다.

본인의 경우 원클릭 인스톨러를 이용한 설치를 하였기 때문에 이에 대한 간단한 설명을 남기겠다.

1. 1 원클릭 인스톨러

먼저 http://nodejs.org에서 인스톨(Install) 버튼을 클릭해서 OS에 적합한 원클릭 인스톨러를 다운로드한다. 바이너리나 소스 코드는 어떻게 사용해야 하는지 알고 있거나, 개발한 환경의 OS가 없는 경우(즉, 윈도우즈나 맥이 아닌 경우)가 아니라면 선택하지 않는다.

인스톨러에 NPM(Node Package Manager)도 포함되어 있다. NPM은 의존성 문제를 처리할 수 있는 중요한 툴이다.

* 개발 환경 OS에 적합한 인스톨러가 없다면(http://nodejs.org/download/), 소스 코드를 다운로드하여 직접 컴파일하면 된다.

1. 2 설치 확인하기

설치 결과를 확인하려면, 터미널 프로그램(윈도우즈의 경우 cmd.exe)에서 다음 커맨드를 실행시킨다.

$ node -v
$ npm -v

이에 대한 각 결과는 다음과 같다(2016-10-14, v4.6.0 LTS 기준).

→ v4.6.0  /  → v2.15.9

Node.js와 NPM에 대한 설치가 완료된 것을 확인하였다면 이제부터는 이 플랫폼을 이용해서 더 자세히 학습해보도록 하자.

* Node.js를 실행시키는 가장 간단한 방법은 가상 환경을 이용하는 것인데, 이는 종종 REPL(Read-Eval-Print-Loop)이라고 불린다.

2. Node.js 스크립트 실행하기

파일을 이용해서 Node.js 스크립트를 시작하려면 $ node 파일명을 실행시키면 된다. 예를 들자면 다음과 같다.

$ node program.js

설명을 빠르게 추가하고 싶다면 인라인 Node.js와 자바스크립트를 실행시킬 수 있는 -e 옵션을 사용하면 된다. 예를 들면 다음과 같다.

$ node -e "console.log(new Date());"

Node.js 프로그램이 환경 변수를 사용할 경우, node 커맨드를 수행하기 전에 환경 변수를 설정할 수 있다. 예를 들면 다음과 같다.

$ NODE-ENV=production API_KEY=442CC1FE-4333-46CE-80EE-6705A1896832 node server.js

3. Node.js 기초와 문법

Node.js는 구글 크롬 V8 엔진과 ECMAScript를 기반으로 하기 때문에 객체와 함수, 메소드를 포함한 대부분의 문법은 프론트엔드 자바스크립트(ECMAScript의 또 다른 구현 방식)와 유사하다. 여기서는 Node.js와 자바스크립트의 기본 핵심이자 가장 중요한 부분들을 살펴보겠다.

  • 루즈 타입(Loose Type) 정의
  • 버퍼 - Node.js의 특별한 데이터 타입
  • 객체 리터럴 표기법
  • 함수
  • 배열
  • 프로토타입 특성
  • 규약
  • Node.js 전역 변수와 예약 키워드
  • __dirname vs. process.cwd
  • 브라우저 API 헬퍼
  • Node.js 핵심 모듈
  • 유용한 Node.js 유틸리티
  • Node.js의 파일 시스템 읽기와 쓰기
  • Node.js의 데이터 스트리밍
  • NPM으로 Node.js 모듈 설치하기
  • Node.js의 콜백
  • HTTP Node.js 모듈을 이용한 Hello World 서버

3. 1 루즈 타입 정의

자동 형변환은 대부분 제대로 동작한다. 시간과 정신적 에너지를 아낄 수 있는 훌륭한 특성이며, 원시(primitive) 타입의 수가 적으며 이에 속한 타입은 다음과 같다.

  • 문자열
  • 숫자(정수와 실수)
  • 불리언
  • Undefined
  • Null
  • RegExp

그 외 데이터들은 모두 객체 타입이다(즉, 변경 가능한 형식에 맞춰진 컬렉션을 말한다). 또한 자바스크립트에는 다음과 같이 원시 타입을 위한 헬퍼(helper)를 포함하고 있는 String, Number, Boolean 객체도 존재한다.

* ==는 자동 형변환이 일어나지만 ===는 그렇지 않다.

3. 2 버퍼 - Node.js의 특별한 데이터 타입

버퍼(Buffer)는 네 개의 원시 데이터 타입(불리언, 문자열, 숫자, RegExp)과 프론트엔드 자바스크립트의 모든 객체 데이터 타입(배열과 함수들 또한 객체다)에서 추가된 Node.js 데이터 타입으로, 데이터를 굉장히 효율적으로 저장한다. 사실, Node.js는 파일 시스템으로부터 읽거나 네트워크로부터 패킷을 수신하는 경우와 같이 버퍼를 사용할 수 있는 상황이 발생할 때마다 사용하려 한다.

3. 3 객체 리터럴 표기법

객체 표기법은 굉장히 읽기 쉽고 간단하다.

var obj = {
   color: "green",
   type: "suv",
   owner: {
      . . .
   }
}

함수들도 객체라는 사실을 기억하자.

var obj = function () {
   this.color: "green",
   this.type: "suv",
   this.owner: {
      . . .
   }
}

3. 4 함수

Node.js(자바스크립트와 마찬가지로)에서 함수들은 객체이므로 가장 중요하게 다룰 변수로 여긴다. 그 뿐만 아니라 함수들은 프로퍼티(property)와 속성(attribute)도 갖는다. 우선, 함수를 정의하는 방법을 살펴보자.

3. 4. 1 함수 정의/생성

함수를 정의 또는 생성하기 위해 가장 흔히 사용되는 세 가지 방법으로는 다음과 같다.

3. 4. 1. 1 선언적 함수

function f () {
   console.log('Hi');
   return true;
}

3. 4. 1. 2 변수에 할당하는 익명 함수

var f = function () {
   console.log('Hi');
   return true;
}

* 선언적 함수와 달리, 변수가 생성되기 전까지는 함수를 사용할 수 

3. 4. 1. 3 앞의 두 방법을 함께 사용한 함수

var f = function f () {
   console.log('Hi');
   return true;
}

3. 4. 1. 4 프로퍼티를 가진 함수

var f = function f () {console.log('Boo');}
f.boo = 1;
f(); //Boo 출력
console.log(f.boo); //1 출력

* 함수는 호출 또는 초기화가 가능한 객체일 뿐이라는 사실을 기억하자.

* return 키워드는 생략할 수 있으며, 생략된 경우 함수는 호출될 때 undefined를 반환한다.

3. 4. 2 매개변수로 함수 전달하기

자바스크립트는 함수를 객체처럼 취급하기 때문에 함수를 다른 함수의 매개변수로 전달할 수 있다(보통 Node.js의 콜백 함수를 전달한다).

var convertNum = function (num) {
   return num + 10;
}
var processNum = function (num, fn) {
   return fn(num);
}
processNum(10, convertNum);

3. 4. 3 함수 호출 vs. 함수 표현식

함수 정의는 다음과 같다.

function f () {};

반면, 함수 호출은 다음과 같다.

f();

함수 표현식의 경우 어떤 값(숫자, 문자열, 객체 또는 불리언)이 될 수 있기 때문에 다음과 같다.

function f() {return false;}
f();

함수 선언문은 다음과 같다.

function f(a) {console.log(a);}

3. 5 배열

배열 또한 Array.prototype 전역 객체로부터 상속 받은 일부 특별한 메소드를 가진 객체다. 하지만 자바스크립트 배열은 실제 배열이 아니며, 고유의 정수(일반적으로 0을 시작으로 하는) 키 값을 가진 객체다.

var arr = [];
var arr2 = [1, "Hi", {a:2}, function () {console.log('boo');}];
var arr3 = new Array();
var arr4 = new Array(1, "Hi", {a:2}, function () {console.log('boo');});

3. 6 프로토타입 특성

자바스크립트는 객체들이 다른 객체로부터 바로 상속받기 때문에(프로토타입 기반 상속이라 불림) 클래스가 존재하지 않는다. 대신 몇 가지 상속 패턴 타입이 존재한다.

  • 클래스적 상속
  • 가짜 클래스(pseudoclassical)적 상속
  • 함수적 상속

함수적 상속 패턴의 예제는 다음과 같다.

var user = function (ops) {
   return { firstName: ops.name || 'John'
             , lastName: ops.name || 'Doe'
             , email: ops.email || 'test@test.com'
             , name: function() { return this.firstName + this.lastName}
            }
}
var agency = function(ops) {
   ops = ops || {}
   var agency = user(ops)
   agency.customers = ops.customers || 0
   agency.isAgency = true
   return agency
}

3. 7 규약

언어 규약을 따르는 것은 굉장히 중요하다. 다음의 몇 가지 규약을 참고하자.

  • 세미콜론
  • 카멜케이스
  • 명명 규약
  • 콤마
  • 들여쓰기
  • 공백

Node.js와 자바스크립트 규약들은 보기가 편하며, 사람들이 많이 선호하는 편이다. 규약들이 프로그램 실행에 영향을 끼치지는 않지만, 하나의 양식을 일관적으로 따를 것을 강력히 권한다. 특히 팀 내 개발자로 일하고 있거나, 오픈 소스 프로젝트의 개발자라면 더더욱 그러하다. 일부 오픈 소스 프로젝트는 세미콜론이 있거나(ex:NPM), 콤마가 맨 앞에 두는 스타일을 사용하지 않을 경우(ex:요청), pull 요청을 수락하지 않는다.

3. 7. 1 세미콜론

세미콜론(;) 사용은 다음 두 가지 경우를 제외하고는 필수가 아니다.

  • for문: for (var i=0; i++; i<n)
  • 새로운 라인이 괄호로 시작할 때, 예를 들어 즉시 실행 함수(IIFF, Immediately-invoked function expression)를 사용하는 경우: ;(function(){...}())

3. 7. 2 카멜케이스

카멜케이스(CamelCase)는 자바스크립트에서 주로 사용하는 명명 규칙이다(클래스명 경우에는 CapitalCamelCase를 사용한다). 사용 예는 다음과 같다.

var MainView = Backbone.View.extend({...})
var mainView = new MainView()

3. 7. 3 명명 규칙

_와 $는 리터럴에 매우 적합한 문자다(jQuery와 Underscore 라이브러리에서 많이 사용). private 메소드와 속성은 _로 시작한다.

3. 7. 4 콤마

콤마(,)를 새 문장의 앞에 작성하는 방식의 예는 다음과 같다.

var obj = { firstName: "John"
              , lastName: "Smith"
              , email: "johnsmith@gmail.com"
             }

3. 7. 5 들여쓰기

보통 탭이나 네 칸 또는 두 칸의 공백을 이용해서 들여쓰기(Indentation)를 한다.

3. 7. 6 공백

보통 =, +, {, } 기호 전후에는 공백(Whitespace)이 있다. 호출할 때는 공백을 사용하지 않지만(ex:arr.push(1);), 익명 함수를 정의할 때는 공백을 사용한다.

function () {}

3. 8 Node.js 전역 변수와 예약 키워드

동일한 모델을 기준으로 설계되었음에도 불구하고, Node.js와 브라우저 자바스크립트의 전역 변수는 서로 다르다. var이 생략되었을 때, 브라우저 자바스크립트는 전역 공간에 변수를 보내면서 전역 공간을 더럽히므로 Node.js에서는 일부로 다르게 설계하였다.

브라우저 자바스크립트에서는 windows 객체가 존재한다. 하지만 Node.js의 경우 window 객체가 없는 대신(브라우저 창을 다루지 않는다는 사실은 명백하다). 새로운 객체 또는 키워드가 존재한다.

  • process
  • global
  • module, exports와 exports

그렇다면 지금부터 Node.js와 자바스크립트의 주요 차이점들을 살펴보겠다.

3. 8. 1 Node.js 프로세스 정보

실행되는 각각의 Node.js 스크립트는 본질적으로 프로세스라고 한다. 예를 들면 ps aux | grep 'node'는 머신에서 실행되고 있는 모든 Node.js 프로그램을 출력한다. 개발자는 process 객체를 이용해서(ex: node -e "console.log(process.pid)") 유용한 프로세스 정보를 편리하게 확인할 수 있다.

3. 8. 2 Node.js 전역 범위 접근하기

브라우저 자바스크립트는 기본적으로 전역 범위에 모든 것을 저장한다. 반면, Node.js는 기본적으로 로컬 범위에 저장한다. 전역 범위에 접근해야 하는 경우 global 객체를 사용하면, 내보내야 하는 경우에는 명시적으로 수행한다.

어떤 의미에서는 프론트엔드나 브라우저 자바스크립트의 window 객체는 global과 process 객체의 결합된 형태로 바뀌었다. Node.js에서는 말할 필요도 없이 웹 페이지의 DOM(Document Object Model)을 대표하는 document 객체는 존재하지 안흔ㄴ다.

3. 8. 3 모듈 내보내기와 가져오기

브라우저 자바스크립트의 또 다른 나쁜 점은 모듈을 포함시킬 수 있는 방법이 없다는 것이다. 스크립트는 다른 언어(HTML)를 사용해서 링크되어야 하는데, 의존성 문제를 해결할 수 있는 방법이 부족하다. CommonJS(http://www.commonjs.org/)와 RequireJS(http://requirejs.org/)는 AJAX-y를 이용해서 이 문제를 해결한다. Node.js는 CommonJS의 콘셉트를 많이 빌려왔다.

Node.js에서 객체를 내보내기 위해 exports.name = object;.를 사용한다. 예는 다음과 같다.

var messages = {
   find: function(req, res, next) {
   . . .
   },
   add: function(req, res, next) {
   . . .
   },
   format: 'title | date | author'
}
exports.messages = messages;

앞서 언급한 스크립트를 가져온 곳의 파일에(경로와 파일명을 routes/messages.js라고 가정한다) 다음 내용을 작성한다.

var messages = require('./routes/messages.js');

그러나 가끔 생성자를 호출하는 것이 좀 더 나은 경우도 있다. 예를 들자면 Express.js 애플리케이션에 프로퍼티를 추가할 때와 같은 경우다. 이 경우에는 module.exports가 필요하다.

module.exports = function(app) {
   app.set('port', process.env.PORT || 3000);
   app.set('views', __dirname + '/views');
   app.set('view engine', 'jade');
   return app;
}

이 샘플 모듈을 포함하는 파일을 다음 내용에 작성한다.

. . .
var app = express();
var config = require('./config/index.js');
app = config(app);
. . .

좀 더 간결한 코드는 var = express(); require('./config/index.js')(app); 이다.

모듈을 포함할 때 가장 흔히 하는 실수는 잘못된 파일 경로를 생성하는 것이다. 핵심 Node.js 모듈의 경우, require('name')과 같이 경로 없이 파일명만 사용한다. node_modules 폴더 내 모듈도 마찬가지다.

이 외의 다른 모든 파일들의 경우(즉, 모듈이 아닌 파일), 파일 확장자와 함께 또는 파일 확장자 없이 .(도트)를 사용한다. 예는 다음과 같다.

var keys = require('./keys.js'), messages = require('./routes/message.js');

뿐만 아니라 파일을 포함할 때 require(path.join(__dirname, ,'routes', 'messages'));와 같이 __dirname과 path.join()을 이용해서 더 긴 문장을 사용하는 것이 가능하다. path.join()으로 유효한 슬래시(OS에 따라 슬래시(/) 또는 백슬래시(\))가 포함된 경로를 생성할 수 있으므로 사용을 적극 권장한다.

require()가 폴더를 가리키는 경우, Node.js는 해당 폴더 내 index.js 파일 읽기를 시도한다.

3. 9 __dirname vs. process.cwd

__dirname은 전역 변수가 호출된 파일의 절대 경로인 반면, process.cwd는 스크립트를 실행하는 프로세스의 절대 경로다. $ node ./code/program.js 커맨드를 실행시키는 것처럼 프로그램을 다른 폴더에서 실행시킨다면, __dirname과 process.cwd는 다를 수 있다.

3. 10 브라우저 API 헬퍼

Node.js에는 브라우저 자바스크립트 API(Application Programming Interface)의 무수히 많은 헬퍼 함수들이 존재한다. 가장 유용한 함수는 String, Array, 그리고 Math 객체의 함수들이다. 다음은 가장 흔히 사용되는 함수들의 목록과 역할들이다.

  • Array (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)
  • Math (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math)
  • String (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)

3. 10. 1 Array

  • some()과 every(): 배열 요소 삽입
  • join()과 concat(): 문자열로 변환
  • pop(), push(), shift()와 unshift(): 스택과 큐로 작업
  • map(): 배열 요소 맵핑
  • filter(): 배열 요소에 대한 쿼리(query) 수행
  • sort(): 요소 정렬
  • reduce(), reduceRight(): 연산
  • slice(): 배열 추출
  • splice(): 삭제
  • indexOf(): 배열 내 해당 값이 위치한 인덱스
  • reverse(): 역순으로 나열
  • in 연산자: 배열 요소에서의 반복문

3. 10. 2 Math

  • random(): 1보다 작은 실수인 난수

3. 10. 3 String

  • substr() substring(): 부분 문자열 추출
  • length: 문자열의 길이
  • indexOf(): 문자열에서 값을 찾기 위한 인덱스
  • split(): 문자열을 배열로 변환

* 뿐만 아니라 Node.js에는 setInterval(), setTimeout(), forEach(), 그리고 콘솔 메소드가 있다. 참고하길 바란다.

3. 11 Node.js 핵심 모듈

Node.js는 다른 프로그래밍 기술과 달리 많은 표준 라이브러리를 제공하지 않는다. Node.js의 핵심 모듈은 가장 기본적인 것만 있으며, 나머지 모듈들은 NPM  레지스트리를 통해 선택할 수 있다. 주요 핵심 모듈, 클래스, 메소드, 그리고 이벤트는 다음과 같다.

  • http
  • util
  • querystring
  • url
  • fs

3. 11. 1 http

  • http(http://nodejs.org/api/http.html#http_http)는 Node.js HTTP 서버를 위한 기본 모듈이다. 주요 메소드는 다음과 같다.
  • http.createServer(): 새로 생성된 웹 서버 객체를 반환한다.
  • http.listen(): 명시된 포트와 호스트명의 접속을 허용한다(서버를 실행한다).
  • http.createClient(): 클라이언트로 다른 서버에 요청한다.
  • http.ServerRequest(): 수신된 요청을 요청 핸들러에 전달한다.
    • data: 메시지 바디 부분을 수신할 때 보낸다.
    • end: 각각의 요청마다 정확히 한 번만 보낸다.
    • request.method(): 문자열로 된 요청 메소드
    • request.rul(): 요청 URL 문자열
  • http.ServerResponse(): 유저가 아닌 HTTP 서버가 내부적으로 해당 객체를 생성하며, 요청 핸들러의 출력으로 사용된다.
    • response.writeHead(): 요청에 응답 헤더를 보낸다.
    • response.write(): 응답 바디를 보낸다.
    • response.end(): 응답 바디를 보내고 종료한다.

3. 11. 2 util

util(http://nodejs.org/api/util.html) 모듈은 디버깅을 위한 유틸리티로 제공한다. 메소드는 다음과 같다.

  • util.inspect(): 객체의 정보를 문자열로 반환하며, 디버깅하는 데 유용하다.

3. 11. 3 querystring

querystring(http://nodejs.org/api/querystring.html) 모듈은 쿼리 문자열(query string)을 처리하기 위한 유틸리티를 제공한다. 메소드는 다음과 같다.

  • querystring.stringify(): 객체를 쿼리 문자열 형태로 직렬화(serialize)한다.
  • querystring.parse(): 쿼리 문자열을 객체 형태로 객체화한다.

3. 11. 4 url

url(http://nodejs.org/api/url.html)은 모듈은 URL 분석과 파싱을 위한 유틸리티를 제공한다. 메소드는 다음과 같다.

  • parse(): URL 문자열을 객체 형태로 반환한다.

3. 11. 5 fs

fs(http://nodejs.org/api/fs.html)는 파일 읽기와 쓰기와 같은 파일 시스템 작업을 처리한다. 라이브러리에는 동기화 메소드와 비동기화 메소드가 있다. 메소드는 다음과 같다.

  • fs.readFile(): 비동기식으로 파일을 읽는다.
  • fs.writeFile(): 비동기식으로 파일에 데이터를 쓴다.

핵심 모듈은 설치하거나 다운로드할 필요가 없다. 애플리케이션에 모듈을 포함시키려면 다음 문법을 사용하면 된다.

var http = require('http');

그 외 모듈들은 다음 위치에 존재한다.

  • npmjs.org(https://npmjs.org): NPM 레지스트리
  • GitHub(https://github.com/joyent/node/wiki/modules): Joyent가 관리하는 Node.js 모듈
  • nodetoolbox.com(http://nodetoolbox.com/): 통계 기반의 레지스트리
  • Nipster(http://eirikb.github.com/nipster/): Node.js를 위한 NPM 검색 도구
  • Node 트래킹(http://nodejsmodules.org): GitHub 통계 기반의 레지스트리

모듈을 직접 코딩하는 방법을 알고 싶다면, "Your First Node.js Module"을 참고하기 바란다.

3. 12 유용한 Node.js 유틸리티

Node.js 플랫폼의 핵심 모듈을 의도적으로 작게 하였지만, 몇몇 중요한 유틸리티도 제공한다. 다음을 참고하기 바란다.

  • Crypto(http://nodejs.org/api/crypto.html): 랜더마이저(randomizer), MD5, HMAC-SHA1, 그리고 다른 알고리즘을 제공한다.
  • Path(http://nodejs.org/api/path.html): 시스템 경로를 처리한다.
  • String decoder(http://nodejs.org/api/string_decoder.html): 버퍼를 문자열 타입으로 변환하거나 문자열을 버퍼로 변환한다.

계속 사용하는 메소드는 path.join으로 적절한 폴더 분리 문자(/ 또는 \\)를 사용해서 경로를 연결시킨다.

3. 13 Node.js의 파일 시스템 읽기와 쓰기

파일 읽기는 기본 핵심 모듈인 fs를 통해 이루어진다. 읽기 작업에는 비동기와 동기라는 두 가지 방법이 있는데, 대부분의 경우 개발자는 fs.readFile과 같이 비동기 읽기 메소드를 사용한다.

var fs = require('fs');
var path = require('path');
fs.readFile(path.join(__dirname, '/data/customers.csv'), {encoding: 'utf-8'},
function (err, data) {
   if (err) throw err;
   console.log(data);
});

파일 쓰기를 하려면 다음을 실행한다.

var fs = require('fs');
fs.writeFile('message.txt', 'Hello World!', function (err) {
   if (err) throw err;
   console.log('Writing is done.');
});

3. 14 Node.js의 데이터 스트리밍

데이터 스트리밍이란, 애플리케이션이 여전히 데이터를 수신하고 있는 동안에도 그 데이터를 처리하는 것을 의미한다. 이 기능은 비디오나 데이터베이스 마이그레이션과 같이 크기가 굉장히 큰 데이터세트를 처리하는 데 유용한다.

바이너리 파일 내용을 출력하는 스트림을 이용한 기본 예제는 다음과 같다.

var fs = require('fs');
fs.createReadStream('./data/customers.csv').pipe(process.stdout);

기본적으로는 Node.js는 스트림을 위해 버퍼를 사용한다. 더 자세한 내용은 stream-adventure와 Stream Handbook을 참고하기 바란다.

3. 15 NPM으로 Node.js 모듈 설치하기

NPM은 Node.js 플랫폼과 함게 제공되며, Node.js 패키지 관리를 자유자재로 할 수 있도록 만들어 준다. npm 설치 방법은 현재 프로젝트를 찾기 위해 작업하고 있는 트리를 변경한다는 점에서 Git과 유사하다. 시작하기에 앞서, $ npm install name을 이용해서 로컬에 모듈을 설치하려면 package.json 파일이나 node_modules 폴더가 필요하다는 사실을 기억하기 바란다. 예를 들어, $ npm install superagent의 경우, program.js에 var superagent = require('superagent');을 작성한다.

NPM의 가장 좋은 점은 모든 의존 모듈을 로컬에 두고 있기 때문에 모듈 A가 모듈 B v1.3을 사용하고, 모듈 C가 모듈 B v2.0(v1.3에서 크게 변경된)을 사용할 경우 로컬에 A와 C 둘 다 서로 다른 버전의 모듈 B 복사본을 갖게 된다. 이러한 점은 기본적으로 전역 설치를 하는 루비와 다른 플랫폼에 비해 굉장히 우수하다.

가장 좋은 방법은 프로젝트가 다른 애플리케이션에서 사용되어야 하는 모듈이라면, Git 저장소에 node_modules 폴더를 포함시키지 않는 것이다. 하지만 배포 가능성이 있는 애플리케이션의 경우, 의존성으로 발생 가능한 문제를 방지하기 위해 node_modules를 포함시킬 것을 권한다.

* 참고로 NPM 개발자는 npm(소문자)으로 쓰는 것을 선호한다.

3. 16 Node.js의 콜백

콜백(https://github.com/maxogden/art-of-node)은 Node.js 코드를 비동기적으로 만들 수 있지만, 자바나 PHP로 작업하는 자바스크립트가 익숙치 않은 프로그래머들은 Callback Hell(http://callbackhell.com/)에서 설명하고 있는 Node.js 코드를 보면 당황할 수도 있다.

fs.readdir(source, function(err, files) {
   if (err) {
      console.log('Error finding files: ' + err)
   } else {
      files.forEach(function(filename, fileIndex) {
         console.log(filename)
         gm(source + filename).size(function(err, values) {
            if (err) {
               console.log('Error identifying file size: ' + err)
            } else {
               console.log(filename + ' : ' + values)
               aspect = (values.width / values.height)
               widths.forEach(function(width, widthIndex) {
                  height = Math.round(width / aspect)
                  console.log('resizing ' + filename + 'to ' + height + 'x' + height)
                  this.resize(width, height).write(destination + 'w' + width + '_' + filename, function(err) {
                     if (err) console.log('Error writing file: ' + err)
                  })
               }.bind(this))
            }
         })
      })
   }
})

두 칸 들여쓰기를 사용했다면 코드를 보기 어렵지는 않을 것이다. 하지만 콜백 코드는 이벤트 emitter나 promise를 사용하거나, 또는 비동기 라이브러리를 사용하여 다시 작성될 수 있다.

3. 17 HTTP Node.js 모듈을 이용한 Hello World 서버

Node.js가 다양한 태스크에 사용될 수 있지만, 주로 웹 애플리케이션을 개발하는 데 사용된다. Node.js는 비동기 특성이나 net, http와 같은 내장 모듈 덕분에 빠르게 성장하고 있다.

서버 객체 생성 및 요청 핸들러(req와 res 인자를 갖는 함수) 정의와 수신부에 데이터를 전달하고, 이 모든 것을 실행시킬 다음 Hello World(hello.js) 예제를 살펴보자.

var http = require('http');
http.createServer(function (req, res) {
   res.writeHead(200, {'Content-Type': 'text/planin'});
   res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');

한 줄씩 살펴보도록 하겠다. 다음 라인은 핵심 모듈인 http를 서버를 위해 로드한다.

var http = require('http');

아래 코드는 응답 핸들러 코드를 포함한 콜백 함수를 가진 서버를 생성한다.

var server = http.createServer(function (req, res) {

적합한 헤더와 상태 코드를 쓰려면 다음과 같이 입력한다.

res.writeHead(200, {'Content-Type': 'text/plain'});

개행 문자와 함께 Hello World를 출력하려면 다음과 같이 입력한다.

   res.end('Hello world\n');
});

req와 res 인자는 주어진 HTTP의 요청과 응답에 대한 정보를 모두 갖고 있다. 뿐만 아니라, req와 res는 스트림으로 사용될 수도 있다.

서버가 요청을 수락하게 하려면 다음과 같이 입력한다.

. . . listen(337, '127.0.0.1');

터미널 server.js가 위치한 폴더에서 다음 커맨드를 실행시킨다.

$ node hello.js

localhost:1337 또는 127.0.0.1:1337, 아니면 함수 console.log()를 실행시켜서 출력되는 주소로 접속하면 브라우저에서 Hello World를 확인할 수 있다. 서버를 종료하려면 Ctrl + c를 누른다.

4. Node.js 프로그램 디버깅하기

자바스크립트와 AJAX 애플리케이션이 번성하기 전에(~2005-2007) 디버깅을 할 수 있는 유일한 방법은 alert()문을 코드에 잔뜩 집어넣는 것뿐이었다. 지금은 크롬 개발자 도구와 파이어폭스 파이어버그(Firefox Firebug)와 같은 훌륭한 개발 환경이 갖춰져 있다. 또한, Node.js가 브라우저 자바스크립트 환경과 상당히 유사한 부분이 많기 때문에 다음과 같이 Node.js에서 디버깅할 수 있는 많은 방법들이 존재한다.

  • Core Node.js Debugger: 어디서나 동작 가능한 GUI가 없는 최소한의 기능을 갖춘 도구
  • Node Inspector: 구글 크롬 개발자 도구의 디버거 인터페이스
  • 웹스톰과 다른 IDE들

5. Node.js IDE와 코드 편집기

Node.js의 가장 좋은 점 중 하나는 메모리에 로드되어 플랫폼에 의해 기계어로 번역되기 때문에 코드를 컴파일할 필요가 없다는 것이다. 그러므로 IDE(통합 개발 환경) 대신 서브라임 텍스트(Sublime Text)와 같은 경량화된 텍스트 편집기를 추천한다. 하지만 이클립스(Eclipse), 넷빈즈(NetBeans), 또는 앱타나(Aptana)와 같이 사용 중인 IDE가 이미 친숙하고 편하다면 계속 사용해도 된다.

다음은 웹 개발에 가장 많이 사용되는 텍스트 편집기와 IDE 목록이다.

  • 텍스트메이트(http://macromates.com/): 맥 OS X 버전만 지원한다. v1.5의 경우 30일 무료 평가판을 지원하며, Missing Editor for Mac OS X이라고도 불린다
  • 서브라임 텍스트(http://www.sublimetext.com/): 맥 OS X과 윈도우즈 버전 지원, 텍스트메이트보다 나은 대안 툴과 기간 제한이 없는 평가판을 지원한다.
  • Coda(http://panic.com/coda/): FTP 브라우저와 프리뷰를 제공하는 올인원 편집기로, 아이패드를 이용한 개발을 지원한다.
  • 앱타나 스튜디오(http://aptana.com/): 내장된 터미널과 다양한 도구를 제공하는 IDE
  • Notepad++(http://notepad-plus-plus.org/): 윈도우즈만 지원하는 경량의 무료 텍스트 편집기다. 다양한 언어를 지원한다.
  • 웹스톰(WebStorm) IDE(http://www.jetbrains.com/webstorm/): Node.js 디버깅이 가능한 풍부한 기능을 제공하고 IDE로 젯브레인스(JetBrains)에 의해 개발되었으며, "the smartest JavaScript IDE(가장 똑똑한 자바스크립트 IDE)"라고 마케팅 중이다.

6. 파일 변경 감시하기

Node.js 애플리케이션은 메모리에 저장되며, 소스 코드를 변경한다면 프로세스(즉, node)를 다시 시작해야 한다. 보통은 소스 코드 변경을 위해 직접 프로세스를 죽이고 새로운 프로세스를 다시 시작한다. 하지만 지속적으로 반복되는 재시작 시퀀스가 자동화된다면, 개발 작업을 좀 더 빨리 진행할 수 있다. 편집기의 변경 내용을 저장할 때 Node.js의 핵심 모듈인 fs의 watch 메소드를 이용해서 서버를 재시작하는 훌륭한 도구들이 있다.

  • forever(http://npmjs.org/forever)는 일반적으로 상용화할 때 주로 사용한다.

  • node-dev(https://npmjs.org/package/node-dev)

  • nodemon(https://npmjs.org/package/nodemon)

  • supervisor(https://npmjs.org/package/supervisor)

위의 도구들을 사용하는 것은 굉장히 쉽다. $ npm install -g node-dev를 이용해서 전역적으로 설치하고, $ node-dev program.js를 이용해서 Node.js 스크립트를 실행하기만 하면 된다. 다른 모듈을 사용하고 싶다면 node-dev 대신 다른 모듈명을 설정하면 된다.

* Express.js는 기본적으로 모든 새로운 요청에 대해 템플릿 파일을 다시 로드한다는 사실을 알고있자. 때문에 서버를 재시작하지 않는다. 하지만 뷰 캐시(view cache) 설정을 활성화함으로써 캐시에 템플릿을 저장할 수 있다.



반응형