Node.js | Express으로 PostgreSQL 사용

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로 리디렉션되므로 표시된 레코드를 확인할 수 있다.

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 측에서 어떻게 해야 한다"라는 것이다.




최종 수정 : 2018-07-16