Node.js에는 데이터베이스를 이용하기 위한 기능도 포함되어 있다. 여기에서는 PostgreSQL의 이용에 대해 설명하고 데이터베이스를 사용한 응용 프로그램을 Heroku에 배포해서 동작하게 하는 방법을 설명한다.


Node.js에서 PostgreSQL 사용

데이터베이스는 Web 응용 프로그램을 만드는 있어서 매우 중요하다. Node.js에도 데이터베이스를 이용하기 위한 기능을 제공한다. 그러나 표준으로 준비되는 것이 아니라 추가 기능 프로그램을 추가하여 이용할 수 있다.

이런 개발에서 데이터베이스를 사용할 때 생각해야 할 것은 '운영 환경은 어떻게 되어 있는지"이다. 로컬로 동작하는 것만으로는 활용도가 만힝 떨어진다.

여기에서는 "Heroku에서 Web 응용 프로그램을 배포한다"는 것을 목표로 설명하기로 한다. 그러기 위해서는 먼저 "Heroku 데이터베이스 환경은 어떻게되어 있는가"를 알아야 할 것이다.

Heroku의 경우 표준 PosgreSQL가 대응하고 있다. 다른 것을 사용할 수 없는 것은 아니지만, 기본은 PostgreSQL으로 생각 하자.

그럼 Heroku에 Node.js 응용 프로그램에서 PostgreSQL을 사용하기 위한 준비를 한다. 이는 Heroku의 설정과 Node.js 측의 설정이 필요하다. 또한, 여기에서는 이미 Node.js 응용 프로그램을 Heroku에 배포할 준비가 되어 있을 거라는 전제로 설명한다. 아직의 준비가 되어 있지 않다면 heroku create에서 응용 프로그램을 준비하고 한번 배치하고 동작 확인을 한다.

PostgreSQL 준비

당연 애기이지만, 로컬 환경에 PostgreSQL을 설치해야 한다. 아래 주소에서 프로그램을 다운로드하여 설치한다. "서버에서 동작하니가 필요없는거 아니야"라고 생각해서는 안된다. 로컬 환경에 PostgreSQL가 설치되어 있지 않으면, 테스트하기도 힘들고 나중에 Heroku의 작업에 문제가 발생하므로 반드시 준비하자.

http://www.postgresql.org/download/

환경 변수 Path 설정

설치한 PostgreSQL의 bin 폴더의 경로를, 환경 변수 path 변수에 추가하고, bin 안에 있는 명령이 이름으로 호출할 수 있도록 해야 한다. Windows이라면 PostgreSQL는 표준으로 다음 경로에 설치되므로 이를 Path에 추가한다.

C:\Program Files\PostgreSQL\x.x\bin

Node.js 준비

명령 프롬프트/터미널에서 Node.js 응용 프로그램의 디렉토리에 현재 디렉토리를 이동해야 한다. 그리고 다음과 같이 명령을 실행한다.

$ npm install

이것은 이미 많이 익숙해 졌을 것이다. 만약을 위해 필요한 프로그램을 npm으로 설치해 둔다.

$ npm install pg

pg는 PostgreSQL을 Node.js에서 사용하기 위한 추가 프로그램이다. 이것으로 pg과 이에 필요한 프로그램들이 설치된다.




Heroku에 준비

Heroku 측의 준비를 한다. 이것도 명령 프롬프트/터미널에서 명령으로 조작한다. Node.js 어플리케이션으로 이동한 후 다음과 같이 작업한다.

$ heroku login

아직 로그인하지 않은 경우라면 먼저 heroku에 로그인한다. 실행하면 계정(등록 메일 주소)와 비밀번호를 물어 오므로 정확하게 입력하면 로그인된다.

$ heroku addons:add 
$ heroku-postgresql:dev

heroku에 PostgreSQL의 기능을 추가한다. 이것은 "heroku addons:add heroku-postgresql:플랜 "이라는 형태로 실행한다. 무료 플랜의 경우는 플랜 이름은 "dev"가 되므로, 우선 이것으로 실행한다.

heroku pg:promote HEROKU_POSTGRESQL_이름_URL

추가 기능이 추가되면 "attached as HEROKU_POSTGRESQL_XXX_URL"라는 데이터베이스 이름이 표시된다. 이 앱의 기본 데이터베이스로 사용하게 해 두는 것이 "heroku pg:promote"이다. 이것으로 앞전에 추가한 애드온 플랜이 사용할 수 있게 된다.

Heroku에 테이블 생성

이제 데이터베이스의 기능을 설정할 수 있었지만, 이 상태에서는 아직 데이터베이스에 테이블도 아무것도 없다. 실제로 프로그램을 작성하기 전에 테이블을 준비하도록 한다. 이번에는 아주 간단한 테이블 mydata을 마련하기로 한다. 이것은 다음의 항목이 있다.

항목설명
id자동으로 할당된 정수 값이다. 기본 키가 되는 것.
name이름을 저장한다. 텍스트 값.
mail이메일 주소를 보관한다. 텍스트 값.
memo메모를 쓴다. 텍스트 값.

그럼 이 테이블을 Heroku에 작성한다. 이 작업도 계속 명령 프롬프트/터미널에서 한다. 다음과 같이 실행한다.

heroku pg:psql

이것으로 Heroku의 데이터베이스에 SQL 문을 전송하고 조작할 수 있다. 이 명령은 로컬 환경에 PostgreSQL가 설치되어 있으며, PostgreSQL 프로그램에 액세스할 수 있어야(환경 변수 Path 경로가 등록 필요) 한다. 그 준비 되지 않으면 실행할 수 없기에 주의한다.

명령을 무사히 통과하면 SQL 문을 입력 할 수있는 상태가 된다. 그럼 다음과 같이 입력하여 테이블 "mydata"를 만든다.

create table mydata (
   id serial primary key,
   name char(50),
   mail char(100),
   memo char(255)
);

이걸로 Heroku에 추가 한 데이터베이스 프랜에 mydata 테이블이 생성되었다. 이 후에는 프로그램을 작성하면서 이 테이블을 이용하면 된다.




Node.js 어플리케이션에 Heroku용 파일을 추가

그럼 프로그램 작성을 해보자. 이번에도 Express를 이용한 Node.js 어플리케이션을 만든다. Express에서 자동 생성된 어플리케이션을 기반으로 설명을 하고 있으므로, 우선 기본적인 어플리케이션을 준비한다.

또한 Express 어플리케이션 작성에 대해서는 다음에 설명되어 있으므로 자세히 읽어 보시기 바란다.

express-generator 설치

그럼, 준비된 Node.js 어플리케이션에 Heroku 이용을 위한 파일을 추가한다. 이러한 내용은 이미 설명을 했었다.

Heroku 운영 환경

또한, 여기에서는 어플리케이션의 스크립트를 "app.js"라는 파일명으로 작성되어 있다. 파일 이름이 다를 경우는 아래 파일 내용에서 app.js부분을 찾아서, 자신의 응용 프로그램의 스크립트 파일 이름에 변경하여 사용하면 된다.

package.json 파일

어플리케이션의 루트에 같은 이름의 파일을 작성한다. 그리고 패키지 정보를 여기에 작성한다. 내용은 아래에 올렸 두었으므로, 이대로 작성한다. 이것은 PostgreSQL을 사용하는 어플리케이션의 설정이다.

  "name": "application-name",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "latest",
    "pg": "latest",
    "ejs": "*"
  }
}

Procfile 파일

이것도 응용 프로그램의 루트에 배치합니다. 이 파일의 내용은 단 한 줄뿐이다. 다음과 같이 작성한다.

web: node app.js

 

이제 Heroku 응용프로그램 시작시 자동으로 node app.js가 실행되고 Node.js 응용 프로그램이 서버에서 실행하고 사용할 수 있다.



Express의 app.js를 준비

그럼 스크립트 작성해 본다. 먼저 어플리케인션의 기본 부분이 되는 app.js 이다. 이 파일은 기본적인 처리는 이미 설명하고 있기 때문에 다시 설명 할 필요는 없을 것이다.

이번에는 홈페이지( "/"주소) 외에, 데이터 작성 양식 페이지("/add" 주소)와 양식을 제출했을 때의 처리( "/create" 주소)를 준비한다. 이 세가지 액션을 위한 라우팅 정보의 등록 부분만 제대로 만들면 나머지는 기본적으로 생성된 스크립트를 그대로 사용하고 있기 때문에 어려운 것은 없을 것이다.

routes 안의 파일 로드

var add = require('./routes/add');
var create = require('./routes/create');

여기에서는 routes 폴더에 add.js, create.js라는 스크립트를 준비한다. 이미 index에 대해 준비하고 있기 때문에, 그 외에 두개의 스크립트 파일을 새로 추가한다. 그들을 읽어 변수에 대입해야 한다.

루트 설정

app.use('/add', add);
app.use('/create', create);

add.js에 마련한 add와 create.js에 준비한 create을 /add와 /create URI로 설정한다. 그러면 이 주소에 액세스가 되면 지정된 스크립트가 호출된다.



테이블 목록 표시

그럼 스크립트를 만들어 보자. 우선은 홈페이지("/"주소)부터 이다. 이것은 routes폴더에 index.js파일로 작업하겠다. 표시용 템플릿은 views폴더에 index.ejs로 만든다.

아래에 각각의 파일의 소스 코드를 올려 두었다. 작성하여 지정된 폴더에 파일을 배치해야 한다.

index.js

var express = require('express');
var router = express.Router();
var pg = require('pg');
 
/* GET home page. */
router.get('/', function(request, response, next) {
    var con = "tcp://이용자:비밀번호@호스트:포트번호/데이터베이스; //★
    pg.connect(con, function(err, client) {
        var query = client.query('select * from mydata;');
        var rows = [];
        query.on('row', function(row) {
            rows.push(row);
        });
        query.on('end', function(row,err) {
            response.render('index', { 
                title: 'Express',
                data:rows
            });
        });
        query.on('error', function(error) {
            console.log("ERROR!!" + error);
            response.render('index', {
                title: title,
                data: null,
                message: "ERROR is occured!"
            });
        });
    });
});
 
module.exports = router;

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>
   <% if (data != null) { %>
       <table border="1">
       <% for(var i=0; i < data.length; i++){ %>
           <tr>
           <td width="200px"><%= data[i].name %></td>
           <td width="200px"><%= data[i].mail %></td>
           <td width="300px"><%= data[i].memo %></td>
           </tr>
       <% } %>
       </table>
   <% } %>
 </body>
</html>

이 상태로는 동작하지 않는다. ★로 표시된 텍스트를 각각의 환경에 맞게 수정한다. 디폴트로는 데이터베이스 이름과 관리자 이름이 postgres가 있고, 암호를 1111 이었다면 아래와 같이 설정하면 된다.

tcp://postgres:1111@localhost:5432/postgres

이것으로 mydata의 데이터를 모두 가져 와서 테이블에 함께 표시한다. 다만. 아직 /add와 /create를 만들지 않기 때문에 이 단계에서 실행해도 잘 동작하지 않을 것이다. 최후까지 완성하고 표시를 확인해야 한다.

그럼, 소스 코드의 설명을 하겠다. Node.js의 pg를 사용하여 데이터베이스에 액세스할 기본 사항을 순서대로 설명하고 있다.

데이터베이스 연결

pg.connect(연결정보, 콜백함수);

데이터베이스에 연결은 pg.connect라는 메소드를 사용한다. 이것은 첫번째 인수에 연결된 주소를 두번째 인수는 연결이 시작되고 나서 실행되는 콜백 함수를 지정한다. 예제 스크립트는 다음과 같이 작성하고 있다.

var con = "tcp://이용자:비밀번호@호스트:포트번호/데이터베이스; //
pg.connect(connectionString, function(err, client) {... 연결시 처리 ...}

연결은 tcp라는 프로토콜 텍스트에서 준비한다. 이용자, 비밀번호, 호스트, 포트 번호, 데이터베이스 등의 정보를 하나로 묶어 작성하기 때문에 틀리지 않도록 주의해야 한다.

connect는 비동기 함수로 연결이 완료된 곳에서 인수의 함수를 호출하여 실행한다. 이 콜백 함수는 두 개의 인수를 준비한다. 첫번째 인수에는 오류가 발생했을 때의 정보를 정리한 객체를 또, 두번째 인수에는 연결된 데이터베이스에 대한 액세스를 관리하는 Client라는 개체가 반환된다.

SQL 쿼리를 실행하기

변수 = client.query(쿼리 문);

연결한 데이터베이스를 조작하려면 Client의 "query"메소드를 사용한다. 이것은 SQL 문을 데이터베이스에 보내고 실행하기 위한 것이다. 인수에는 SQL 쿼리 텍스트를 지정한다.

SQL 문에서는 select처럼 값을 취득하는 처리도 있지만,이 query 메소드 자체에서 리턴 값을 돌려주는 것은 아니기 때문에 주의가 필요하다. 이 query 메서드는 비동기적으로 액세스 한다. 따라서 실행 결과가 되는 반환 값으로 데이터를 받을 수 있는 것은 아니다.

반환되는 값은 "Query"라는 객체이다. 이 Qurery에는 처리의 실행 상황에 따라 발생하는 이벤트 핸들러가 준비되어 있어 이것을 이용하여 작업을 수행한다.

예제를 살펴 보자. 우선 다음과 같이 select 문을 실행하고 Query를 변수로 받고 있다.

var query = client.query('select * from mydata;');

이 query는 두번째 인수에 실행한 후에 처리하는 콜백 함수를 지정할 수 있지만, 여기에서는 단순히 쿼리를 실행하기만하도록 작성하였다. 이 query를 실행한 후에 반환되는 Query 객체의 이벤트를 설정한다. 이 이벤트는 총 3가지 종류 준비되어 있다. 각각 다음과 같이 설정한다.

row의 검색 이벤트

query.on('row', function(row){……처리……} );

select 등 레코드를 검색하는 쿼리를 실행할 때 레코드를 검색 할 때마다 이 row 이벤트가 발생 처리가 호출된다. 인수 row에는 얻은 레코드를 객체로 정리한 것이 저장된다.

종료 이벤트

query.on('end', function(result){……처리……} );

모든 작업이 완료될 때 호출된다. 인수에는 쿼리 실행에 관한 각종 정보를 정리한 Result 객체가 전달된다.

오류 이벤트

query.on('error', function(error){……처리……} );

오류가 발생했을 때에 호출된다. 다만 query 메소드를 호출할 때에 콜백 함수를 설정하고 있던 경우는 그쪽이 우선되어, 이 이벤트는 발생하지 않으므로 주의한다.

이 3개의 이벤트를 조합하여 쿼리 실행 후의 처리를 만들 수 있다. 예제 스크립트에는 row 이벤트에서 얻은 row 객체를 배열에 추가 저장한다. 이렇게 하여 얻은 모든 row 데이터를 하나로 모을 수 있다.

또한 주목해 줬으면 하는 것은 index.js의 렌더링 타이밍이다. 여기에서는 end 이벤트가 발생한 곳에서 render 표시를 렌더링하고 있는 것을 알 수 있다(다른, error일때도 수행하고 있지만 ...). 이처럼 처리가 완료되고 표시를 렌더링하지 않으면, 결과를 잘 얻어와도 표시가 되지 않기에 주의해야 한다.


레코드 추가

다음은 레코드를 추가 할 과정이다. 이것은 routes내 준비하는 add.js에서의 폼 표시를 create.js에서 양식 제출 후 처리(즉, 양식의 내용에서 레코드를 저장하는 처리)를 구현한다. 또한, views 폴더에 양식을 표시하는 add.ejs 템플릿 파일을 준비할 필요하다.

아래 목록 란에 3 개의 파일의 내용을 정리해 게재하고 있습니다.

add.ejs

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <p><%= msg %></p>
    <form method="post" action="/create">
    <table>
        <tr><td>NAME:</td><td><input type="text" name="name"></td></tr>
        <tr><td>MAIL:</td><td><input type="text" name="mail"></td></tr>
        <tr><td>MEMO:</td><td><input type="text" name="memo"></td></tr>
        <tr><td></td><td><input type="submit"></tr>
    </table>
    </form>
  </body>
</html>

add.js

var express = require('express');
var router = express.Router();
 
/* add page. */
router.get('/', function(request, response, next) {
    response.render('add', 
        {
            title: 'Add Page',
            msg: 'please type data:'
        }
    );
});
 
module.exports = router;

create.js

var express = require('express');
var router = express.Router();
var pg = require('pg');
 
/* add page. */
router.post('/', function(request, response, next) {
    var name_str = request.body["name"];
    var mail_str = request.body["mail"];
    var memo_str = request.body["memo"];
      
    var con = "tcp://postgres:admin@localhost:5432/postgres";
    pg.connect(con, function(err, client) {
        var qstr = "insert into mydata (name,mail,memo) values($1, $2, $3);";
        var query = client.query(qstr,[name_str, mail_str, memo_str]);
        query.on('end', function(row,err) {
            response.redirect("/");
        });
        query.on('error', function(error) {
            console.log("ERROR!");
            response.render('index', {
                title: "ERROR",
                data: null,
                message: "ERROR is occured!"
            });
        });
    });
});
 
module.exports = router;

フォームに記入して送信すると、それがレコードとしてテーブルに追加されます。作成後、indexにリダイレクトされるので、表示されるレコードを確認できます。

폼을 작성하여 제출하면 그것이 레코드로 테이블에 추가된다. 작성 후에 index로 리디렉션되므로 표시된 레코드를 확인할 수 있다.

● POST 처리

router.post (...중략...);

이번, create.js에는 처리를 추가하는데 router.get 대신 "router.post"를 사용하고 있다. POST된 처리는 이 post 함수를 이용하여 처리를 설정해야 한다.

수신된 폼에서 정보 얻기

var name_str = request.body["name"];
var mail_str = request.body["mail"];
var memo_str = request.body["memo"];

전송된 폼의 값은 일반 Node.js에서의 방식과는 조금 다르다. request의 "body"에 값이 정리하고 있는 것이다. request.param에서 얻어 오려고 하면 오류가 되므로 주의한다.

레코드 추가

레코드 추가도 기본적으로는 앞전의 select 처리와 실제는 변함이 없다. db.connect으로 데이터베이스에 연결하고 Client 객체의 query 메소드에서 SQL을 실행하는 흐름이다. 단순히 실행하는 SQL의 내용이 변경되었을 뿐이다.

var qstr = "insert into mydata (name,mail,memo) values($1, $2, $3);";
var query = client.query(qstr,[name_str, mail_str, memo_str]);

여기에서는 이렇게 하고 insert 문을 실행하고 있다. 주목하고 싶은 것은 values 이 후에 있는 $1, $2, $3이다. 이것은 query를 실행하면 값이 전달되는 변수이다.

query에는 두번째 인수에, [name_str, mail_str, memo_str]라는 배열이 준비되어 있다. 이 값이 $1, $2, $3에 각각 대입된다. 쿼리 문장의 텍스트에 변수 등의 값을 포함하려면이 같은 $를 사용한 변수를 사용한다.

실행 후에 end 이벤트를 사용하여 실행한 후 첫 페이지로 리디렉션한다. 아래와 같은 부분이다.

query.on('end', function(row,err) {
    response.redirect("/");
});

리디렉션은 Response 객체의 "redirect"를 이용한다. 인수에는 리디렉션 주소를 지정하면 된다. 간단하다.

데이터베이스 액세스에 대한 기능은 이것 뿐이다. 물론 객체의 메소드 이라든지 세세한 것은 아직 남아 있지만, 기본적으로 "connect하고 query한다. 이 후에 이벤트 핸들러에서 처리하면 끝이라는 흐름 알면, 기본적인 데이터베이스 액세스는 대부분 구현할 수 있다.

어쨌든, SQL을 그대로 쓰기 뿐이기에, "세세한 것은 SQL 측에서 어떻게 해야 한다"라는 것이다.



Express를 이용하여 Web 페이지를 만들 때에 필요한 처리로서. "쿼리 문자열 처리", "양식 제출 처리", "Ajax로 JSON 데이터 송수신", "쿠키의 읽기 및 쓰기"라는 것에 대해 설명한다.


쿼리 문자열로 페라미터 전송

어느 페이지에서 다른 페이지에 액세스할 때, 어떠한 형태로 필요한 정보를 전달할 필요가 있었다. 어떤 방식을 생각할 수 있을까?

쿼리 문자열로 페라미터 전송하기

우선, 모두가 생각할 수 있는 것은 "쿼리 문자열"을 이용하는 것이다. 쿼리 문자열이라고 하는 것은, URL의 뒷부분에 사용하여 값을 작성한다. 예를 들어. http://localhost/index?a=hello와 같은 형태로 URL을 지정하여 a라는 페라미터에 "hello"를 설정하여 보낼 수 있다.

이 쿼리 문자열 값은 Request 객체의 "query" 속성 안에 정리되고 있다. 이 중에 각각의 key마다 속성으로 값이 저장된다. 예를 들어, /index?a=hello라면, quey.a에 "hello"가 저장되어 있다는 것이다.

실제로 간단한 사용 예제를 아래에 올려 두었다.

views/helo.ejs

 <body>
   <h1><%= title %></h1>
   <p><%= msg %></a>
   <p><a href="/helo?p1=hello&p2=bye">link</a></p>
 </body>

routes/helo.js

var express = require('express');
var router = express.Router();
 
/* GET helo page. */
router.get('/', function(req, res, next) {
    var p1 = req.query.p1;
    var p2 = req.query.p2;
    var msg = p1 == undefined ? "" : p1 + "," + p2;
    res.render('helo', 
        {
            title: 'HELO Page',
            msg: msg
        }
    );
});
 
module.exports = router;
app.get('/helo', helo.helo);

지난번 작성한 것처럼, views 폴더 안에 helo.ejs, routes 폴더 안에 helo.js을 배치하고, app.js 안에 이것들을 이용하기 위한 코드를 추가한다.

여기에서는 href="/helo?p1=hello&p2=bye으로 <a> 태그 링크를 작성하고 있다. 그리고 /helo 액세스 처리는 다음과 같이 쿼리 문자열에서 값을 얻어서 사용하고 있다.

var p1 = req.query.p1;
var p2 = req.query.p2;
var msg = p1 == undefined ? "" : p1 + "," + p2;

쿼리 문자열이 전달되지 않은 경우, p1과 p2의 값은 undefined이다. undefined이 아니면, 쿼리 문자열에 어떤 값이 전달되어 있다고 생각해도 좋을 것이다.

 

여기의 예제는 express 명령으로 Web 어플리케이션을 생성하고 이용하는 것을 전제로 작성하고 있다. express 명령을 사용하여 어플리케이션의 생성에 대해서는 다음을 참조한다.

express-generator 설치




Ajax으로 액세스한 정보를 JSON으로 받기

최근에는 폼을 사용하지 않고 Ajax으로 필요한 정보를 서버로부터 받는 경우가 많아지고 있다. 폼보다 이것을 사용하는 것 기억해 두는 편이 좋을지도 모릅니다.

Ajax에서 액세스는 기본적으로 클라이언트에서 JavaScript로 실시하기 때문에, 액세스에 대한 처리를 Node.js의 템플릿과 스크립트를 준비할 필요는 없다. 일반적인 Ajax와 마찬가지로 Ajax 스크립트를 적 작성하면 된다.

다만, 액서스한 측(즉, Ajax에서 액세스되었을 때 서버 측의 처리)에 대해 생각해야 하는 필요가 있다. 단순히 필요한 정보를 내 보낼 정도라면 Response의 "send"를 호출하면 된다.

"Response".send(출력 값);

단지 이것만으로 텍스트나 템플릿의 내용을 클라이언트에 다시 보낼 수 있다. 또한 만약 JSON을 사용하여 데이터 반환을 하고자 한다면, "json" 메소드가 편리하다.

"Response".json(JSON 형식의 데이터);

이것으로 JSON 데이터를 써서 보낼 수 있다. 수신 측에서는 텍스트를 JSON.parse하고 처리하며 된다.

그럼 이도 사용할 예를 들어 둔다.

routes/helo.js

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

/* GET helo page. */
router.get('/', function(req, res, next) {
   var p1 = req.query["p1"];
   var p2 = req.query.p2;
   var msg = p1 == undefined ? "" : p1 + "," + p2;
   res.render('helo', 
       {
           title: 'HELO Page',
           msg: msg,
           input: ''
       }
   );
});

/* POST helo page. */
router.post('/', function(req, res, next) {
   var str = req.query.input1;
   res.json(
       { msg: str }
   );
});

module.exports = router;

views/helo.ejs

<!DOCTYPE html>
<html>
   <head>
   <title><%= title %></title>
   <link rel='stylesheet' href='/stylesheets/style.css' />
   <script type="text/javascript">
   var ajax = null;
 
   function doAction(){
       var input = document.getElementById("input1");
       var callback = function(){
           var target = document.getElementById("msg");
           var res = JSON.parse(ajax.getResponse());
           target.textContent = "you send::" + res.msg;
       }
       ajax = new AjaxObject('/helo?input1=' + 
           input.value,callback);
   }

   function AjaxObject(url,callback){
       var response = null;
       var callback = callback;
         
       ajaxStart(url);

       this.getResponse = function(){
           return response;
       }
         
       function ajaxStart(url){
           var req =  createRequest();
           if (req == null){
               alert("実行できません!");
               return;
           }
           req.open("POST",url);
           req.setRequestHeader
               ("User-Agent","XMLHttpRequest");
           req.onreadystatechange = function(){
               if (this.readyState == 4 && 
                       this.status == 200){
                   precallback(this);
               }
           }
           req.send();
       }
         
       function createRequest(){
           var httplist = [
               function(){
                   return new XMLHttpRequest();
               },
               function(){
                   return new ActiveXObjct
                       ("Msxml2.XMLHTTP");
               },
               function(){
                   return new ActiveXObject
                       ("Microsoft.XMLHTTP");
               }
           ];
           for(var i = 0;i < httplist.length;i++){
               try {
                   var http = httplist[i]();
                   if (http != null) return http;
               } catch(e){
                   continue;
               }
           }
           return null;
       }
         
       function precallback(request){
           response = request.responseText;
           callback();
       }
   }
   </script>
</head>
<body onload="init()">
   <h1><%= title %></h1>
   <p id="msg"><%= msg %></a>
   <form id="form1" name="form1" method="post" action="/helo"
           onsubmit="return false;">
       <input type="text" id="input1" name="input1"
           value="<%= input %>">
       <input type="button" id="btn" value="click"
           onclick="doAction();">
   </form>
</body>
</html>

방금 전에 예제을 사용하여, helo에서 Ajax 통신에 액세스하고, JSON으로 보내진 값을 받아 표시해 본다. 아까와 마찬가지로 폼의 입력 필드에 뭔가 쓰고 버튼을 클릭하여 본다. 페이지 이동하지 않고 그 자리에서 표시가 업데이트된다.

helo.js의 router.post에서 전송된 정보에서 input1의 값을 얻고, 그것을 JSON 데이터를 클라이언트에게 돌려 보내고 있다. 다음 부분이다.

var str = req.query.input1;
res.json(
    { msg: str }
);

query는 쿼리 문자열을 정리하고 있는 속성이다. 거기에서 input1의 값을 꺼내, 그리고 Request의 'json'메소드로 전송한다. 전송하는 데이터는 연관 배열의 형태로 인수 준비한다. 이것으로 변수 str의 값이 msg라는 키에 보내진다. 이 후에는 Ajax 통신의 콜백 함수로 JSON 형식의 데이터를 받아 처리를 해주고 있다.

var callback = function () {
    var target = document.getElementById ( "msg");
    var res = JSON.parse (ajax.getResponse ());
    target.textContent = "you send :"+ res.msg;
}

JavaScript의 JSON.parse으로 Ajax 통신으로 받은 결과에서 getResponse의 값을 파싱하고 거기에서 msg 값을 꺼낸다. JSON을 사용하면 필요에 따라 여러 개의 값을 서버에서 받을 수 있어서 매우 편리하다. 여기서는 msg뿐이지만, 서버 측에서 보내는 값을 만들면, 여러 개의 값을 함께 보낼 수 있다.



Express에서 쿠키 이용

약간의 데이터 저장에 많이 이용되는 것이 "쿠키"이다. Express는 쿠키를 이용하기 위한 기능이 준비되어 있다. 그 사용법을 설명한다.

Express에서 쿠키

라이브러리로드

"Application".use (express.cookieParser());

쿠키를 이용하면 express의 "cookieParser"을 로드해야 한다. 이걸로 쿠키 정보를 객체로 취급할 수 있게 된다.

쿠키의 저장

"Response".cookie(이름, 값 [,옵션]);

쿠키를 저장하는 경우, Response의 cookie 메소드를 호출한다. 값에 붙이는 이름과 텍스트를 지정하면된다. 세번째 인수에 옵션 설정을 연관 배열로 사용할 수 있다. 예를 들어 쿠키의 유효 기간을 expires 값으로 갖게 할 수 있다.

쿠키의 얻기

"Request".cookies.이름

쿠키의 값은 cookieParser 의해 Request의 "cookies"속성으로 저장된다. 이 중에 쿠키 이름의 속성이 만들어지고 거기에 각각의 값이 저장된다.

그럼 다음에 간단한 사용 예제를 올려 두었다.

routers/helo.js

var express = require('express');
var router = express.Router();
 
/* GET helo page. */
router.get('/', function(req, res, next) {
    var str;
    try {
        str = req.cookies.lastdata;
    } catch(e){}
    res.render('helo', 
        {
            title: 'HELO Page',
            msg: 'please type...',
            cookie: "last:" + str,
            input: ''
        }
    );
});
 
/* POST helo page. */
router.post('/', function(req, res, next) {
    var str = req.body.input1;
    res.cookie("lastdata",str,
        { expires: new Date(Date.now() + 600000)});
    res.render('helo', 
        {
            title: 'HELO Page',
            msg: "you typed: " + str,
            cookie: str,
            input: str
        }
    );
});
 
module.exports = router;

views/helo.ejs

<!DOCTYPE html>
<html>
   <head>
   <title><%= title %></title>
   <link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body onload="init()">
   <h1><%= title %></h1>
   <p><%= msg %></a>
   <p><%= cookie %></p>
   <form id="form1" name="form1" method="post" action="/helo">
       <input type="text" id="input1" name="input1"
           value="<%= input %>">
       <input type="submit" value="click">
   </form>
</body>
</html>

앞전에 /helo를 사용한 예제를 그대로 사용하고 있다.

/helo 폼에 뭔가 쓰게 보내면 그것이 쿠키에 저장된다. 이후, /helo에 액세스하면 저장되어 있던 값이 표시되도록 한다. 여기에 10분 동안 쿠키를 보관할 수 있도록되어 있지만, expires를 조정하여 좀 더 오래 보관 할 수 있다.

여기에서는 helo.js의 router.post에 res.cookie를 사용하여 쿠키를 저장하고, router.get에서 req.cookies.lastdata에서 쿠키 값을 얻어 표시를 수행하고 있다. 쿠키의 사용은 이렇게 Express를 사용하면 대단히 쉽게 할 수 있다.

Express에는 Web 어플리케이션을 자동 생성하는 편리한 기능이 있다. 이것을 사용하여 빠른 개발에 대해 설명한다.



express-generator 설치

지난번 Express를 이용한 Web 어플리케이션의 극히 초보적인 것을 만들었다. 그래서 "결국, 모든 파일을 직접 만드는 것이 별로 유용하지 않다"고 느낄지도 모른다.

Express 생성 프로그램 설치

우선 "기본부터"라고 하여 최소한의 파일을 만들어 동작시켰지만, 사실은 그렇게 하지 않아도 Express의 Web 응용 프로그램은 만들 수 있다. Express에는 Web 응용 프로그램의 기본 파일을 자동 생성하는 기능이 있어서 이를 사용하면 Web 응용 프로그램 작성을 거의 자동화 할 수 있다.

그러기 위해서는, express-generator의 준비가 되어 있어야 한다. 지난번 간단히 언급했지만 Express는 ver.3에러 ver. 4.7로 업데이트되면서 패키지가 바뀌었다. 아마도 대부분은

$ npm install -g express

이러한 식으로 설치되어 있다고 생각된다. 그래도 현시점에서는 문제가 없지만, ver.4가 출시됨에 따라 자동 생성 기능은 Express Generator라는 프로그램에 분리되었다. 다음과 같이 패키지를 설치한다.

$ npm install -g express-generator

이것으로 ver. 4 이후의 응용 프로그램 생성 기능을 사용할 수 있다.



Express 명령으로 Web 어플리케이션 생성

그럼 Web 응용 프로그램을 만들 수 본다. 아래 순서대로 작업한다.

1. 설치 폴더 만든다.

먼저 Web 응용 프로그램 폴더를 어딘가에서 준비한다. 여기에서는 적당한 곳에 "exapp"라는 폴더를 만들어 둔다. 이 안에 Express의 Web 응용 프로그램을 작성해 나갈 것이다.

2. 명령 프롬프트으로 작업 폴더로 이동한다.

명령 프롬프트(Mac OS X의 터미널)을 시작하고 만든 작업 폴더(여기에서는 "exapp"폴더)가 있는 곳으로 cd 명령 사용하여 이동한다. 예를 들어, 특정 폴더에 "exapp"폴더가 있다면,

$ cd [특정 폴더]

이렇게 하면 된다. 주의할 점은 "exapp 폴더 안으로 이동하면 안된다"는 점이다. 즉, "cd [해당 폴더]\exapp"이렇게 하면 된다. exapp이 있는 상위 폴더로 이동한다.

3.Express 명령을 실행한다.

그럼 Express 명령으로 Web 애플리케이션을 생성한다. 이는 "express 폴더명과 같은 실행한다. 단, 이렇게 하면 템플릿 엔진에 "jade"라는 것이 설정된다. 아무튼, 이대로 라도 괜찮지만(또한, jade는 템플릿 엔진을 기억하면 된다), 아무래 이미 사용 경험이 있는 EJS을 사용하여 만드는 것이 편할 것이다.

EJS을 템플릿 엔진으로 설정하는 경우에는 "-e"옵션을 지정하고, "express -e 폴더명"과 같이 실행한다. 여기에서 예를 들면, "exapp"폴더에 파일을 생성할 것이기에,

$ express -e exapp

이와 같이 실행한다. 이것으로 필요한 파일들이 "exapp"폴더에 만들어 졌다. "-e"를 붙이지 않으면 생성되는 템플릿 파일 등이 달라지므로 주의해야 한다.

4. npm install을 실행한다.

이것으로 끝이 아니다. 이어서 cd 폴더명 && npm install명령을 실행한다. 여기에서 예를 들자면, 다음과 같이 실행한다.

cd exapp && npm install

디렉토리 이동하는 명령인 cd로 "exapp 폴더로 이동하고, npm install을 실행하는 작업을 하고 있다. 그렇지 않으면, 필요한 파일들이 모이지 않기 때문에, 배포 시에 응용 프로그램이 잘 동작하지 않는다.

이것으로 Web 응용 프로그램은 완성되었다!

Web 어플리케이션 구성

그럼 만든 Web 응용 프로그램의 구성을 살펴보자. 설치한 폴더(여기서는 "exapp"폴더) 안에는 다음과 같은 파일과 폴더가 만들어지고 있다.

bin 폴더

실행하는 프로그램이 저장되어 있는 곳이다. 이 폴더에는 'www'라는 파일이 하나 있다. 이 응용 프로그램의 실행 스크립트 파일이다.

node_modules 폴더

Node.js에서 사용하는 모듈(Express 등 각종 프로그램과 거기에 필요한 파일)들이 모두 여기에 정리되어 있다. 사용자가 이 안에 있는 파일을 조작할 은은 거의 없다.

public 폴더

이 폴더은 공개 파일을 설치하는 곳이다. 예를 들어, 이미지 파일 및 JavaScript 라이브러리, 스타일시트 파일 등을 여기에 정리한다. 여기에 배치하면 Express에서 사용할 수 있다.

routes 폴더

여기에 있는 파일들은 각각의 Web 페이지에서 처리할 스크립트 파일들이다. Express가 생성한 Web 어플리케이션에서는 스크립트 관련의 주요 처리 부분과 개별 페이지의 처리가 별도의 폴더로 나누어져 있다. 여기에는 각각의 페이지 처리가 보관되어 있다.

views 폴더

여기는 앞전에 설명했듯이, 페이지 템플릿 파일들을 정리된다. 화면 표시를 위한 필요한 파일들이 모두 이 안에 준비된다.

app.js 파일

이 파일은 메인 프로그램이 되는 스크립트 파일이다. Node.js으로 프로그램을 실행할 때에 이 파일은 지정하지 않는다. 앞전에 bin 폴더에 있는 www에서 app.js를 호출 실행한다.

package.json 파일

이 파일도 앞전에 설명했다. Web 어플리케이션의 패키지 정보를 기술한 파일이다.

 

대략 전체의 구성을 정리되었다. 이를 바탕으로 Web 어플리케이션을 작성하는 경우에는 "app.js에서 메인 프로그램 수정", "routes에 만드는 페이지의 스크립트 파일을 준비", "views에 표시용 템플릿 파일을 준비"와 같은 흐름으로 만들어 가면 된다.

조금 이해하기 어려운 것은 bin 폴더에 있는 www와 app.js의 차이이다. Express Application Generator로 생성되는 프로그램은, 어플리케이션의 기동 스크립트 및 메인 스크립트가 나뉘고 있다. www는 프로그램을 시작하기 위한 스크립트이고, app.js이 응용 프로그램의 메인 프로그램이다.

흐름을 알게 되면, 명령 프롬프트에서 "exapp"폴더로 이동하여 다음과 같이 실행한다.

$ node .\bin\www

이걸로 서버 프로그램이 실행된다. 브라우저에서 http://localhost:3000 에 액세스하면 간단한 페이지가 표시된다. Express으로 자동 생성한 Web 어플리케이션은 디폴트로 포트 번호 3000이 지정되기 때문에 주의하도록 한다.




메인 프로그램 app.js

그럼 작성된 Web 응용 프로그램을 살펴 보자.

메인 프로그램 "app.js"에 대해

우선 메인 프로그램인 app.js부터 보도록 하겠다. 파일 내용은 아래와 같다.

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
 
var routes = require('./routes/index');
var users = require('./routes/users');
 
var app = express();
 
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
 
// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
 
app.use('/', routes);
app.use('/users', users);
 
// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});
 
// error handlers
 
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}
 
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});
 
module.exports = app;

포인트를 골라서 설명해 나가도록 하겠다.

1. 라이브러리 로드

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');

먼저 수행하는 것은 라이브러리 로드이다. 각종 라이브러리를 로드하고 다음과 같은 변수에 저장한다.

변수설명
expressExpress 객체의 변수이다.
pathPath 객체이다.
serve-faviconfavicon (Web 페이지에 표시되는 아이콘 데이터)에 관한 것이다.
morgan로그 출력에 대한 객체이다.
cookie-parser쿠키 이용에 관한 객체이다.
bodyParser바디 부분의 파싱과 관련된 객체이다.
routesroutes 폴더에 설치되어 있는 index.js의 객체이다. 이 내용은 나중에 설명하겠다.
userroutes 폴더에 설치되어있는 user.js의 개체입니다. 이것도 나중에 설명하겠다.

마지막 2개는 모듈이 아닌 어플리케이션에 포함되어 있는 스크립트 파일을 로드하는 것이다. require으로, 이런 식으로 다른 스크립트를 로드할 수 있다.

충분히 다양한 모듈이 로드되어 있지만, 사실은 로드되는 것은 이뿐만이 아니다. 어플리케이션을 기동하기 위한 www에 다음과 같은 문장이 작성되어 있다.

var app = require('../app');
var debug = require('debug')('exapp:server');
var http = require('http');

이것으로 이 app.js 스크립트 디버깅에 대한 개체, 그리고 http 객체가 제공된다. 이 모든 것이 갖추어져 어플리케이션을 구축하고 있다.

2. express 함수로 Application 만들기

var app = express();

express 함수로 Application 개체를 만든다. 이것은 이전과 동일하다.

3. 각종 어플리케이션 설정하기

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

다음에는 app.set ~ 및 app.use ~ 와 같은 코드가 정렬되어 있는데, 이것은 각종 어플리케이션의 설정을 수행하고있는 부분이다. 이들은 우선 사용자가 이를 편집하는 일은 없을 것이다. 작성을 변경되거나 하지 않도록 주의하자.

4. get으로 루트 설정하기

app.use('/', routes);
app.use('/users', users);

get으로 '/'와 '/users' 주소에 각각 routes과 users 변수를 설정하고 있다. 이 routes 및 users 변수는 이전에 나온 것이다. routes 폴더에 있는 index.js와 users.js을 각각 require으로 로드한 것을 얻은 변수였다.

이러한 변수를 use로 설정하여, 지정된 주소에 스크립트를 할당하고 있다. 즉, '/'에 액세스가 있으면 routes 안에 index.js가, '/users'에 액세스가 있으면 users.js가 각각 실행되도록 되는 것이다.

5. 에러 처리 설정

app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});

app.use, if문 또한 app.use 계속되지만, 이러한 에러에 대한 처리를 수행하는 것이다. 이것도, 특별히 사용자가 만질 부분은 없다. 우선 잊어도 되는 부분이다.

6. 어플리케이션을 설정하기

module.exports = app;

마지막으로 만든 app 객체를 module.exports에 대입한다. 이것으로 어플리케이션이 설치되어 동작하게 된다.

도중에 갑자기 어려워 보이는 처리가 나와하기도 했지만, 모든 이해할 필요는 없다. 전체의 흐름으로써, 대충 "이런 식으로 처리가 되어 있구나" 정도로 파악할 수 있으면 그것으로 충분하다.



routes 스크립트 및 템플릿

이어서 표시하는 Web 페이지의 처리를 보고 가자. Web 페이지는 routes 안에 스크립트와 views 안에 템플릿을 준비하여 양자의 조합으로 만들어 진다. 여기에서는 /index로 표시되는 페이지를 구성하는 routes 안에 index.js와 views의 index.ejs의 내용을 살펴 보자. (아래와 코드 참조)

routes/index.js

var express = require('express');
var router = express.Router();
 
/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});
 
module.exports = router;

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>

express 라이브러리 로드

var express = require('express');

여기에 필요한 express 라이브러리를 로드한다. 이는 app.js에 대해 설명 시에도 등장 했다. Express 객체이다.

Router 객체의 준비

var router = express.Router();

express의 "Router"메소드를 호출한다. 이것으로 라우팅(URL과 그것을 호출하는 처리와 연관 얻을 수있는 것)에 대한 객체를 만든다.

GET으로 루트 정보를 설정하기

router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

Router에도 app.js의 express 객체와 마찬가지로 get 메소드가 준비되어 있다. 이것으로 URL 경로에 함수를 설정한다. 이것으로 지정된 주소에 액세스가 있으면 설정한 함수를 호출하여 실행하게 된다.

여기에서 render 메소드를 호출하여 index의 템플릿을 렌더링한다.

exports에 router를 설정

module.exports = router;

마지막으로, module.exports에 router 객체를 사용하고 작업 완료이다. app.js에서 마지막으로 module.exports = app;을 수행하고 있던 것과 같은 거다.

이상, 정리하면 "Router 작성", "get에 의한 경로 설정", "module.exports에 router 설정"이라는 흐름에서 라우팅이 이루어지고 있었다. 여기에서는 index에 대해 조사했지만, 기본적으로는 모두 같은 방식으로 라우팅을 설정한다. 라우팅을 위한 스크립트는 자신의 새로운 페이지를 추가하여 준비해야 하기에, 작성법의 기본은 제대로 기억해 두도록 한다.



새로운 페이지 작성

그럼, 이 기본적인 구조를 사용하여 새 페이지를 만들어 보자. 이번에는 /helo를 접근하여 표시되는 페이지를 만들려고 한다. 필요한 것을 정리한다.

routes 안에 helo.js

/helo의 처리를 기술하는 스크립트 파일로, routes 폴더 안에 helo.js 파일을 작성한다.

var express = require('express');
var router = express.Router();
 
/* GET helo page. */
router.get('/', function(req, res, next) {
    res.render('helo', {
        title: 'Helo',
        data: {
            '성진':'sungjin@foryou',
            '원석':'wonsuck@flower',
            '병호':'byeongho@devkuma.com'
        }
    });
});
 
module.exports = router;

이 중에 router.get으로 '/'렌더링하는 처리를 설정해 두었다. 그리고 간단한 샘플로 title와 data를 템플릿에 전달하여 처리하도록 하고 있다.

views 안에 helo.ejs

/helo으로 표시되는 템플릿 파일이다.

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <table>
    <% for(var key in data){ %>
        <tr><td><%= key %></td>
        <td><%= data[key] %></td></tr>
    <% } %>
    </table>
  </body>
</html>

여기에서는 <%= title %>으로 title을 출력하는 문장과 변수 data 안에 키와 값을 <table>에 정리하여 출력하는 문장을 기술하고 있다.

app.js

메인 프로그램인 app.js 파일이다.

var helo = require('./routes/helo');
 
app.use('/helo', helo);

이 안에는 helo = require('./routes/helo')으로 helo.js의 exports를 변수 helo에 대입하고, app.use('/helo', helo);으로 /helo 액세스 처리를 helo 객체의 helo 메소드로 설정하고 있다.

public의 stylesheets 폴더 안에 style.css

마지막으로 <table> 스타일 시트를 추가한다. 스타일시트는 "public"의 "stylesheets"폴더에 저장된다.

table tr td{
    background-color:#eeeeff;
    padding: 3px;
}

이 안에 'style.css'를 열고, <table>의 스타일을 기술해 두면 된다.

 

대충 편집이 끝나면, Node.js에서 app.js를 실행하고, http://localhost:3000/helo에 방문해 본다. 만든 페이지가 제대로 표시되는지 확인한다.



Node.js으로 효율적인 Web 어플리케이션을 개발한다면, 이를 위한 프레임워크를 도입하는 것이 가장 좋다. 여기에서는 Node.js의 Web 어플리케이션 프레임워크 간판이라고 할 수있는 "Express"를 설치하고 이용해 보자.


프레임워크 장점과 Express

지금까지 Web 어플리케이션의 기본적인 기능에 대한 처리 방법에 대해 설명하였다. 그런데 솔직히 말하면 "생각보다 귀찮다"고 느꼈을 수도 있다.

프레임워크의 필요성

어쨌든, 무엇을 해야 할때도 모든 것을 세세하게 스스로 처리를 쓰지 않으면 안된다. EJS을 도입하여 템플릿을 사용할 수 있게 됐지만, 템플릿 파일을 미리로드해 놓고 그것을 렌더링 처리를 직접 작성해야 한다. 여러 페이지를 준비해야 한다면, 직접 URL 기반의 라우팅 프로세스를 만들어 두지 않으면 안된다. POST로 된 데이터는 이벤트를 사용하여 데이터를 모두 꺼내 와서 그것을 디코드하여 필요한 값을 사용할 수 있도록 이도 또한 처리를 직접 작성해야 한다. 이것으로는 다수의 페이지를 가진 복잡한 Web 응용 프로그램을 만들려는 의욕이 사라질 것이다.

이러한 작업의 대부분은 "Web 응용 프로그램이라면 반드시 필요한 것"이기도 한다. 그렇다면, Web 응용 프로그램이 사용하는 기능을 미리 모두 구현하여, Web 응용 프로그램으로 기본 시스템을 준비해 두면 보다 쉽게 개발을 할 수 있게 될 것이다.

이러한 생각에 탄생한 것이 "Web 어플리케이션 프레임워크"로 일반적으로 불리는 것이다. 단순히 기능을 모은 라이브러리가 아닌 Web 어플리케이션으로서의 기본적인 시스템 자체를 제공하는 프로그램이다.

Express 프레임워크

Node.js에도 이 Web 어플리케이션 프레임워크는 준비되어 있다. 그 중에서도 가장 널리 사용되고 있는 것이 "Express"라는 거다.

Express는 EJS과 동일하게 Node.js 패키지로 제공되고 있다. npm에서 간단히 설치하고, 이를 통합한 서버 프로그램을 만들 수 있다. 또한 템플릿 엔진에 EJS을 이용할 수 있기 때문에, 지금까지 만든 EJS 템플릿을 그대로 재사용할 수 있다.

Express에는 Web 응용 프로그램을 위한 기본적인 시스템이 구축되어 있으며, 이에 필요한 처리를 추가하는 것이다. Node.js에서만의 구현과 동일하게 이벤트 처리를 통합해 간다는 기본적인 개발 스타일은 변함이 없지만, 준비되어 있는 다양한 객체를 이용하여 아주 쉽게 필요한 처리를 조합할 수 있다.

Express 사이트는 아래와 같다. 여기에 문서 등도 공개되어 있다. (단, npm을 이용하기 때문에 여기에서 라이브러리 파일 등을 다운로드할 필요가 없다)

http://expressjs.com/



Express을 이용한 Web 어플리케이션 생성

그러면 실제로 Express를 이용하여 보자. Express를 사용하려면 먼저 npm을 사용하여 설치한다. 먼저 명령 프롬프트(Mac OS X라면 터미널)을 시작한다.

Express를 설치하는 경우, 생각해야 하는 것은 "Express를 어디에 설치 하느냐 "이다. 이는 다음 두 가지가 있다.

Node.js 모듈로 설치

Node.js 본체의 모듈로 저장한다. 이렇게 하면 어느 Web 애플리케이션에서도 사용할 수 있다. 그러나 응용 프로그램을 서버에 배포할 때에 해당 서버에 Express가 없으면 동작하지 않는다.

Node.js 모듈로 설치하는 것은 간단하다. EJS과 같이 명령 프롬프트에서 다음과 같이 실행하면 된다.

$ npm install express

Web 어플리케인션 폴더에 설치

이 방식은 어플리케인션을 만들 때마다 매번 설치해야 한다. 단, 어플리케인션에 모든 파일이 포함되어 있기 때문에 서버에 배포하게 되었을 때, 서버에 Express가 없어도 문제없이 동작한다.

실제로 만든 응용 프로그램을 배포하는 것을 생각한다면, 어플리케이션에 필요한 모듈을 전부 설치하는 것이 좋다. 여기에서는 그 방식으로 간단한 어플리케이션을 만들어 보자. 다음 단계에 따라 작업을 진행하십시오.

1. 응용 프로그램 폴더를 준비한다.

우선은 응용 프로그램을 만들 폴더를 준비한다. 여기에서는 바탕 화면에 "expressapp"라는 폴더를 준비하기로 한다.

명령 프롬프트를 시작하는 경우는, cd로 적당히 설치하고 싶은 폴더로 이동하여 다음과 같이 실행하면 폴더가 생성된다.

mkdir expressapp

2. 명령 프롬프트에서 폴더로 이동

명령 프롬프트(Mac OS X라면 터미널)을 시작한다. 그리고 cd 명령에서 "expressapp"폴더로 이동한다.

3. npm을 초기화

npm을 초기화한다. 이것은 다음과 같이 실행한다.

npm init

실행하면 이름, 버전, 설명문, 엔트리 포인트, 테스트 명령, git 저장소, 키워드, 저자, 라이선스 등을 차례로 묻어 온다. 잘 모르는 경우 전부 그대로 Enter 키를 누른다. 이것으로 package.json 파일이 생성된다.

4. Express 설치

Express를 설치한다. 이것은 다음과 같이 실행한다.

npm install express --save

이제 응용 프로그램의 "expressapp"에 "node_modules"라는 폴더를 만들지고, 그 안에 'express'라는 폴더가 저장된다. 이것이 Express 파일이 저장되는 폴더이다.

5. EJS 설치

이어 Express에서 템플릿 엔진으로 사용하는 EJS을 설치한다. 역시 npm을 사용하여 다음과 같이 실행한다.

npm install ejs

이것으로 "node_modules"폴더에 "ejs"폴더가 만들어지고 거기에 파일이 저장된다.

 

이제 Web 응용 프로그램 작성을 위한 준비가 되었다. 이 후에는 여기에 스크립트 및 템플릿 등 구체적인 코드를 추가하여 응용 프로그램을 구축해 나가면된다.



Express을 이용한 Web 어플리케이션 생성

그러면 실제로 Express를 이용하여 보자. Express를 사용하려면 먼저 npm을 사용하여 설치한다. 먼저 명령 프롬프트(Mac OS X라면 터미널)을 시작한다.

Express를 설치하는 경우, 생각해야 하는 것은 "Express를 어디에 설치 하느냐 "이다. 이는 다음 두 가지가 있다.

Node.js 모듈로 설치

Node.js 본체의 모듈로 저장한다. 이렇게 하면 어느 Web 애플리케이션에서도 사용할 수 있다. 그러나 응용 프로그램을 서버에 배포할 때에 해당 서버에 Express가 없으면 동작하지 않는다.

Node.js 모듈로 설치하는 것은 간단하다. EJS과 같이 명령 프롬프트에서 다음과 같이 실행하면 된다.

$ npm install express

Web 어플리케인션 폴더에 설치

이 방식은 어플리케인션을 만들 때마다 매번 설치해야 한다. 단, 어플리케인션에 모든 파일이 포함되어 있기 때문에 서버에 배포하게 되었을 때, 서버에 Express가 없어도 문제없이 동작한다.

실제로 만든 응용 프로그램을 배포하는 것을 생각한다면, 어플리케이션에 필요한 모듈을 전부 설치하는 것이 좋다. 여기에서는 그 방식으로 간단한 어플리케이션을 만들어 보자. 다음 단계에 따라 작업을 진행하십시오.

1. 응용 프로그램 폴더를 준비한다.

우선은 응용 프로그램을 만들 폴더를 준비한다. 여기에서는 바탕 화면에 "expressapp"라는 폴더를 준비하기로 한다.

명령 프롬프트를 시작하는 경우는, cd로 적당히 설치하고 싶은 폴더로 이동하여 다음과 같이 실행하면 폴더가 생성된다.

mkdir expressapp

2. 명령 프롬프트에서 폴더로 이동

명령 프롬프트(Mac OS X라면 터미널)을 시작한다. 그리고 cd 명령에서 "expressapp"폴더로 이동한다.

3. npm을 초기화

npm을 초기화한다. 이것은 다음과 같이 실행한다.

npm init

실행하면 이름, 버전, 설명문, 엔트리 포인트, 테스트 명령, git 저장소, 키워드, 저자, 라이선스 등을 차례로 묻어 온다. 잘 모르는 경우 전부 그대로 Enter 키를 누른다. 이것으로 package.json 파일이 생성된다.

4. Express 설치

Express를 설치한다. 이것은 다음과 같이 실행한다.

npm install express --save

이제 응용 프로그램의 "expressapp"에 "node_modules"라는 폴더를 만들지고, 그 안에 'express'라는 폴더가 저장된다. 이것이 Express 파일이 저장되는 폴더이다.

5. EJS 설치

이어 Express에서 템플릿 엔진으로 사용하는 EJS을 설치한다. 역시 npm을 사용하여 다음과 같이 실행한다.

npm install ejs

이것으로 "node_modules"폴더에 "ejs"폴더가 만들어지고 거기에 파일이 저장된다.

 

이제 Web 응용 프로그램 작성을 위한 준비가 되었다. 이 후에는 여기에 스크립트 및 템플릿 등 구체적인 코드를 추가하여 응용 프로그램을 구축해 나가면된다.



Express을 이용한 기본 스크립트

그럼 Express를 이용한 응용 프로그램의 스크립트를 작성한다. 먼저 Express를 사용하는 가장 기본적인 스크립트를 생각해 본다.

기본 스크립트 작성

텍스트 편집기를 사용하여, 아래의 스크립트를 작성한다. 그리고 앞전에 "expressapp"폴더에 "app.js"라는 이름으로 저장한다.

var express = require('express');
var app = express();
 
app.get('/', function(req, res){
    res.send('Hello World!');
})
 
var server = app.listen(3000, function(){
    console.log('Server is running!');
})

저장되면, 명령 프롬프트에서이 app.js가 있는 폴더( "expressapp"폴더)로 이동하여,

node app.js

이렇게 node 명령을 실행한다. 그리고 브라우저에서 다음을 방문해 본다.

http://localhost:3000/

화면에 "Hello World!"로 텍스트가 표시된다. 아직 템플릿도 아닌, 단지 텍스트를 출력하고 있는 만큼 간단한 예제이지만, "Express를 사용하는 응용 프로그램"의 기본 스크립트는 완성 되었다.

Express 기본 처리 정리

이 예제에서는 Express를 이용하기에 기존의 스크립트는 작성법이 변경 되었다. 그럼 포인트를 정리한다.

Express를 로드한다.

var express = require('express');

먼저 Express 라이브러리를 로드한다. 이는 require('express')으로 수행할 수 있으며, 로드된 변수 express를 사용하여 Express 기능을 이용할 수 있다.

Express Application 객체 만든다.

var app = express();

최초에 실행하는 것은 애플리케이션의 객체를 준비이다. 이는 express 함수를 호출한다. 이 함수는 Express 애플리케이션인 'Application' 객체를 생성하고 반환한다.

GET 등록하기

app.get("/", function(req, res){……중략……});

이어서 GET 등록을 수행한다. 이는 HTTP의 GET에 의한 액세스를 등록한다. 첫번째 인수에는 액세스하는 경로를 두번째 인수에는 실행하는 함수를 각각 지정한다. 이것으로 첫번째 인수의 경로에 액세스가 있었을 때, 두번째 인수의 함수를 호출을 하게 된다.

두번째 인수의 콜백 함수는 request와 response 객체가 각각 인수로 전달된다. 이는 이미 잘 알고 있는 요청 및 응답 객체가 전달된다.

이 get 메소드에 필요한 만큼 액세스하는 경로에 함수를 등록해 나가면, 얼마든지 페이지를 추가해 나갈 수 있다. 이걸로 귀찮은 라우팅 처리 등이 필요 없어지게 된다.

포트를 수신(listen)한다.

app.listen(3000, function(){……중략……});

마지막으로, 포트 번호를 지정하여 "listen"를 호출한다. 이것으로 지정 포트 번호로 대기 상태를 시작한다. 콜백 함수로 수신 시작 후의 처리를 준비할 수 있다.

이것으로 서버 시작 처리가 완료된다. 이 후에는 어디선가 지정된 경로에 액세스가 있으면, 그 처리가 실행되게 된다.

Node.js 자체로 만들었던 것과 비교하면, 충분히 깔끔한 스크립트가 되었다. Express를 사용하면 충분히 효율적으로 Web 어플리케이션을 만들 수 있다.



Express에서 템플릿 사용

우선, Express를 이용한 기본적인 처리의 흐름은 대체로 알 수 있었다. 이어서 템플릿을 이용하여 표시을 해 보도록 한다. 템플릿 파일을 준비하고, app.js 스크립트를 수정하면 가능할 것이다.

템플릿 준비

먼저 템플릿부터 작성한다. 이번에는 "test.ejs"라는 파일명으로 생성할 수 있다. 아래에 소스 코드를 올려 두었다.

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title><%=title %></title>
    <style>
    body { font-size:12pt; color:#000066; }
    h1 { font-size:18pt; background-color:#DDDDFF; }
    pre { background-color:#EEEEEE; }
    </style>
    </head>
<body>
    <header>
        <h1><%=title %></h1>
    </header>
    <article>
        <%-content %>
    </article>
</body>
</html>

이번에 작성한 test.ejs는 Web 어플리케이션에 그대로 배치는 하지 않는다. Web 어플리케이션의 폴더에 새로운 "views"라는 폴더를 준비한다. 그리고 이 폴더에 test.ejs을 넣어 둔다.

Express에는 화면 표시 관련 템플릿은 이렇게 "views"폴더에 정리 된다(설치 장소 및 폴더 이름은 변경할 수 있지만, 디폴드로 views라는 폴더로 되어서 여기에서는 그대로 작성한다).

만든 템플릿은 기본적으로 앞전에 EJS 템플릿과 거의 동일하다.

여기에서는 헤더 부분과 바디에 <%=title %>라는 제목을 출력하는 태그를 준비하고, 컨텐츠를 표시하기 위해 <%-content %>라는 태그를 준비하고 있다. Node.js의 스크립트에는 이러한 태그로 출력하는 title과 content 같은 변수에 값을 전달하는 방식으로 처리를 준비하면 된다.

템플릿 이용한 처리

그럼 Node.js 스크립트를 작성해 보도록 한다. app.js를 아래과 같이 작성한다.

var express = require('express');
var ejs = require("ejs");
  
var app = express();
  
app.engine('ejs', ejs.renderFile);
  
app.get("/", function(req, res){
    res.render('test.ejs',  {title: 'Test Page',  content: 'this is test.'});
});
  
var server = app.listen(3000, function(){
    console.log('Server is running!');
})

다시 "node app.js"에서 서버를 시작하고, localhost:3000에 액세스해 본다. 템플릿을 렌더링한 표시가 나타날 것이다.

템플릿 이용 포인트

이것으로 EJS을 사용한 템플릿 기능이 Express에서도 제대로 사용할 수 있게 되었다. 그럼 수정 사항을 체크해 보겠다.

EJS 로드

var ejs = require("ejs");

우선 먼저 require으로 EJS을 로드해야 한다. 이것은 이미 익숙한 작업이기 때문에 알것이다.

템플릿 엔진 설정

app.engine('ejs', ejs.renderFile);

템플릿 엔진을 설정한다. 이것은 Application 객체의 "engine"메소드를 사용하여 설정한다. 첫번째 인수는 템플릿 파일의 확장명을 두번째 인수에는 템플릿 엔진의 콜백 함수를 설정한다. 이 콜백 함수는 ejs 객체의 renderFile 프로퍼티를 설정한다. 이것으로 템플릿 엔진의 설정이 완료되었다.

GET 콜백 함수 준비

콜백 함수안에서 수행하고 있는 것은 EJS 템플릿을 사용한 페이지 표시이다. response의 render 메소드를 호출하고 있다.

res.render( 템플릿 파일, 객체 );

첫번째 인수에 사용하는 템플릿 파일 이름, 두번째 인수에는 템플릿에 전달할 변수 이름을 키로 값을 연관 배열로 정리하여 결합을 지정한다. 이것으로 페이지가 렌더링된다.

주의할 것은 템플릿 파일 이름이다. 이것은 "views"폴더에 저장되어 있어야 한다. 여기서 "views/test.ejs"와 같은 식으로 views 폴더의 경로를 쓸 필요는 없다. 다만 "test.ejs" 파일 이름만 지정하면 된다.

Express는 자동으로 "views"폴더에서 파일을 검색할 수있게 되어있는 것이다. 반대로, "views" 폴더가 아닌 어플리케이션 폴더안에 파일이 있거나 하면 발견되지 않고 오류가 발생되므로 주의한다.

자, 이것으로 우선 Express를 사용한 응용 프로그램의 기본은 알았다. 사용법만 알면, Node.js 단독으로 사용하는 것보다 전체가 정리되어 알기 쉬워진다.

서버 프로그램이라는 것은, 클라이언트에서 보낸 요청을 받아 처리하는 것이다. 기본적인 처리로 "여러 페이지의 라우팅(routing)"와 "폼이 POST 전송 처리"에 대해 설명한다.



여러 페이지의 라우팅 개념

Node.js으로 간단한 페이지를 표시할 수 있게 됐다. 그런데, 한 페이지만 표시가 된다면, 실제로 사용할 수가 없다. 일반 Web에서는 더 다양한 요청에 처리를 해야 한다.

여러 페이지 작성

우선은 "복수의 페이지"부터 생각해 보자. 보통 Web이라는 것은 여러 페이지가 있다. Node.js에서 여러 페이지를 표시하려면 어떻게해야 하나?

여기에서는 전회에 사용한 EJS라는 템플릿 엔진을 사용하여 생각을 해보록 한다(사용하지 않아도 개념은 같지만 ...). 기본적인 개념은 매우 간단하다. 여러 페이지를 이용하려면, 먼저 그 페이지를 미리 로드해 두고, 요청에 따라 어떤 페이지를 렌더링하여 표시할지 여부를 결정하면 된다.

그럼 "어느 페이지를 표시할지"를 클라이언트에서 어떻게 전달할 수 있는가? 보통의 Web 사이트에서는 URL에 의해 그것은 전달할 수 있다. http://xxx/index라면 index 페이지를 http://xxx/helo라면 helo 페이지를...... 이와 같은 식이다.

보통의 Web 서버에서는 자동으로 해당 HTML 페이지를 로드하여 반환하지만, Node.js의 경우 프로그램 중에 이러한 처리를 해야 한다. 보내 온 URL에서 도메인 이후의 부분을 얻어서 그 값에 의해 표시하는 페이지를 바꾸는 등의 작업을 생각할 수 있다.

그럼 실제로 해보자. 먼저, 아래 준비로 표시에 사용하는 EJS 템플릿 파일을 준비해 둔다. 여기에는 페이지 전체의 레이아웃이 된다. "template.ejs"(이전까지 hello.ejs라는 파일명으로 사용하고 있었던 파일이다)과 실제 표시되는 컨텐츠가 되는 'content1.ejs", "content2.ejs"까지 총 3개의 템플릿 파일을 준비하록 한다.

template.ejs

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta http-equiv="content-type"
        content="text/html; charset=UTF-8">
    <title><%=title %></title>
    <style>
    body { font-size:12pt; color:#006666; }
    h1 { font-size:18pt; background-color:#AAFFFF; }
    pre { background-color:#EEEEEE; }
    </style>
</head>
 
<body>
    <header>
        <h1 id="h1"><%=title %></h1>
    </header>
    <div role="main">
        <p><%-content %></p>
    </div>
</body>
 
</html>

content1.ejs

<h2>예제로 작성한 컨텐츠입니다.</h2>
<p><%= message %></p>
<hr>
<p><a href="/other">other pageへ</a></p>

content2.ejs

<p>다른 페이지의 컨텐츠이다.</p>
<p><%= message %></p>
<p> </p>
<p><a href="/">돌아가기</a></p>

이 파일들을 사용하는 두 페이지를 표시하는 스크립트를 작성한다.

url개체를 사용하여 URL 처리

그럼, 준비한 템플릿을 사용하는 스크립트를 작성한다. 이것은 앞에 스크립트를 보고나서 설명을 하는 편이 빠를 것이다.

아래에 Node.js 스크립트 예제는 아래와 같다.

var http = require('http');
var fs = require('fs');
var ejs = require('ejs');
var url = require('url');
 
var template = fs.readFileSync('./template.ejs', 'utf8');
var content1 = fs.readFileSync('./content1.ejs', 'utf8');
var content2 = fs.readFileSync('./content2.ejs', 'utf8');
 
var routes = {
    "/":{
        "title":"Main Page",
        "message":"이것을 예제 페이지입니다.",
        "content":content1},
    "/index":{
        "title":"Main Page",
        "message":"이것을 예제 페이지입니다.",
        "content":content1},
    "/other":{
        "title":"Other Page",
        "message":"다른 페이지 표시하고 있다.",
        "content":content2}
};
 
var server = http.createServer();
server.on('request', doRequest);
server.listen(1234);
console.log('Server running!');
 
// 요청 처리
function doRequest(request, response) {
    var url_parts = url.parse(request.url);
    // route check
    if (routes[url_parts.pathname] == null){
        console.log("NOT FOUND PAGE:" + request.url);
        response.writeHead(200, { 'Content-Type': 'text/html' });
        response.end("<html><body><h1>NOT FOUND PAGE:" + 
            request.url + "</h1></body></html>");
        return;
    }
    // page render 
    var content = ejs.render( template,
        {
            title: routes[url_parts.pathname].title,
            content: ejs.render(
                routes[url_parts.pathname].content,
                {
                    message: routes[url_parts.pathname].message
                }
            )
        }
    );
    response.writeHead(200, {'Content-Type': 'text/html'});
    response.write(content);
    response.end();
}

이를 실행하여 http://127.0.0.1:1234/로 접근해 본다. content1.ejs의 내용이 표시된다. 이 페이지에있는 링크를 클릭하면, http://127.0.0.1:1234/other로 이동하고 content2.ejs의 내용을 표시한다.

그럼 동작이 확인되었다면, 스크립트의 내용을 보면서 처리 방법을 설명하겠다.

url의 로드

var url = require('url');

URL을 처리하기 위해서는 "url"라는 개체를 로드한다. 이 url 객체는 URL 문자열을 파싱하고, 거기에서 필요한 것을 얻어오는 기능을 제공한다.

URL의 파싱

var url_parts = url.parse (request.url);

요청이 액세스해 온 URL을 파싱 처리한다. 요청된 URL은 request 이벤트 핸들러의 인자로 전달된 request 객체의 'url'이라는 속성(property)에서 얻을 수 있다.

url 객체의 "parse"는 URL의 문자열을 요소마다 분할하여 오브젝트화 하여 반환한다. 이것으로 변수 url_parts에 URL 요소가 저장된다. 각각의 요소는 생성된 객체의 속성으로 저장되어 언제든지 사용할 수 있다.

각 페이지의 데이터를 준비한다

이번 스크립트에서는 각 페이지의 데이터를 routes라는 변수에 정리하고 있다. 이것은 액세스하는 곳의 경로를 키로 준비하고, 그 경로에 표시되는 페이지의 정보를 연관 배열로 정리한 것을 값으로 설정되어 있다. 예를 들어, 루트인 "/"의 값을 보면,

"/":{
    "title":"Main Page",
    "message":"이것을 예제 페이지입니다.",
    "content":content1 }

이런 식으로 되어있는 것을 알 수 있을 것이다. 연관 배열에는 title, message, content라는 키가 준비되어 있으며, 각각 "제목 텍스트", "페이지에 표시하는 메시지 텍스트", "표시하는 페이지의 내용(템플릿 데이터)"를 값으로 저장되어 있다. 이 변수 routes에서 액세스하는 주소의 경로마다 필요한 정보를 얻는 처리 하는 것이다.

경로를 얻을 수 없는 경우의 처리

if (routes[url_parts.pathname] == null){……}

변수 url_parts에 URL의 각 요소가 오브젝트로 저장되어 있는데, 이 중에 "경로"값은 "pathname"라는 속성으로 저장되어 있다.

변수 routes에는 기존에 언급한 바와 같이 각 경로마다 필요한 정보가, 경로를 키로 한 연관 배열에 정리되어 있다. 이는 routes[url_parts.pathname] 값을 꺼내면, 현재 요청이 액세스하고 있는 경로의 정보를 얻을 수 있게 된다. 만약이 값이 null이라면, 변수 routes는 정보가 없다는 것은 "그 경로에 액세스할 수 없다'라는 것을 의미한다.

그래서 null의 경우에는 에러 메시지 등을 표시해야 한다. 이것으로 준비되지 않은 주소로 대응을 할 수 있다.

액세스한 경로의 페이지를 렌더링하기

var content = ejs.render( template,
    {
        title: routes[url_parts.pathname].title,
        content: ejs.render(
            routes[url_parts.pathname].content,
            {
                message: routes[url_parts.pathname].message
            }
        )
    }
);

이후에는 변수 routes에서 필요한 값을 얻어 렌더링을 하면 된다. 예를 들어, title에 설정하는 값은

title: routes[url_parts.pathname].title

이렇게 준비하면 되고, 컨텐츠의 렌더링을 content 준비하려면

content: ejs.render( 
    routes[url_parts.pathname].content, ……)

이와 같이하면 된다. 여기에 표시할 내용의 템플릿은 routes[url_parts.pathname].content에서 얻을 수 있기에, 이를 render 할 뿐이다.

그 후로는 writeHead, write, end 세트로 실행하여 페이지 출력이 완료된다. 템플릿 렌더링 처리가 있기 에 조금은 귀찮게 보이지만, 기본적으로 "페이지에서 사용하는 값은 routes[url_parts.pathname]에 정리되고 있다"라는 것을 알고 있으면 어렵지 않다.



입력폼 POST 전송

이어서 폼을 POST 전송했을 때의 처리에 대해 생각해 본다. 이미 주소로 페이지 처리하는 방법은 알았기에 입력폼을 가진 페이지를 준비하고, 그 전달 대상의 주소 처리하는 작업은 알고 있다.

GET과 POST

가장 큰 문제는 "GET인지, POST인지"를 어떻게 알 것인가하는 점이다. 그리고 또 하나는 "전송된 입력폼의 정보를 어떻게 얻을 것인가"라는 점이다.

우선, GET과 POST 분리하는 방법이다. 이는 사실 간단하다. request 이벤트 핸들러에 인수로 전달된 request 객체의 'method'를 조사하는 것만으로 끝이다. 핸들러 안에 이런 식으로 처리를 준비하면 된다.

if (request.method == "GET"){
    ...... GET 처리 ......
}
if (request.method == "POST"){
    ...... POST 처리 ......
}

다음으로 "POST 전송된 입력폼 데이터를 가져오기"는 실제 코드를 보는 편이 알기 쉬울 것이다. 그럼, 이에 대해서도 샘플을 만들어 설명한다. 우선은 표시하는 내용의 템플릿이다. 이번에는 content1.ejs에 입력폼을 넣고, 새롭게 준비하는 content3.ejs에서 전송된 입력폼의 표시를 하도록 한다.

아래애 예제를 올려 두었다. 우선 이를 작성한다.

content1.ejs

<h2>예제로 작성한 내용입니다.</h2>
<p><%= message %></p>
<hr>
<form method="post" action="./post">
<table>
    <tr><td>ID:</td><td><input type="text" name="idname"></td></tr>
    <tr><td>PASS:</td><td><input type="password" name="pass"></td></tr>
    <tr><td></td><td><input type="submit"></td></tr>
</table>
</form>

content3.ejs

<p>POST으로 액세스된 내용입니다.</p>
<p>ID: <%= idname %></p>
<p>PASS: <%= pass %></p>
<p><a href="/">돌아가기</a></p>

POST로 전송된 데이터 처리

이어서 Node.js 스크립트를 작성한다. 이번에는 입력폼의 대상으로 content3.ejs을 표시하기 위해 "/ post"라는 경로로 정보를 추가하고 있다.

var http = require('http');
var fs = require('fs');
var ejs = require('ejs');
var url = require('url');
var qs = require('querystring');
 
var template = fs.readFileSync('./template.ejs', 'utf8');
var content1 = fs.readFileSync('./content1.ejs', 'utf8');
var content2 = fs.readFileSync('./content2.ejs', 'utf8');
var content3 = fs.readFileSync('./content3.ejs', 'utf8');
 
var routes = {
    "/":{
        "title":"Main Page",
        "message":"이것은 예제 페이지입니다.",
        "content":content1},
    "/index":{
        "title":"Main Page",
        "message":"이것은 예제 페이지입니다.",
        "content":content1},
    "/other":{
        "title":"Other Page",
        "message":"다른 페이지를 표시하고 있습니다.",
        "content":content2},
    "/post":{
        "title":"Post Page",
        "content":content3}
};
 
var server = http.createServer();
server.on('request', doRequest);
server.listen(1234);
console.log('Server running!');
 
// 요청 처리
function doRequest(request, response) {
    var url_parts = url.parse(request.url);
    // route check
    if (routes[url_parts.pathname] == null){
        response.writeHead(200, { 'Content-Type': 'text/html' });
        response.end("<html><body><h1>NOT FOUND PAGE:" + 
            request.url + "</h1></body></html>");
        return;
    }
    // get
    if (request.method == "GET"){
        var content = ejs.render( template,
            {
                title: routes[url_parts.pathname].title,
                content: ejs.render(
                    routes[url_parts.pathname].content,
                    {
                        message: routes[url_parts.pathname].message
                    }
                )
            }
        );
        response.writeHead(200, {'Content-Type': 'text/html'});
        response.write(content);
        response.end();
        return;
    }
    // post
    if (request.method == "POST"){
        if (url_parts.pathname == "/post"){
            var body='';
            request.on('data', function (data) {
                body +=data;
            });
            request.on('end',function(){
                var post =  qs.parse(body);
                var content = ejs.render( template,
                    {
                        title: routes[url_parts.pathname].title,
                        content: ejs.render(
                            routes[url_parts.pathname].content,
                            {
                                idname: post.idname,
                                pass: post.pass
                            }
                        )
                    }
                );
                response.writeHead(200, {'Content-Type': 'text/html'});
                response.write(content);
                response.end();
            });
        } else {
            response.writeHead(200, {'Content-Type': 'text/plain'});
            response.write("NO-POST!!");
            response.end();
        }
    }
}

스크립트를 작성한 후에 Node.js를 기동시켜서 액세스해 본다. http://127.0.0.1:1234/ 에 액세스하면 준비된 양식이 표시된다. 여기에 ID와 PASS를 적당히 값을 기입하고 전송하면, 전송된 내용이 표시된다.

이번 스크립트에는 "querystring"라는 객체를 로드하여 사용하고 있다. 시작 부분에 있는 다음 문장 이다.

var qs = require('querystring');

이 querystring은 쿼리 문자열을 처리하는 기능을 제공한다. 이를 이용하여 쿼리 문자열에서 필요한 값을 지정하여 꺼낼 올 수 있게 되다. 그럼, 스크립트의 내용을 살펴 보자.

여기에서는 request.method으로 GET시와 POST에서 처리를 나눈다. GET인 경우에 처리는 앞전과 동일하다. 문제는 POST에서의 처리이다. 여기에서는 먼저 "data"라는 이벤트 핸드링을 하고 있다.

var body='';
request.on('data', function (data) {
    body +=data;
});

이 data 이벤트는 POST로 전송된 데이터를 수신했을 때 발생한다. 이벤트 핸들러에는 보내져 온 데이터가 인수로 전달된다. 이렇게 얻어진 데이터를 변수 body에 하나씩 추가하여 수신된 데이터가 완성되어 간다.

그리고 모든 수신 처리가 완료된 후에 POST 데이터의 처리와 페이지의 렌더링을 실시한다. 이것은 "end"라는 이벤트 핸들러를 준비하고 구현한다.

request.on('end',function(){
    var post =  qs.parse(body);
    ...... 중략 ......

end 이벤트 핸들러으로 최초에 수행하고 있는 것은 앞전에 data 이벤트에서 받은 데이터를 정리한 변수 body를 파싱하는 과정이다. 이는 querystring 객체의 "parse"라는 메소드로 실행하고 있다. 이 메소드는 인수로 전달된 쿼리 문자열을 파싱하여 객체에 정리한다. 예를 들면, 아래와 같은 식이다.

a=abc&x=xyz
↓
{ a: "abc", x:"xyz" }

이 때, URL 인코딩된 값도 자동으로 원래의 문자열로 디코딩된다. 이렇게 얻어진 변수 post에서 필요한 값을 꺼내면 된다. 이번에는 content3를 렌더링할 때 다음과 같이하고 있다.

content: ejs.render(
    routes[url_parts.pathname].content,
    {
        idname: post.idname,
        pass: post.pass
    }
)

전송된 값은 post.idname, post.pass에서 꺼낼 수 있다. 이 후에는 이들을 정리해서 렌더링할 뿐이다.

POST 전송은 데이터의 수신이 조금 복잡하지만, 그래도 알게 되면 쉽다. 이것으로 대부분의 일반 Web 페이지는 만들 수 있을 것이다.

+ Recent posts