Hapi 시작하기

Node.js에서 서버 구현은 대부분 Express.js 프레임웍으로 개발했다. Express.js 3.x에서 4.x대로 업그레이드 되면서 개선된 성능과 사용 편의성에서 만족하고 있었다. 이제 막 익스프레스 프레임웍이 손에 익을 무렵 Hapi라는 신규 노드 페레임웍에 대한 글을 접한게 되었다. (Hapi.js로 Node.js를 시작하세요. - 1부 Why Hapi.js)

Hapi를 스터디한 동기는 코드의 간격함이다. 튜터리얼에 보면 단 네 문장으로 서버를 설정하고 구동할수 있다. 익스프레스 보다는 코드가 간결하고 모듈화하기 쉽다는 것을 실감한다. 라우팅을 별도로 모듈화할 수 있다는 점, 프로토콜 파라메터 검증이 용이한 점(Joi), 로깅(Good) 등.

아래 코드를 보자.  (index.js)

/* index.js */

var Hapi = require('hapi');

// 서버 객체 생성 및 컨넥션 정보 설정
var server = new Hapi.Server();
server.connection({
    host: 'localhost',
    port: 8000
});

// 라우팅 설정 ('/')
server.route({
    method: 'GET',
    path:'/',
    handler: function (req, reply) {
        reply('server is running');
    }
});

// 서버 구동
server.start(function () {
    console.log('Hello, hapi server is running in ' + server.info.uri);
});

홈페이지에 나온 샘플 코드다. 코드만 봐도 '나는 Api 서버를 구현하는 놈이요'라는 걸 알수 있다. 서버 객체를 생성하고 바로 라우팅 설정 후 서버를 구동한다. 이후에 할것은 Api 서버의 목적에 맞게 필요한 프로토콜을 route() 함수로 추가하는 일이다.

라우팅

라우팅을 좀 더 살펴보자. server객체의 route() 함수로 라우팅을 설정한다. method에 'GET', 'POST', 'PUT', 'DELTE'를 설정하고 path에 라우팅 경로를 설정한다. 그리고 handler에 라우팅 로직 함수를 연결한다. 핸들러 함수는 두 파라매터를 받는데 첫번째 req 변수가 프로토콜 요청에 대한 정보를 담는 객체다. 이 request 객체를 통해 파라매터 등을 받아 비지니스 로직을 처리하면 된다.  파라매터는 세 가지 형식으로 받는다.

  • `req.params`: url의 리소스 구분자('/')분을 통해 입력받은 값, 예( /users/Chris일 경우 Chris를 입력받을수 있음)
  • `req.query`: url의 '?'이하 '&'로 구분하여 입력받은 값 (보통 GET 메소드를 통해 얻는 파라매터)
  • `req.payload`: POST 메소드를 통해 얻는 파라메터

다음 포스트에서 설명하겠지만 Hapi는 request객체를 통해 얻는 파라매터를 검증하는 강력한 모듈(Joi)을 제공한다.

두 번째 파라매터 reply 객체는 비지니스 로직 처리후 클라이언트 응답을 위해 사용한다. 파라메터로 문자열을 넣으면 Plane Text를, 자바스크립트 객체를 넘겨주면 Json형식으로 반환한다. reply.code()로 http statusCode를 보낼 수도 있다. 함수 체이닝을 이용해 reply().code()로 사용할수도 있다.

라우팅 설정을 server객체의 route() 함수가 담당하므로 server 객체를 넘겨주면 라우팅 모듈만 별도로 분리할 수 있다. 모듈화를 위해 reoutes.js라는 파일로 라운팅을 분리하자.

/* index.js */

var Hapi = require('hapi');

// 서버 객체 생성 및 컨넥션 정보 설정
var server = new Hapi.Server();
server.connection({
    host: 'localhost',
    port: 8000
});

// 라우팅 설정 ('/')
require('routes.js')(server);

// 서버 구동
server.start(function () {
    console.log('Hello, hapi server is running in ' + server.info.uri);
});


/* routes.js */

module.exports = function (server) {

    server.route({
        method: 'GET',
        path:'/',
        handler: function (req, reply) {
            reply('server is running');
        }
    });

    server.route({
        method: 'GET',
        path:'/hello/{name}',
        handler: function (req, reply) {
            reply('hello ' + req.params.name);
        }
    });

};

기존 index.js 파일에서는 라우팅 모듈을 불러와서 server 객체를 넘겨준다. routes.js 모듈에서는 이 server객체를 이용하여 라우팅 설정을 한다.

라우팅 모듈화

좀 더 확장해 보면 리소스명으로 폴더를 만들고 각 폴더에 server 객체를 받는 라우팅 모듈을 구현할 수 있다. 기존 index.js 는 라우팅 설정만 변경한다. 모듈을 분리한 routes/index.js 파일을 불러와서 server 객체를 넘겨 준다.

/* index.js */
// 서버 객체 생성 및 컨넥션 정보 설정 ...

// 라우팅 설정 ('/')
require('routes')(server);

// 서버 구동 ...

routes/index.js에서는 index.js에서 넘겨받은 server 객체를 이용해 하위 라우팅을 위해 다시 server 객체를 전달한다.

/* routes/index.js */
module.exports = function (server) {

    // 각 폴더에 라우팅을 위해 server 객체를 넘겨줌
    require('users')(server);
    require('posts')(server);
    // ...
};

리소스 단위로 만든 routes 하위폴더에 각 각 index.js 파일을 만들고 넘겨받은 server 객체를 통해 라우팅 설정을 한다. 만약 하위 리스소를 가지고 있다면 동일하게 server 객체를 넘겨준다.

/* routes/users/index.js */
module.exports = function (server) {

    // 라우팅 설정 ...

    // 하위 라우팅을 위해 하위 폴더에 server 객체를 넘김
    require('users 하위폴더1')(server);
    require('users 하위폴더2')(server);
    // ...

};<br>

 

라우팅 헬퍼

지금까지 코드를 보면 폴더별로 하위 폴더에 대한 라우팅을 위해 server 객체를 넘기는 코드가 중복이다. 이 부분을 routeHelper 모듈로 분리해 보자.

/* reouteHelper.js */

exports.route = function(server, _path) {
  if (!server || !_path) throw Error();

  fs.readdirSync(_path).forEach(function (dir) {

    // Ignore files. Only folders
    if (/.js/.test(dir)) return;

    require(path.resolve(_path,  dir))(server);
  });
};

server객체를 첫번째 파라매터로 받고, 두번째 파라매터인 _path에 호출 측 경로를 전달 받는다. 이것을 통해 하위 폴더에 라우팅을 위한 server 객체를 전달할 수 있다. 아래는 routeHelper로 대체한 코드다.

/* index.js */
// 서버 객체 생성 및 컨넥션 정보 설정 ...

// 라우팅 설정 ('/')
require('routes')(server);

// 서버 구동 ...


/* routes/index.js */
module.exports = function (server) {

    // 각 폴더에 라우팅을 위해 server 객체를 넘겨줌
    routeHelper.route(server, __dirname);
};


/* routes/users/index.js */
module.exports = function (server) {

    // 라우팅 설정

    // 하위 라우팅을 위해 하위 폴더에 server 객체를 넘김
    routeHelper.route(server, __dirname);
};


/* routes/posts/index.js */
module.exports = function (server) {
    // 라우팅 설정 ...

    // 하위 라우팅을 위해 하위 폴더에 server 객체를 넘김
    routeHelper.route(server, __dirname);
};

전체 코드: https://github.com/jeonghwan-kim/hapi_study/tree/05_routeHelper