본문 바로가기

node.js (OctoberSkyJs)

[node.js 따라배우기 03] RESTful 메소드와 테스팅

[node.js 따라배우기 02] 설치와 애플리케이션 뼈대 만들기 (Installation and Skeleton App)에 이은 따라배우기 세 번째 입니다. 본 시리즈는 "Node.js"를 이용해서 웹 애플리케이션(이하 웹앱)을 만들어보는 따라배우기(tutorial)시리즈입니다 node.js를 이용해서 웹앱을 만드는 과정을 따라가면서, 자신만의 애플리케이션을 만들 때 접하게 될 모든 영역을 다룰 예정입니다. 

- Part 1: 소개 (Introduction)

- Part 2: 설치와 애플리케이션 뼈대 만들기(Installation and Skeleton App)


이번 파트에서는 지난 강좌의 뼈대만 있는 앱을 완성시켜볼 예정입니다. 간단한 Document 모델을 추가했었습니다. 이제 살을 조금 붙어봅시다. 이번 따라하기는 git 저장소에서 코드를 체크아웃 해서 진행합니다. 여길 방문하시면 이번 따라하기 실습의 완성 코드를 보실 수 있습니다.

(옮긴이 주)
전체 실습의 단계별 코드들을 보고 싶으시면 다음 주소를 이용하시면 됩니다.
https://github.com/alexyoung/nodepad/commits/master
사용하는 모듈들의 업데이트에 따라서 실제 코드들도 업데이트 되고 있는 걸 보실 수 있습니다.
대표적으로 mongoose의 사용법, express 구조 등이 그렇습니다. 따라하기에서는 현재 버전기준에 맞춰 진행할 예정입니다.



로깅(Logging)

 

, 로깅을 추가해 봅시다. Express는 로거를 가지고 있으며, app.configure 블럭에서 설정 가능합니다. use를 쓰는걸 잊지말아주세요.

 


app.configure(function() {

  app.use(express.logger());

  // 지난 강좌의 설정 옵션들...

});

 

환경에 따라서 로깅설정을 조금 고치는 게 좋습니다. 마찬가지로 전 이렇게 수정해 보았습니다.

app.configure('development', function() {

    app.use(express.logger());

    app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));

});

 

app.configure('production', function() {

    app.use(express.logger());

    app.use(express.errorHandler());

});


 

API

CRUD((Create, Read, Update, Delete)기반의 RESTful API를 사용해서 HTTP 프로토콜로 문서(document) 접근 방식을 모델링 할 수 있습니다.

-       GET /documents  <--문서 목록을 보여주는 인덱스(index) 메소드

-       POST /documents/  <--새로운 문서 생성

-       GET /documents/:id <--특정 문서 리턴

-       PUT /documents/:id  <--문서 업데이트

-       DELETE /documents/:id <-- 문서 삭제


HTTP
메소드들은 중요합니다. 인덱스 호출 메소드와 문서 생성 메소드의 URL은 동일하지만 HTTP GET인지 POST인지에 따라 다르게 동작한다는 점을 유의 깊게 봐주세요. Express는 적절한 메소드를 이용해 이런 식의 요청들을 처리합니다.

 


HTTP
메소드가 중요합니다.

만약 이런 방식에 익숙하지 않다면, "HTTP 메소드(GET, POST, PUT…)가 중요하다"는 점만 기억해 주세요. 이를테면 지난번에 우리는 아래와 같은 메소드를 정의했었습니다.

 

app.get('/', function(req, res) {

  // Respond to GET for '/'

  // ...

});

 

만약 동일한 URLPOST 호출을 하는 폼(form)을 만들어 데이터를 보낸다면, Express는 에러를 뿌릴겁니다. 왜냐하면, 라우팅 처리를 하지 않았기 때문입니다. (옮긴이 주: 라우팅 처리란 적절한 메소드와 적절한 URL을 연결해 페이지를 처리하는 것)

 

또한, 지난 번에 우리가 설정 옵션에 express.methodOverride를 추가했던걸 떠올려봅시다. 이렇게 하는 이유는 브라우저가 DELETE 같은 메소드는 이해하지 못하기 때문입니다만, 관례적인 방식으로 이런 문제를 회피할 수 있습니다. 폼의 히든(hidden) 태그 변수를 사용하면 Express DELETE같은 메소드를 실제’ HTTP 메소드처럼 해석합니다.

이런 식의 RESTful API사용 접근은 우아하지 못한 듯 보일 수 있지만, 이런 관례적 처리방식은 많은 웹 애플리케이션에서 장점을 가집니다.

 

CRUD 스텁 참조 (CRUD Stub Reference)

 

// List

app.get('/documents.:format', function(req, res) {

});

 

// Create

app.post('/documents.:format?', function(req, res) {

});

 

// Read

app.get('/documents/:id.:format?', function(req, res) {

});

 

// Update

app.put('/documents/:id.:format?', function(req, res) {

});

 

// Delete

app.del('/documents/:id.:format?', function(req, res) {

});


ExpressDELETE대신에 del을 사용하는 걸 눈여겨 봐주세요.



(옮긴이 주)
:id는 뭐고 format은 뭔가 하실텐데요, 이건 요청 URL에 대한 라우팅을 나타내는 표현식입니다.

패턴: "/user/:id"
URL 대상: /user/12

패턴: "/users/:id?"
URL 대상
 /users/5
 /users

패턴: "/products.:format"
URL 대상
 /products.json
 /products.xml

같은 식으로 말입니다.
관련해서는 http://firejune.io/express/guide#routing 부분을 한번 보세요. :)


 

비동기 데이터베이스(Asynchronous Databases)

 

각각의 REST메소드를 작성하기전에 다음 예제(문서 목록 읽어들이기)를 살펴봅시다.


app.get('/documents', function(req, res) {

  var documents = Document.find().all();

 

  // 결과를 JSON으로 보냄

  res.send(documents);

 

우리는 일반적으로 노드 환경에 비동기적으로 데이터베이스 라이브러리들을 씁니다. 다음과 같은 식으로 말입니다.

 


app.get('/documents', function(req, res) {

  Document.find().all(function(documents) {

    // 'documents'는 호출 결과에 해당하는 모든 문서들을 담게됩니다.

    res.send(documents.map(function(d) {


     
// res.send()
JSON으로 보내주는 유용한 대표 객체를 돌려줍니다.

      return d.__doc;

    }));

  });

});

 

둘의 차이점은 결과에 접근하는데에 있어 콜백을 사용한다는 점에 있습니다.

 

 

포맷(Formats)

전 적절히 HTMLJSON을 지원했으면 좋겠다는 생각이 듭니다. 다음과 같은 패턴을 쓸수 있습니다.

 


// :format
json 이나 html

app.get('/documents.:format?', function(req, res) {

  // 어떤 종류의 몽고 쿼리나 업데이트

  Document.find().all(function(documents) {

    switch (req.params.format) {

      // json일때, 적당한 데이터를 생성한다.

      case 'json':

        res.send(documents.map(function(d) {

          return d.__doc;

        }));

      break;

 

      // 아닐 경우에는 데이터 베이스 템플릿을 렌더링 한다. 아직은 준비 안되었음

      default:

        res.render('documents/index.jade');

    }

  });

});

 

위 코드는 Express/Connect 기능의 코어 로드를 보여줍니다. 클라이언트가 JSON을 원하는지 HTML을 원하는지 탐지하기 위해서 라우팅 문자열은 :format 을 사용합니다. 물음표는 해당 포맷의 생략가능 여부를 나타냅니다.

이 패턴은 데이터베이스의 동작을 실제 응답코드로 둘러싼다는 점을 유의 깊게 살펴보세요. 비슷한 패턴을 아이템 저장이나 삭제에도 사용할 수 있습니다.

 

리디랙션(Redirection)

 

문서 생성 메소드는 JSON 문서를 반환하거나 클라이언트를 리디렉션(=다른 페이지로 보내기)합니다.


app.post('/documents.:format?', function(req, res) {

  var document = new Document(req.body['document']);

  document.save(function() {

    switch (req.params.format) {

      case 'json':

        res.send(document.__doc);

       break;

 

       default:

        res.redirect('/documents');

    }

  });

});

 

브라우저를 다시 문서 리스트로 리디렉트 하기 위해서 res.redirect를 사용했습니다. 혹은 폼 수정으로 쉽게 리디렉션할 수 도 있습니다. 이 부분은 사용자 인터페이스를 붙일 때 좀 더 자세히 살펴보도록 하겠습니다.

 

테스트(Tests)

 

저는 보통 API를 테스트하면서 이런 앱들을 만들기 시작합니다. 클라이언트 쪽 코드 작업을 착수하기 전에 많은 작업을 수월하게 만들어 줍니다. 처음 해야 하는 건 데이터 베이스를 테스트 하기 위해 데이터 베이스 커넥션을 추가하는 것입니다.

 


app.configure('test', function() {

  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));

  db = mongoose.connect('mongodb://localhost/nodepad-test');

});

 

그런다음, test/app.test.js에 테스트 환경을 강제 지정합니다.

 


process.env.NODE_ENV = 'test';

 

(이런 구성은) 테스트 데이터베이스를 안전하게 휴지통으로 보낼 수 있다는 걸 뜻합니다.

 

테스트들 자체가 익숙해지는 데는 시간이 좀 필요합니다. Expresso 테스트는 Express 애플리케이션들을 테스트하는데 매우 좋습니다. 하지만 소스 코드를 읽거나 메일링 리스트 검색등을 통해 좀 더 자세히 살펴볼 필요가 있습니다.

 

다음은 그런 예제입니다.



'POST /documents.json': function(assert) {

    assert.response(app, {

        url: '/documents.json',

        method: 'POST',

        data: JSON.stringify({ document: { title: 'Test' } }),

        headers: { 'Content-Type': 'application/json' }

      }, {

        status: 200,

        headers: { 'Content-Type': 'application/json' }

      },

 

      function(res) {

        var document = JSON.parse(res.body);

        assert.equal('Test', document.title);

      });

  }

 

테스트 이름은 'POST /documents.json' 입니다만, 뭐든 가능합니다. 프레임워크가 사실 이름을 파싱(parsing)하진 않습니다. HTTP 요청은 이어지는 첫 파라미터 세트들로 정의되어 있습니다. 이번 같은 경우엔, 저는 특별히 Content-Type을 지정했습니다. 만약 절절한 타입이 세팅되어 있지 않으면, Connect 미들웨어는 data를 해석 못합니다.

 

전 구체적으로 JSONapplication/x-www-form-urlencoded에 대한 테스트를 작성했습니다. 독자 여러분들은 각자 본인 나름의 테스트 코드를 작성하고 싶으실테니까요. 이것만 기억하세요. Express는 인코딩된 폼 데이터를 자동으로 다루지 못합니다. 그게 바로 설정 블록에서 methodOverride를 설정해 놓은 이유입니다.

 

 

결론


이제 다음과 같은 내용을 아셔야 합니다.

-       Express에서 HTTP 메소드를 적절히 다룰 수 있는 CRUD 메소드 스텁 만드는 법

-       Express(익스프레스) Exprsso(엑스프레소), 그리고 Mongoose를 이용해서 테스트 할 수 있도록 앱을 구조화 하는 방법

-       간단한 Expresso 테스트를 만드는 법

 

다음에는 문서 API 메소드 작성을 마치고 기본적인 HTML 템플릿을 추가해 볼 예정입니다.

당신의 코를 납작하게 만들어줄 jQuery기반 인터페이스를 추가해 볼 생각입니다만, 우선은 테스트를 만들고 API를 정리하면 더 좋을 것 같습니다.

 

참조(References) 

파트3을 마치며...
이번 실습은 본문에도 살짝 나와있는것 처럼 전체 소스를 다 설명하고 있지 않습니다. 문서 생성/수정/삭제 등의 본격적인 작업은 다음 따라하기부터 시작됩니다. 차분하게 읽어보시고, 궁금한 부분들을 조금씩 찾아보면서 다음 따라하기를 준비하시면 되겠습니다. :D