Node.js 기본 사용법

이재홍 http://www.pyrasis.com 2014.5.24 ~

Node.js는 자바스크립트(JavaScript)로 서버 프로그래밍을 할 수 있도록 해주는 플랫폼입니다. 이 문서는 Node.js를 처음 접하는 초보자들을 위한 설치부터 간단한 예제까지 실습해보는 기본 사용법 강좌입니다.

목차

자바스크립트

Node.js를 사용하려면 먼저 자바스크립트에 대해 이해를 해야 합니다. 자바스크립트는 넷스케이프 웹 브라우저에서 HTML의 DOM(Document Object Model) 객체를 제어하기 위해 개발되었습니다.

웹 브라우저 초창기에는 자바스크립트 처리가 매우 느렸습니다. 그래서 간단한 DOM 객체 제어나 경고창 띄우기 등 사용자의 입력을 처리하는 부분에서 활용되었습니다.

구글이라는 인터넷 강자가 탄생하면서 상황이 바뀌었습니다. 구글은 인터넷 검색 시장을 석권하게 되었고 웹 브라우저 시장까지 진출하게 됩니다. 기존에는 넷스케이프부터 시작해서 윈도우에 포함된 Internet Explorer가 90프로 이상의 점유율을 가지게 되었지만 구글이 크롬을 출시하면서 엄청난 속도로 점유율을 높여 나가기 시작했습니다.

2014년 4월 기준으로 전세계 웹 브라우저 점유율은 크롬 45.22%, IE 21.43%, Firefox 18.62%, Safari 9.79%, Opera 1.39%입니다.
http://gs.statcounter.com

구글 크롬이 이렇게 무서운 속도로 퍼지게 된 것은 다름아닌 자바스크립트 처리 속도 때문이었습니다. 크롬은 IE에 비해서 매우 가볍고 빠릅니다.

V8 자바스크립트 엔진

구글이 크롬 웹 브라우저를 개발하면서 함께 개발것이 V8 자바스크립트 엔진(http://code.google.com/p/v8)입니다.

V8 자바스크립트 엔진은 자바스크립트 처리 속도가 기존에 웹 브라우저에 포함된 엔진들 보다 월등히 빨랐습니다. 가히 혁명적이었지요. 기존 자바스크립트 엔진들은 자바스크립트를 바이트코드로 변환하거나 인터프리트하여 처리했지만 V8은 JIT(Just In Time) 컴파일 방식을 사용하여 성능을 획기적으로 개선했습니다. 그리고 인라인 캐싱과 같은 기법으로 좀더 최적화하고 있습니다.

JIT 컴파일 방식은 자바스크립트를 인터프리트하지 않고 실행 즉시 기계어(x86, ARM 등)로 컴파일합니다.

Node.js

2009년 라이언 달이라는 프로그래머가 구글의 V8 자바스크립트 엔진을 웹 브라우저가 아닌 서버로 사용할 수 있도록 만든 것이 Node.js입니다. 즉, 우리가 흔히 봐왔던 PHP, ASP.NET, JSP 등의 서버 사이드 플랫폼과 같습니다.

자바스크립트의 간결함과 V8 자바스크립트 엔진의 월등한 속도 그리고 단일 스레드 Non-bloking I/O로 빠른 성능을 내면서 전 세계적으로 큰 인기를 끌게 되었습니다.

기존에는 소켓으로 서버 프로그래밍을 하려면 유닉스/리눅스의 socket(select, poll, epoll)이나 윈도우에서 IOCP를 이용해서 C++로 만들어야 했습니다. C++로 만들면 처리 속도는 빠르지만 코드가 길어지고 디버깅도 힘들어서 일부 서버 프로그래머들만의 영역이었습니다.

Node.js가 나오면서 소켓 서버 프로그래밍에 대한 장벽이 무너졌습니다. 누구든지 자바스크립트로 패킷을 주고 받을 수 있고, 서버를 간단하게 구현할 수 있게 된 것입니다. C++ 소켓 프로그래밍에 비해 정말 간단해졌습니다.

단언컨대, Node.js는 가장 생산성이 좋은 플랫폼입니다.

일반적인 소켓 통신 뿐만 아니라 Node.js 자체를 HTTP 웹 서버로 실행할 수 있고, WebSocket(socket.io) 등의 HTTP 기반 실시간 프로토콜도 손쉽게 사용할 수 있습니다. 실시간 통신을 자바스크립트 단 몇줄로 구현할 수 있게 된 것이죠.

결과적으로 Node.js는 자바스크립트를 웹 브라우저 속에서만 사용되던 언어에서 범용 스크립트 언어로 탈바꿈 시켰습니다. Python이나 Perl, Ruby와 같은 레벨이 된 것입니다.

npm

npm(Node Packaged Modules)은 Node.js로 만들어진 모듈을 인터넷에서 받아서 설치해주는 패키지 매니저입니다.

Node.js가 대세가 된 결정적인 부분은 이 npm에 있습니다. npm 자체가 월등히 훌륭해서가 아니고, Node.js 모듈의 개수가 상상을 초월할 만큼 많다는 겁니다. 대략 몇 만개 단위입니다. 우리가 생각한 모든게 다 Node.js 모듈로 만들어져 있습니다(GitHub 저장소의 상당수가 Node.js 모듈입니다). 새로 만들 필요 없이 npm으로 설치해서 쓰면 됩니다.

이 수많은 Node.js 모듈로 인해 엄청난 생산성 향상을 가져왔습니다.

단일 스레드 모델과 Non-blocking I/O

Node.js의 가장 큰 특징을 꼽으라면 단일 스레드 모델과 Non-blocking I/O입니다.

지금까지 소프트웨어 프로그래밍 세계에서는 성능향상을 위해 끊임없는 연구를 해왔습니다. 그중 널리 쓰이게된 방식이 멀티 스레드 방식입니다. 멀티 스레드 방식은 프로세스 안에서 스레드를 여러 개 만들어서 여러 개의 로직을 동시에 처리하는 방식입니다. 이 방식은 지금까지 널리 쓰이고 있지만 복잡한 동기화 문제가 늘 골칫거리였습니다. 프로그래머들은 수많은 동기화 모델 혹은 락(Lock)에 대해 학습해야 했고, 제대로 쓰기도 힘들었으며 생산성 저하로 이어졌습니다. 생산성 저하는 곧 비용 증가죠.

Node.js는 멀티 스레드 모델 대신 단일 스레드 모델과 Non-blocking I/O를 채택했습니다. 이것은 자바스크립트의 언어적 특성과도 연관이 있습니다. Non-blocking 방식이 있으면 Blocking 방식도 있겠죠. Blocking 방식이 지금까지 흔히 보던 프로그래밍 방식입니다.

C언어로 예를 들면 main 함수에서 각 줄의 실행 순서는 그냥 줄 순서대로 입니다.

#include <stdio.h>

int sum(int a, int b)
{
    return a + b;
}

int main()
{
   int result;
   result = sum(1, 2);          // 1
   printf("sum: %d", result);   // 2

   return 0;                    // 3      
}

Blocking 방식은 한 줄이 끝나야 다음 줄이 실행됩니다. printf 함수로 결과를 찍으려면 sum 함수가 끝날 때까지 기다려야 합니다. 즉, 뒷 줄의 실행이 앞 줄에 막히기 때문에 Blocking 방식이라고 합니다.

이제 자바스크립트로 Non-blocking 방식을 보겠습니다. 덧셈 뺄셈 예제로는 모양이 안나니까 파일 읽기로 해보겠습니다.

var fs = require('fs');

fs.readFile('./컴배콤.txt' /* 1 */, function (err, data) { 
  console.log(data); // 3
});

console.log('Hello JavaScript'); // 2

문법도 좀 특이하고 실행 순서도 이상합니다. 실행 순서는 주석에 표시한대로 입니다. fs.readFile() 함수의 결과값이 C언어처럼 리턴값으로 받지 않고 함수 형태로 받습니다(이것이 그 유명한 콜백(Callback)입니다). 다시 이 함수에서 data 매개변수가 파일을 읽은 결과값이 들어있습니다. 즉, fs.readFile()의 결과값이 나올때까지 기다리지 않고, 그 다음 줄 console.log('Hello JavaScript') 함수를 실행해버립니다. 그리고 시간이 흐른 후 파일을 다 읽었으면 3번 console.log(data)가 실행됩니다.

fs.readFile() 처럼 함수를 매개변수로 전달하는 부류Non-blocking 방식으로 동작하고, 함수 호출 후 리턴값을 받거나 그냥 함수만 호출하는 부류는 Blocking 방식으로 실행됩니다.

내부 구조를 살펴보면 이렇습니다. 시간이 오래 걸리는 작업은 워커 스레드로 보내버리고 메인 스레드는 코드를 계속 실행합니다. 그리고 워커 스레드에서 작업이 끝나면 결과를 다시 메인 스레드로 보냅니다. 이렇게 2개의 스레드로 동작하며 이 메인 스레드와 워커 스레드 방식은 libuv에 구현되어 있습니다.

설치하기

이제 본격적으로 Node.js를 설치해보겠습니다. 오픈소스가 다 그렇듯이 설치하는 방법이 무지 많습니다. 다 설명해드리겠습니다.

소스 컴파일

소스 코드를 받아서 컴파일 한 후 설치하는 방법입니다. http://nodejs.org/download에 가서 소스 코드(Source Code)를 받습니다. wget으로 받으면 편합니다.

~$ wget http://nodejs.org/dist/v0.10.28/node-v0.10.28.tar.gz
~$ tar vxzf node-v0.10.28.tar.gz
~$ cd node-v0.10.28
~/node-v0.10.28$ ./configure
~/node-v0.10.28$ make
~/node-v0.10.28$ sudo make install

요즘 참 편해졌습니다. 예전에는 OpenSSL 따로 받고 뭐 따로 받고 configure 옵션으로 다 설정해줬어야 했는데 지금은 그냥 다 들어있습니다.

파일들을 /usr/local 등에 설치하려면 root 권한이 필요하기 때문에 make installsudo를 붙여주었습니다. 비밀번호를 입력하라고 나오면 현재 계정의 비밀번호를 입력하면 됩니다.

리눅스

서버를 무료로 사용하려면 역시 리눅스지요.

우분투

제가 좋아하는 우분투(Ubuntu) 리눅스입니다. 데비안 기반의 리눅스이며 데비안의 apt-get 명령으로 deb 패키지를 설치할 수 있습니다.

$ sudo install nodejs
$ sudo install npm

페도라

레드햇 페도라 리눅스입니다.

$ sudo yum install nodejs
$ sudo yum install npm

RedHat Enterprise Linux, CentOS

레드햇 엔터프라이즈 리눅스(RHEL)와 CentOS에서 설치하는 방법입니다. RHEL과 CentOS 패키지 저장소에는 nodejs가 없으므로 EPEL(Fedora Extra Packages For Enterprise Linux) 저장소를 사용할 수 있도록 설정합니다.

$ sudo rpm --import https://fedoraproject.org/static/0608B895.txt
$ sudo rpm -Uvh http://download-i2.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
$ sudo yum install nodejs npm --enablerepo=epel
$ sudo yum install npm --enablerepo=epel

AWS EC2에 설치되는 Amazon Linux(RHEL 기반)는 EPEL 저장소를 바로 사용할 수 있으므로 rpm --import ..., epel-release-6-8.noarch.rpm을 설치하지 않아도 됩니다.

Mac OS X

매킨토시에서 설치하는 방법입니다. http://nodejs.org/download에 가서 Mac OS X Installer (.pkg)를 받아서 설치하거나 Mac OS X Binaries를 받아서 사용합니다.

Fink를 사용한다면

$ sudo fink install nodejs

homebrew를 사용한다면

$ sudo brew install node

mactports를 사용한다면

$ sudo port install nodejs

FreeBSD

FreeBSD ports로 설치하는 방법입니다.

$ cd /usr/ports/www/node
/usr/ports/www/node$ sudo make install clean

pkgng를 이용하여 설치하는 방법입니다.

$ sudo pkg install node

윈도우

윈도우에서 설치하는 방법입니다. http://nodejs.org/download에 가서 Windows Installer (.msi)를 받아서 설치합니다. Windows Binary (.exe)는 node.exe 실행파일만 있고 npm 명령이 들어있지 않아서 사용하기 불편합니다. 꼭 msi 파일로 설치하세요. 설치 방법은 특별한 것이 없으므로 생략하겠습니다.

사용하기

Node.js는 node(윈도우에서는 node.exe) 실행파일이 자바스크립트를 읽어서 실행하는 방식입니다. 즉 스크립트 언어인 Python이나 Perl, Ruby와 동일한 실행 방식입니다.

가장 쉬운 예제는 Hello World 겠지요. 다음 내용을 작성하고 app.js로 저장합니다.

app.js

console.log('Hello World');

터미널이나 명령 프롬프트에서 node app.js로 app.js를 실행할 수 있습니다.

$ node app.js
Hello World

간단합니다. 이게 끝입니다.

웹 서버 만들기

콘솔에 Hello World만 찍어서는 재미가 없습니다. 그래서 매우 간단한 웹 서버를 한번 구현해보겠습니다. 다음 내용을 작성하고 app.js로 저장합니다.

app.js

var http = require('http');

var server = http.createServer(function (req, res) {
  res.writeHead(200, { 'Content-Type' : 'text/plain' });
  res.end('Hello World');
});

server.listen(8000);

터미널이나 명령 프롬프트에서 node app.js로 app.js를 실행합니다.

$ node app.js

이번에는 node 실행파일이 바로 종료되지 않고 계속 대기 중입니다. 이건 server.listen() 함수 때문에 그렇습니다. 8000번 포트로 접속을 계속 대기하고 있어서 사용자가 종료하기 전까지는 끝나지 않습니다. 끝내려면 Ctrl+C를 입력하면 됩니다.

웹 브라우저를 실행하고 http://127.0.0.1:8000으로 접속합니다. 빈 화면에 Hello World가 표시됩니다.

참고

server.listen()에서 8000번 포트를 설정했는데, 일반 HTTP 포트인 80번을 사용해도 됩니다. 일부 운영체제(윈도우8 -_-;)에서 이미 80번 포트를 사용하고 있는 경우가 있어서 예제에서는 8000번으로 했습니다.

윈도우8에서 80번 포트 사용하기

1. http 서비스를 중단시킨다. - http://www.rejetto.com/forum/hfs-~-http-file-server/port-80-already-used-in-windows-8-solution/
2. Skype의 80번 포트 설정을 끈다. - http://lordamit.blogspot.kr/2012/06/windows-7-windows-8-apache-errorport-80.html
참고
유닉스/리눅스 계열에서 80번 포트를 사용하려면 root 권한이 필요합니다(1번 부터 1024번 까지).
$ sudo node app.js

웹 서버를 구현하는 것도 매우 간단합니다. 저 6줄이 끝입니다. 다른 언어 보다 웹 서버를 실행하는 기본 코드가 짧습니다. 그리고 http는 Node.js에 내장된 기본 모듈입니다.

다 좋지만 이 상태로는 제대로 된 웹 서버를 만들 수 없습니다. 그래서 Node.js의 최대 장점인 방대한 모듈을 이용해야 합니다.

express로 웹 서버 만들기

express는 Node.js에서 가장 유명한 웹 프레임워크 모듈입니다. express를 이용하면 더 간단하게 웹 서버를 만들 수 있고, 다양한 템플릿 엔진과 기능들을 사용할 수 있습니다.

이제 express를 사용하여 간단한 웹 서버를 만들어보겠습니다.

먼저 웹 서버 소스들이 위치할 디렉터리(폴더)를 생성합니다. 앞으로 새로운 것을 구현할 때 마다 디렉터리를 만드는 것이 편합니다. 저는 HelloWebServer라고 만들었습니다.

$ mkdir HelloWebServer

이제 이 디렉터리에 다음 내용을 작성하고 app.js로 저장합니다.

app.js

var express = require('express')
  , http = require('http')
  , app = express()
  , server = http.createServer(app);

app.get('/', function (req, res) {
  res.send('Hello /');
});

app.get('/world.html', function (req, res) {
  res.send('Hello World');
});

server.listen(8000, function() {
  console.log('Express server listening on port ' + server.address().port);
});

이 상태로 실행하면 express가 없어서 실행이 안됩니다. 그래서 express를 설치해야겠죠. npm install <모듈명>으로 모듈을 설치할 수 있습니다.

~/HelloWebServer$ npm install express

주의할 점은 app.js가 있는 디렉터리에서 npm install express 명령을 실행해야 된다는 것입니다. npm install 명령을 실행하면 app.js가 있는 디렉터리에 node_modules 디렉터리가 생성됩니다. 그리고 node_modules 디렉터리 안에 express 모듈이 설치됩니다.

참고
npm install -g express와 같이 -g 옵션을 주면 Node.js 설치 디렉터리에 모듈이 설치됩니다. 전역 모듈 설치라고 하는데 이렇게 설치하면 app.js 디렉터리와 상관없이 모듈을 사용할 수 있습니다.

express 설치가 완료되었으면 app.js를 실행합니다.

~/HelloWebServer$ node app.js

웹 브라우저에서 http://127.0.0.1:8000으로 접속합니다. 빈 화면에 Hello /가 표시됩니다. 그리고 http://127.0.0.1:8000/world.html로 접속하면 Hello World가 출력됩니다.

소스 내용을 살펴보겠습니다. require는 모듈을 로딩하는 함수입니다. express, http 모듈을 로딩하고 변수에 저장된 express를 함수로 실행해서 app 객체를 생성합니다. 그리고 http.createServer(app) 함수로 app 객체와 http 서버를 연결합니다.

var express = require('express')
  , http = require('http')
  , app = express()
  , server = http.createServer(app);

app 객체에서 get 함수로 HTTP의 GET 메서드를 처리할 수 있습니다. get 함수에 경로를 지정하고, 웹 브라우저가 해당 경로에 접속했을 때 실행될 함수를 지정합니다. 그리고 이 함수는 웹 브라우저가 접속할 때마다 실행됩니다. req는 요청(Request) 객체이고 res는 응답(Response) 객체입니다.

웹 브라우저가 접속하면 이 콜백 함수에서 res의 send 함수를 실행하여 웹 브라우저에 표시할 내용을 보냅니다. app 객체에 경로마다 응답할 함수를 지정하면 여러 개의 파일을 처리할 수 있습니다.

app.get('/', function (req, res) {
  res.send('Hello /');
});

app.get('/world.html', function (req, res) {
  res.send('Hello World');
});

app 객체에서 HTTP의 GET 메서드 뿐만 아니라 모든 메서드(POST, PUT, DELETE, OPTIONS, HEAD 등)를 처리할 수 있습니다. 다양한 HTTP 메서드를 간단하게 처리할 수 있어서 Node.js와 express로 RESTful 서비스를 손쉽게 만들 수 있습니다.

마지막으로 http 모듈로 생성한 http 서버를 8000번 포트로 실행합니다

server.listen(8000, function() {
  console.log('Express server listening on port ' + server.address().port);
});
참고
express는 http 모듈로 생성한 http 서버와 연결하지 않고 app 객체에서 바로 listen() 함수로 서버를 실행할 수도 있습니다.
var server = app.listen(8000, function () {
  console.log('Express server listening on port ' + server.address().port);
});
http 모듈과 연결하면 express와 socket.io와 같은 실시간 모듈을 같은 포트에서 실행할 수 있습니다.

템플릿 엔진 사용하기

express로 웹 서버를 만들더라도 많은 경로(파일)를 하나하나 정의하기에는 무리가 있습니다. express에서 템플릿 엔진을 사용하면 PHP나 ASP, JSP 처럼 서버에서 HTML을 동적으로 생성할 수 있습니다. 또한, HTML 태그를 전부 입력하지 않고, 간단한 문법으로 웹 페이지를 만들 수 있습니다.

EJS

EJS(Embedded JavaScript)는 서버에서 자바스크립트로 HTML을 생성하는 템플릿 엔진입니다. PHP를 자바스크립트로 프로그래밍 한다고 생각하면 이해가 쉽습니다.

express-generator를 사용하여 뼈대를 먼저 만듭니다. express-generator는 전역 모듈(-g 옵션)로 설치를 해야 합니다(윈도우에서는 sudo 명령을 사용하지 않습니다). 그리고 웹 서버 소스들이 위치할 디렉터리를 생성합니다.

~$ sudo npm install -g express-generator
~$ mkdir ExampleEJS
~$ cd ExampleEJS
~/ExampleEJS$ express --ejs

--ejs 옵션으로 기본 뼈대를 생성했습니다.

├── app.js
├── bin
│   └── www
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── index.js
│   └── users.js
└── views
    ├── error.ejs
    └── index.ejs

필요한 모듈을 설치합니다. npm install만 입력하면 package.js에 정의된 모듈이 모두 설치됩니다. package.json에 대해서는 뒤에서 자세히 설명하겠습니다.

~/ExampleEJS$ npm install

express-generator로 어플리케이션을 생성하면 bin 디렉터리 아래에 www 파일이 생깁니다. 이 파일을 node로 실행합니다.

 ~/ExampleEJS$ node bin/www

웹 브라우저에서 http://127.0.0.1:3000으로 접속합니다. 빈 화면에 Express Welcome to Express라고 표시됩니다.

이제 EJS 기본 문법을 알아보겠습니다. views/index.ejs 파일을 텍스트 에디터로 엽니다.

views/index.ejs

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
  </body>
</html>

HTML 태그 사이에 <% %>가 보입니다. 이 태그 안에서 자바스크립트를 사용하면 됩니다. 변수 출력은 <%= 변수 %>로 할 수 있습니다.

자바스크립트의 for, if 문법을 그대로 사용할 수 있습니다. 아래 코드는 Welcome to Express5번 출력합니다.

views/index.ejs

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <% for (var i = 0; i < 5; i++) { %>
    <p>Welcome to <%= title %></p>
    <% } %>
  </body>
</html>

routes/index.js 파일을 텍스트 에디터로 엽니다. routes 디렉터리 아래에 있는 자바스크립트 파일에서 .ejs 파일을 웹 브라우저에 보여주게 됩니다. res.render() 함수에 ejs 파일명을 지정하며 확장자는 생략합니다.

방금 views/index.ejs 파일에서 <%= title %>로 title 변수를 출력했습니다. 이 변수는 res.render() 함수에 넘겨준 값입니다. res.render() 함수에 넘겨주지 않은 값은 .ejs 파일에서 출력할 수 없습니다.

routes/index.js

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res) {
  res.render('index', { title: 'Express' });
});

module.exports = router;

템플릿 엔진인 만큼 함수를 이용해서 HTML 태그를 생성할 수 있습니다. 함수 이름이나 사용 방식은 Ruby On Rails와 비슷합니다.
https://code.google.com/p/embeddedjavascript/wiki/ViewHelpers

Jade

Jade는 HTML 문법을 간략화한 템플릿 엔진입니다. HTML은 간단한 내용이라도 코드가 길어지고 복잡하여 가독성이 떨어지는 단점이 있죠. Jade는 가독성이 높은 문법을 사용하여 생산성을 높입니다.

express-generator를 사용하여 뼈대를 먼저 만듭니다. express-generator는 전역 모듈(-g 옵션)로 설치를 해야 합니다(윈도우에서는 sudo 명령을 사용하지 않습니다). 그리고 웹 서버 소스들이 위치할 디렉터리를 생성합니다.

~$ sudo npm install -g express-generator
~$ mkdir ExampleJade
~$ cd ExampleJade
~/ExampleJade$ express

express-generator에 아무 옵션을 주지 않으면 Jade로 어플리케이션을 생성합니다.

├── app.js
├── bin
│   └── www
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── index.js
│   └── users.js
└── views
    ├── error.jade
    ├── index.jade
    └── layout.jade

필요한 모듈을 설치합니다. npm install만 입력하면 package.js에 정의된 모듈이 모두 설치됩니다. package.json에 대해서는 뒤에서 자세히 설명하겠습니다.

~/ExampleJade$ npm install

express-generator로 어플리케이션을 생성하면 bin 디렉터리 아래에 www 파일이 생깁니다. 이 파일을 node로 실행합니다.

 ~/ExampleJade$ node bin/www

웹 브라우저에서 http://127.0.0.1:3000으로 접속합니다. 빈 화면에 Express Welcome to Express라고 표시됩니다.

이제 Jade 기본 문법을 알아보겠습니다. views/layout.jade 파일과 views/index.jade 파일을 텍스트 에디터로 엽니다. 아래 내용을 보면 HTML 태그에서 <, > 기호를 뺀 모양입니다.

views/layout.jade

doctype html
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
  body
    block content

views/index.jade

extends layout

block content
  h1= title
  p Welcome to #{title}

중복되는 내용은 따로 분리하여 layout.jade 파일로 만들었고, index.jade 파일에서는 extends layout 문법을 이용하여 layout.jade 파일을 상속합니다. .jade는 확장자는 생략할 수 있습니다.

layout.jade, index.jade 파일 모두 block content가 있습니다. layout.jade 파일에서 block을 선언하고, index.jade 파일에서 block을 정의합니다.

HTML 태그에 변수를 바로 출력하고 싶다면 h1= title처럼 하면 됩니다. HTML 태그 중간에 변수를 출력할 때, 변수를 할당 할 때, 제어문을 사용할때에는 #{ }을 사용하면 됩니다.

아래 코드는 items 배열의 내용을 li 태그로 출력합니다.

extends layout

block content
  h1= title
  p Welcome to #{title}

  #{ items = ["one", "two", "three"] }
  each item, i in items
    li #{item}: #{i}

Jade는 Javascript와 별개로 Jade만의 태그 문법과 제어문을 가지고 있습니다.
http://jade-lang.com/reference

routes/index.js 파일을 텍스트 에디터로 엽니다. routes 디렉터리 아래에 있는 자바스크립트 파일에서 .jade 파일을 웹 브라우저에 보여주게 됩니다. res.render() 함수에 jade 파일명을 지정하며 확장자는 생략합니다.

방금 views/index.jade 파일에서 h1= title, #{title}로 title 변수를 출력했습니다. 이 변수는 res.render() 함수에 넘겨준 값입니다. res.render() 함수에 넘겨주지 않은 값은 .jade 파일에서 출력할 수 없습니다.

routes/index.js

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res) {
  res.render('index', { title: 'Express' });
});

module.exports = router;

실시간 통신 사용하기

Node.js에서 가장 강력한 기능은 누가 뭐래도 실시간 통신일겁니다. 다양한 실시간 통신 기술을 지원하고, 복잡한 코드 없이 간단하게 만들 수 있습니다.

Node.js에서 사용할 수 있는 대표적인 실시간 통신 기술은 다음과 같습니다.

  • TCP socket: 기본 내장 net 모듈로 TCP 소켓을 사용할 수 있습니다.
  • WebSocket: HTTP 프로토콜을 기반으로 양방향 통신(full-duplex)을 구현한 것입니다. 기존 HTTP 프로토콜은 서버 방향으로 요청 후 응답만 받을 수 있었고, 클라이언트 방향으로는 요청을 할 수 없었습니다. 또한, WebSocket은 HTTP 프로토콜을 기반으로 하고 있기 때문에 방화벽에서 걸러지는 경우가 적습니다. WebSocket은 모든 브라우저에서 지원하지는 않습니다. WebSocket 지원 웹 브라우저
  • socket.io: WebSocket 등 실시간 통신 기술의 웹 브라우저 호환성 문제를 해결하기 위해 생긴 프로젝트입니다. 옛날 IE6 부터 최신 웹 브라우저까지 지원합니다. WebSocket, Flash Socket, AJAX Long Polling, AJAX Multipart Streaming, Forever iframe, JSONP Polling 기술을 모두 포함하고 있으며 웹 브라우저의 종류와 버전에 따라 최적화된 기술을 알아서 사용합니다. 따라서 사용자는 여러 가지 실시간 통신 기술을 신경쓸 필요 없이 일관된 API와 문법을 사용하여 개발하면 됩니다.

WebSocket

WebSocket을 사용하려면 npm으로 모듈을 설치해야 합니다. 소스가 위치할 디렉터리를 생성하고, 모듈을 설치합니다.

~$ mkdir ExampleWebSocket
~/ExampleWebSocket$ npm install websocket

다음 내용을 작성하고 app.js로 저장합니다. WebSocket을 이용하여 클라이언트에서 받은 메세지를 다시 클라이언트로 보내는 예제입니다.

app.js

var WebSocketServer = require('websocket').server;
var http = require('http');

var server = http.createServer(function (req, res) {
  console.log('Received request for ' + req.url);
  res.writeHead(404);
  res.end();
});

server.listen(8000, function () {
  console.log('Server is listening on port 8000');
});

wsServer = new WebSocketServer({
  httpServer: server,
  autoAcceptConnections: false
});

wsServer.on('request', function (request) {
  var connection = request.accept('example-echo', request.origin);
  connection.on('message', function (message) {
    if (message.type === 'utf8') {
      console.log('Received message: ' + message.utf8Data);
      connection.sendUTF(message.utf8Data);
    }
    else if (message.type === 'binary') {
      connection.sendBytes(message.binaryData);
    }

    connection.on('close', function (reasonCode, description) {
      console.log('Peer ' + connection.remoteAddress + ' disconnected.');
    });
  });
});

node로 app.js를 실행합니다.

~/ExampleWebSocket$ node app.js

웹 브라우저에서 WebSocket을 사용하는 예제를 만들어보겠습니다. 다음 내용을 작성하고 index.html로 저장합니다. 코드를 간단하게 만들기 위해 HTML 처리 부분은 jQuery를 사용하였습니다.

WebSocket 서버에 연결되면 Hello 숫자10개 보냅니다. 그리고 서버에서 받은 메세지를 출력합니다.

index.html

<!DOCTYPE HTML>
<html>
<head>
  <title>Example WebSocket</title>
</head>
<body>

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script type="text/javascript">
if ('WebSocket' in window) {
  var ws = new WebSocket('ws://127.0.0.1:8000', 'example-echo');

  ws.onopen = function () {
    $('#status').text('connected');

    for (var i = 0; i < 10; i++) {
      ws.send('Hello ' + i);
    }
  };

  ws.onmessage = function (evt) {
    $('#messages').append($('<li>').text('Received message: ' + evt.data));
  };

  ws.onclose = function () {
    $('#status').text('connection is closed');
  };
}
else
  $('#status').text('WebSocket not supported.');
</script>

Status: <span id="status"></span><br /><br />
Messages: <ul id="messages"></ul>
</body>
</html>

index.html 파일을 웹 브라우저에서 열어봅니다(그냥 파일을 더블클릭 하면 됩니다). 웹 브라우저에서 Received message: Hello 숫자가 표시됩니다. 서버를 실행시킨 터미널(명령 프롬프트)에서도 Received message: Hello 숫자가 출력됩니다.

소스 내용을 살펴보겠습니다. require() 함수로 websocket과 http 모듈을 로딩합니다. 그리고 http 모듈로 http 서버를 생성하고 8000번 포트로 실행합니다.

var WebSocketServer = require('websocket').server;
var http = require('http');

var server = http.createServer(function (req, res) {
  console.log('Received request for ' + req.url);
  res.writeHead(404);
  res.end();
});

server.listen(8000, function () {
  console.log('Server is listening on port 8000');
});
참고
http 모듈로 생성한 서버, express와 연결하기
var express = require('express')
  , http = require('http')
  , app = express()
  , server = http.createServer(app)
  , WebSocketServer = require('websocket').server;

server.listen(8000);

wsServer = new WebSocketServer({
  httpServer: server,
  autoAcceptConnections: false
});
8000번 포트 뿐만 아니라 80번 포트도 사용할 수 있습니다.

http 서버를 이용하여 WebSocket 객체 wsServer를 생성합니다. 그리고 wsServer에 요청(request)이 발생하면 처리할 함수를 지정합니다. request.accecpt() 함수에 example-echo를 지정했습니다. 이것은 WebSocket 프로토콜이라고 하는데 여러 개의 통신을 처리할 수 있도록 이름을 만들어서 구분한 것입니다.

request.accecpt()로 연결 객체를 만든 뒤 메시지(message)가 오면 처리할 함수를 지정합니다. 메시지 형식이 UTF-8 인지, 바이너리인지에 따라 처리방식을 다르게 해야 합니다. 그리고 연결이 끊어졌을 때 호출될 함수를 지정할 수 있습니다.

아래 코드에서는 UTF-8 형식의 메시지가 오면 sendUTF() 함수를 사용하여 클라이언트로 메시지를 다시 보냅니다. 이것은 예제일 뿐이며 실제로는 서버에서 데이터를 생성하여 클라이언트로 보내게 됩니다.

wsServer = new WebSocketServer({
  httpServer: server,
  autoAcceptConnections: false
});

wsServer.on('request', function (request) {
  var connection = request.accept('example-echo', request.origin);
  connection.on('message', function (message) {
    if (message.type === 'utf8') {
      console.log('Received message: ' + message.utf8Data);
      connection.sendUTF(message.utf8Data);
    }
    else if (message.type === 'binary') {
      connection.sendBytes(message.binaryData);
    }

    connection.on('close', function (reasonCode, description) {
      console.log('Peer ' + connection.remoteAddress + ' disconnected.');
    });
  });
});

웹 브라우저에서 실행되는 HTML 소스를 살펴보겠습니다. 웹 브라우저에 내장된 WebSocket API를 사용하였습니다. WebSocket을 지원하지 않는 브라우저는 WebSocket API 자체가 없습니다.

WebSocket API는 자바스크립트에서 호출할 수 있습니다. 먼저 WebSocket 서버 주소와 프로토콜(example-echo)을 지정하여 WebSocket 객체를 만듭니다. WebSocket 서버 주소는 ws://로 시작합니다.

생성한 ws에 WebSocket이 연결되었을 때(onopen), 메시지를 받았을 때(onmessage), 연결이 끊어졌을 때(onclose) 처리할 함수를 지정할 수 있습니다. 아래 예제에서는 WebSocket 서버에 연결되면 connected 문자열을 표시하고 Hello 숫자 메시지를 10개 보냅니다. 그리고 서버에서 받은 메시지를 표시합니다.

<script type="text/javascript">
if ('WebSocket' in window) {
  var ws = new WebSocket('ws://127.0.0.1:8000', 'example-echo');

  ws.onopen = function () {
    $('#status').text('connected');

    for (var i = 0; i < 10; i++) {
      ws.send('Hello ' + i);
    }
  };

  ws.onmessage = function (evt) {
    $('#messages').append($('<li>').text('Received message: ' + evt.data));
  };

  ws.onclose = function () {
    $('#status').text('connection is closed');
  };
}
else
  $('#status').text('WebSocket not supported.');
</script>

이렇게 실시간 통신을 자바스크립트로 간단하게 만들 수 있습니다. WebSocket 서버 <-> 웹 브라우저 뿐만 아니라 WebSocket 서버 <-> WebSocket 서버도 가능합니다.
http://github.com/Worlize/WebSocket-Node

socket.io

이번에는 socket.io 모듈을 사용하여 웹 브라우저에서 실시간 통신을 예제를 만들어보겠습니다. socket.io를 사용하려면 npm으로 모듈을 설치해야 합니다. 소스가 위치할 디렉터리를 생성하고, 모듈을 설치합니다.

~$ mkdir ExampleSocketIO
~/ExampleSocketIO$ npm install socket.io

다음 내용을 작성하고 app.js로 저장합니다. 서버와 클라이언트가 한번씩 메시지를 주고받는 예제입니다.

app.js

var io = require('socket.io').listen(8000);

io.sockets.on('connection', function (socket) {
  socket.emit('example message 1', { hello: 'world 1' });

  socket.on('example message 2', function (data) {
    console.log(data);
  });
});

node로 app.js를 실행합니다.

~/ExampleSocketIO$ node app.js

웹 브라우저에서 socket.io를 사용하는 예제를 만들어보겠습니다. 다음 내용을 작성하고 index.html로 저장합니다. 코드를 간단하게 만들기 위해 HTML 처리 부분은 jQuery를 사용하였습니다.

socket.io 서버에 연결하고 서버로 메시지를 보냅니다. 그리고 서버에서 받은 메시지를 출력합니다.

index.html

<!DOCTYPE HTML>
<html>
<head>
  <title>Example Socket.IO</title>
</head>
<body>

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://127.0.0.1:8000/socket.io/socket.io.js"></script>
<script type="text/javascript">
var socket = io.connect('http://127.0.0.1:8000');
socket.on('example message 1', function (data) {
  $('#messages').append($('<li>').text('Received message: ' + data.hello));
});

socket.emit('example message 2', { hello: 'world 2' });
</script>
Messages: <ul id="messages"></ul>
</body>
</html>

index.html 파일을 웹 브라우저에서 열어봅니다(그냥 파일을 더블클릭 하면 됩니다). 웹 브라우저에서 Received message: world 1이 표시됩니다. 서버를 실행시킨 터미널(명령 프롬프트)에서는 Received message: world 2가 출력됩니다.

소스 내용을 살펴보겠습니다. require() 함수로 socket.io를 로딩하고, 8000번 포트로 서버를 실행합니다.

var io = require('socket.io').listen(8000);
참고
http 모듈로 생성한 서버와 연결하기
var server = require('http').createServer(handler)
  , io = require('socket.io').listen(server);

function handler(req, res) {
  res.writeHead(404);
  res.end();
}

server.listen(8000);
http 모듈로 생성한 서버, express와 연결하기
var express = require('express')
  , http = require('http')
  , app = express()
  , server = http.createServer(app)
  , io = require('socket.io').listen(server);

server.listen(8000);
8000번 포트 뿐만 아니라 80번 포트도 사용할 수 있습니다.

io.sockets에 웹 브라우저가 접속했을 때 실행할 함수를 지정합니다. 메시지는 이 함수가 호출되어야 보낼 수 있습니다. socket.emit() 함수로 클라이언트에 메세지를 보낼 수 있는데 아래와 같이 메시지 이름을 정할 수 있습니다.

socket.on() 함수에 메시지 이름을 지정하면 해당 메시지가 왔을 때 함수가 호출됩니다. 클라이언트에서 example message 2 메시지를 보내면 터미널(명령 프롬프트)에 데이터를 출력합니다.

io.sockets.on('connection', function (socket) {
  socket.emit('example message 1', { hello: 'world 1' });

  socket.on('example message 2', function (data) {
    console.log(data);
  });
});

웹 브라우저에서 실행되는 HTML 소스를 살펴보겠습니다. 웹 브라우저에서 실행되는 클라이언트에서 가장 중요한 부분은 <script src="http://127.0.0.1:8000/socket.io/socket.io.js"></script>입니다. socket.io.js 안에서 웹 브라우저의 종류와 버전에 맞게 알아서 처리해줍니다.

WebSocket에 비해서 코드가 간단하고, 함수 사용법도 서버와 동일합니다. io.connect() 함수로 socket.io 서버에 접속하여 socket 객체를 만듭니다. 그리고 socket.emit() 함수로 서버에 메시지를 보낼 수 있고, socket.on() 함수로 메시지 마다 처리할 함수를 지정할 수 있습니다.

<script src="http://127.0.0.1:8000/socket.io/socket.io.js"></script>
<script type="text/javascript">
var socket = io.connect('http://127.0.0.1:8000');
socket.on('example message 1', function (data) {
  $('#messages').append($('<li>').text('Received message: ' + data.hello));
});

socket.emit('example message 2', { hello: 'world 2' });
</script>

socket.io는 WebSocket 및 기타 실시간 통신 방법을 추상화한 라이브러리입니다. 그래서 다양한 기능을 제공하고 있고, 몇 줄 안되는 코드로 실시간 통신을 구현할 수 있습니다.

모듈 작성하기

웹 브라우저의 자바스크립트는 HTML에서 <script> 태그로 로딩을 합니다. 하지만 지금까지 자바스크립트 끼리는 서로 로딩할 방법이 없었는데 최근 여러 가지 표준이 개발되었습니다. node.js는 CommonJS 모듈 방식을 사용합니다.

앞에서 require() 함수로 모듈을 로딩하여 사용했습니다. require() 함수는 패키지화 된 모듈(node_modules 디렉터리 안에 있는), 사용자가 만든 파일 등을 로딩할 수 있습니다.

이번에는 파일 하나를 모듈로 작성하여 사용해보겠습니다. 아래 내용을 작성하여 각 파일로 저장합니다. 간단한 덧셈, 곱셈 모듈과 데이터 모듈 예제입니다.

sum.js

module.exports = function (a, b) {
  return a + b;
};

mul.js

module.exports.Multiple = function (a, b) {
  return a * b;
};

data.js

module.exports = {
  hello: 'example data 1',
  world: 'example data 2'
};

app.js

var sum = require('./sum.js');
var Multiple = require('./mul.js').Multiple;
var data = require('./data.js');

console.log(sum(1, 2));
console.log(Multiple(3, 4));
console.log(data.hello);

node로 app.js를 실행합니다. 3example data 1이 출력됩니다.

$ node app.js
3
12
example data 1

파일을 모듈로 만들려면 아래와 같이 module.exports에 함수, 배열, 오브젝트를 지정해주면 됩니다. module.exports에 바로 지정할 수도 있고, module.exports.Func1처럼 키를 더 지정해 줄 수도 있습니다. 자바스크립트는 문법이 매우 자유롭기 때문에 다양한 형식으로 사용할 수 있습니다.

module.exports = function () { };
// 또는
module.exports.Func1 = function () { };
module.exports = { hello: 'world', example: function () { } };
// 또는
module.exports.Data1 = { hello: 'world', example: function () { } };
module.exports = [1, 2, 3];
// 또는
module.exports.Data2 = [1, 2, 3];

require() 함수를 사용할 때 패키지화 된 모듈은 이름만 지정하면 되고, 파일은 정확한 경로와 확장자를 지정해야 합니다.

var sum = require('./sum.js');
var Multiple = require('./mul.js').Multiple;
var data = require('./data.js');

package.json 작성하기

개발한 Node.js 어플리케이션을 서버에 배포할 때에는 package.json 파일이 필수입니다. package.json 파일은 현재 어플리케이션에서 사용하고 있는 npm 모듈을 정의합니다. 그래서 npm 명령은 package.json 파일을 읽어서 모듈을 설치하게 됩니다.

https://www.npmjs.org/doc/json.html

요즘은 인터넷 환경도 많이 바뀌었고, 규모도 커져서 서버 한대로 서비스를 하기 보다는 서버 여러대로 서비스를 하기도 합니다. 만약 100대 가량 되는 서버에 일일이 서버에 접속하여 npm install express, npm install socket.io 명령을 입력하고 있을 수는 없습니다.

package.json 파일은 소스의 최상위 디렉터리에 위치해야 합니다. 아래는 제가 개발 중인 서비스에서 사용하고 있는 package.json 파일입니다.

다른 부분은 키 이름만 보면 쉽게 이해가 될 것입니다. 가장 중요한 부분은 dependencies 부분입니다. 여기서 npm 모듈의 이름과 버전을 지정합니다.

package.json

{
  "name": "Example Application",
  "version": "0.0.1",
  "description": "Example Application ...",
  "main": "app.js",
  "author": {
    "name": "pyrasis",
    "email": ""
  },
  "dependencies": {
    "express": "3.4.x",
    "express-validator": "1.0.x",
    "cluster": "0.7.7",
    "sync": "0.2.x",
    "mongoose": "3.8.x",
    "ejs": "0.8.x",
    "randomstring": "1.0.x",
    "request": "2.33.x",
    "bcrypt": "0.7.x",
    "redis": "0.10.x",
    "kue": "0.7.x",
    "socket.io": "0.9.x",
    "connect": "2.13.x",
    "cookie": "0.1.x",
    "gm": "1.14.x",
    "sitemap": "0.7.x",
    "universal-analytics": "0.3.x",
    "node-schedule": "0.1.x",
    "moment": "2.5.x",
    "aws-sdk": "2.0.x",
    "fs-extra": "0.8.x",
    "client-sessions": "0.6.x"
  }
}

dependencies에서 버전을 지정하는 방법은 여러 가지 입니다. 정해진 버전만 사용하거나, 부등호-로 버전 대역을 설정할 수 있습니다. ~1.2는 가장 근접한 버전, 2.x는 2.으로 시작하는 가장 최신 버전을 사용합니다. npm 대신 직접 URL을 지정할 수도 있습니다.

https://www.npmjs.org/doc/misc/semver.html

{ "dependencies" :
  { "foo" : "1.0.0 - 2.9999.9999"
  , "bar" : ">=1.0.2 <2.1.2"
  , "baz" : ">1.0.2 <=2.3.4"
  , "boo" : "2.0.1"
  , "qux" : "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"
  , "asd" : "http://asdf.com/asdf.tar.gz"
  , "til" : "~1.2"
  , "elf" : "~1.2.3"
  , "two" : "2.x"
  , "thr" : "3.3.x"
  }
}

Node.js와 npm 모듈들은 발전속도가 매우 빠릅니다. 버전이 조금만 바뀌어도 API가 바뀌거나 호환이 안될때가 많습니다. 그러므로 버전을 고정하거나 호환성이 유지되는 최신 버전을 사용하고자 할 때 유용합니다.

package.json 파일을 작성하고 npm install 명령을 입력하면, 정의된 모듈을 설치합니다.

$ npm install

환경변수 설정하기

윈도우에서 환경변수 설정하기(PowerShell을 실행합니다)

PS C:\Project\Hello> $env:NODE_ENV="production"
PS C:\Project\Hello> node app.js

리눅스에서 환경 변수 설정하기

$ NODE_ENV="production" node app.js

유용한 모듈

Node.js로 개발할 때 생산성을 높일 수 있는 유용한 모듈이 많습니다. 그중 제가 애용하는 모듈을 소개하겠습니다.

forever

자바스크립트 소스 파일을 감시하다가 소스 파일의 내용이 바뀌면 자동으로 node를 재시작하는 모듈입니다. forever start로 node를 시작하며 -w 옵션으로 파일을 감시합니다.

$ npm install -g forever
$ forever start -w ./app.js

유닉스/리눅스에서 80번 포트 등을 사용하기 위해 root 권한이 필요하다면 sudo를 앞에 붙여줍니다.

$ sudo forever start -w ./app.js

forever로 실행하면 로그가 터미널에 바로 출력되는데 이 로그를 파일로 저장할 수 있습니다. -l 옵션으로 로그 파일의 경로를 지정합니다. -a 옵션은 로그를 계속 덧붙입니다.

forever start -l $PWD/logs/example.log -a -w ./app.js

forever의 감시에서 제외할 파일과 경로를 지정할 수 있습니다.

.foreverignore

*.log
**/.git/**
**/upload/**
**/backup/**
**/public/**

주의
-l 옵션으로 로그를 저장할 때 로그 파일의 위치가 소스 디렉터리나 그 하위 디렉터리라면, 꼭 .foreverignore 파일에서 로그파일을 제외를 시켜주어야 합니다.

로그 파일을 제외시키지 않으면 로그 내용이 로그 파일에 저장될 때마다 forever가 node를 재시작 시킵니다. 특히 app.js를 시작할 때 로그를 출력하면 node가 무한 재시작 되니 주의하세요!

이런 문제가 발생하면 원인을 찾기가 너무 어려워집니다. 서버에 접속이 잘 되었다가 안되었다가 하니까 엉뚱하게도 네트워크 문제나 OS 문제를 찾게됩니다. Node.js는 시작 속도가 매우 빨라서 무한 재시작 되어도 언듯 보기에는 접속이 원활한 것 처럼 느껴집니다.

환경변수를 설정하는 방법은 node를 실행할 때와 동일합니다.

nodemon

forever와 비슷한 모듈입니다. 소스 파일의 내용이 바뀌면 자동으로 node를 재시작 합니다. 윈도우에서 개발할 때 forever보다 nodemon이 좀더 편리합니다.

$ npm install -g nodemon
$ nodemon app.js

환경변수를 설정하는 방법은 node를 실행할 때와 동일합니다.


저작권 안내

이 웹사이트에 게시된 모든 글의 무단 복제 및 도용을 금지합니다.
  • 블로그, 게시판 등에 퍼가는 것을 금지합니다.
  • 비공개 포스트에 퍼가는 것을 금지합니다.
  • 글 내용, 그림을 발췌 및 요약하는 것을 금지합니다.
  • 링크 및 SNS 공유는 허용합니다.

Published

24 May 2014