종종 좋은 것들은 한곳에 머무르지 않고, 그래서 모카(Mocha)와 Superagent를 이용한 테스트와 Mongoskin과 Express.js를 사용하여 Node.js와 MongoDB를 이용한 JSON REST API 서버를 만드는 튜토리얼을 Express.js 4버전이 출시됨과 함께 만들게 되었다. 최신의 Express.js 4, Node.js, MongoDB를 다루는 튜토리얼을 만나보자.
Express.js4와 MongoDB REST API Tutorial은 아래 파트로 나뉘어 구성되어있다.
1. Node.js와 MongoDB REST API 개요
2. 모카와 Superagent를 이용한 REST API 테스트
3. NPM-ing Node.js Server Dependencies
4. Express.js 4.x Middleware Caveat
5. Express.js와 MongoDB (Mongoskin) 구현
6. Express.js 4 랩을 실행하고 모카를 이용한 MongoDB 테스팅
7. Express.js와 Node.js의 결론 및 확장성
만약 당신이 자장소와 그것이 무엇을 동작하는지로부터 코드의 동작에 관심이 있다면, 여기 REST API server가 어떻게 다운로드되고 동작하는지 간단한 설명이 여기있다.
$ git clone git@github.com:azat-co/rest-api-express.git
$ npm install
$ node express.js
$ mongod 와 함께 MongoDB를 시작한다. 그다음, 새 터미널창을 띄워서 모카 테스트를 실행한다. :
아니면, 모카를 전역으로 설치하지 않은 경우. :
$ ./node_modules/mocha/bin/mocha express.test.js
1. Node.js와 MongoDB REST API 개요
서버 응용프로그램은 Express.js 4버전대 프레임워크와 MongoDB를 위한 Mongoskin 라이브러리를 필요로한다. 이 REST API 서버에서 우리는 CRUD(create, read, update and delete)기능을 실행하고 app.param( ), app.use( )와 같은 Express.js middleware 방식의 메소드를 실행할 것이다.
가장 처음에 할 것은, MongoDB를 설치하는 것이다. 이 링크를 따라가면 할 수 있을 것이다. 우리는 아래의 버전의 라이브러리를 사용하게 될 것이다.
- express: ~4.1.1
- body-parser: ~1.0.2
- mongoskin: ~1.4.1
- expect.js: ~0.3.1
- mocha: ~1.18.2
- superagent: ~0.17.0
만약 버전이 맞지 않다면 코드가 동작하지 않을 수도 있다 :-(
2. 모카와 Superagent를 이용한 REST API 테스트
시작하기 전에, 우리가 만들게될 REST API 서버에 HTTP 요청을 만드는 기능 테스트를 한번 적어보자. 만약 모카를 사용할 줄 알거나, 바로 Express.js 앱 구현을 해보고 싶은 사람들은 마음대로 해도 된다. 당신은 물론 터미널에서 CRUL테스트를 할 수도 있다.
우리는 이미 Node.js, npm, MongoDB를 설치했다고 가정하고, 새 폴더를 만들어보자.
$ mkdir rest-api
$ cd rest-api
우리는 모카, Expect.js, SuperAgent 라이브러리를 사용하게 될 것이다. 그것들을 설치하고, 프로젝트폴더에 들어가서 이 명령을 실행해라.
$ npm install mocha@1.18.2 --save-dev
$ npm install expect.js@0.3.1 --save-dev
$ npm install superagent@0.17.0 --save-dev
Note: 당신은 물론 (명령에 -g를 넣고) 모카를 전역으로 설치할 수도 있다.
이제 아까 그 폴더에 express.test.js파일을 만들자. 아래 6가지를 진행할 것이다.
- 새 객체를 생성한다.
- 객체의 ID를 가져온다.
- 객체의 모든 정보를 가져온다.
- ID를 이용해 객체를 업데이트한다.
- ID를 이용해 객체를 제거한다.
HTTP 요청은 Super Agent’s와 연관된 (단지 테스트케이스를 넣기만 하면되는) 함수들을 사용하면 굉장히 식은죽먹기이다.
이 강좌는 Express.js 4, MongoDB, Mocha를 사용하여 REST API를 만드는것이 목적이기 때문에 테스트케이스(test suits)에 대해 깊게 들어가지는 않겠다. 코드를 복붙하시오!
아래 코드는 express.test.js파일이다.
var superagent = require('superagent')
var expect = require('expect.js')
describe('express rest api server', function(){
var id
it('post object', function(done){
superagent.post('http://localhost:3000/collections/test')
.send({ name: 'John'
, email: 'john@rpjs.co'
})
.end(function(e,res){
// console.log(res.body)
expect(e).to.eql(null)
expect(res.body.length).to.eql(1)
expect(res.body[0]._id.length).to.eql(24)
id = res.body[0]._id
done()
})
})
it('retrieves an object', function(done){
superagent.get('http://localhost:3000/collections/test/'+id)
.end(function(e, res){
// console.log(res.body)
expect(e).to.eql(null)
expect(typeof res.body).to.eql('object')
expect(res.body._id.length).to.eql(24)
expect(res.body._id).to.eql(id)
done()
})
})
it('retrieves a collection', function(done){
superagent.get('http://localhost:3000/collections/test')
.end(function(e, res){
// console.log(res.body)
expect(e).to.eql(null)
expect(res.body.length).to.be.above(0)
expect(res.body.map(function (item){return item._id})).to.contain(id)
done()
})
})
it('updates an object', function(done){
superagent.put('http://localhost:3000/collections/test/'+id)
.send({name: 'Peter'
, email: 'peter@yahoo.com'})
.end(function(e, res){
// console.log(res.body)
expect(e).to.eql(null)
expect(typeof res.body).to.eql('object')
expect(res.body.msg).to.eql('success')
done()
})
})
it('checks an updated object', function(done){
superagent.get('http://localhost:3000/collections/test/'+id)
.end(function(e, res){
// console.log(res.body)
expect(e).to.eql(null)
expect(typeof res.body).to.eql('object')
expect(res.body._id.length).to.eql(24)
expect(res.body._id).to.eql(id)
expect(res.body.name).to.eql('Peter')
done()
})
})
it('removes an object', function(done){
superagent.del('http://localhost:3000/collections/test/'+id)
.end(function(e, res){
// console.log(res.body)
expect(e).to.eql(null)
expect(typeof res.body).to.eql('object')
expect(res.body.msg).to.eql('success')
done()
})
})
})
테스트를 하기위해, $ mocha express.test.js 커맨드를 날릴 것이다. (모카를 전역으로 설치하지 않은 경우 $ ./node_modules/mocha/bin/mocha espress.test.js)
3. NPM-ing Node.js Server Dependencies
Express.js는 Node.js HTTP module 핵심 객체로 감싸져있다. Express.js 프레임워크는 Connect middleware의 상위층을 기반으로 만들어져있고, 어마어마하게 많은 편리함을 제공한다. 몇몇 사람들은 ...
만약 당신이 이전 섹션(Text Converage)에서 rest-api 폴더를 만들었다면, 어플리케이션 모듈을 설치하기 위해 아래 명령만 입력하면 된다. :
$ npm install express@4.1.1 --save
$ npm install mongoskin@1.4.1 --save
4. Express.js 4.x Middleware Caveat
슬프게도 NPM express만 하는 것으로는 Express.js와 함께 최소한의 REST API 서버를 구축하는게 불가능하다. 왜냐하면 4.버전대의 middlewares은 번들이 아니기 때문이다!(the middlewares are not bundled) 개발자들은 Express.js 4.x.왼쪽에 있는 express.static를 제외한 분리된 모듈들을 설치해야한다. 그리고 들어오는 정보를 파싱하기위해 body-parser를 추가해야한다:
$ npm install body-parser@1.0.2 --save
5. Express.js와 MongoDB (Mongoskin) 구현
제일 처음에 우리의 express.js 안에 우리의 dependencies를 정의해야한다. :
var express = require('express'),
mongoskin = require('mongoskin'),
bodyParser = require('body-parser')
3.x대 이후 버전(물론 v4도 마찬가지), Express.js은 앱 인스턴스의 객체를 간소화해서 가져온다, 아래 라인은 서버객체를 우리에게 제공할 것이다.(번역자:그냥 import정도로 생각하면 될듯):
요청의 바디로부터 파람들을 추출하기위해, 우리는 아래와같은 모양의 bodyParser() middleware를 사용할 것이다.:
Middleware(여기, 다른 포럼)는 Express.js에서 강력하고 편리한 패턴이고 구성요소를 연결하며 코드의 재사용을 증진시킨다.
HTTP 요청의 바디객체 파싱의 넘사벽으로부터 구해주는 bodyParser()메소드와 같이, Mongoskin은 딱 한줄의 코드로 MongoDB 데이터베이스에 접속하는게 가능하다.:
var db = mongoskin.db('mongodb://@localhost:27017/test', {safe:true})
Note: 만약 당신이 원격으로 데이터베이스에 접속하고 싶다면(MongoHQ와 같은 것들..), 당신의 username, password, host and port의 값들을 스트링으로 치환하라. 여기 URI 스트링의 포맷이 있다: mongodb://[username:password@]host1[:port1][, host2[:port2], …[, hostN[:portN]]][/[database][?options]].
app.param() 메소드는 또다른 Express.js middleware이다. 이것은 기본적으로 “요청 핸들러의 URL페턴에 어떤 값이 있으니 매 시간마다 뭔갈 처리해라”는 것을 말하고 있다. 우리의 경우 요청 패턴이 collectionName 스트링에 콜론이 점두사로 있을 때, 우리는 특정 콜랙션을 선택한다. 그러면 다음 요청 핸들러에서 사용할 수 있는 요청 객체(widespreadreq)의 프로퍼티(콜랙션이나 다른것일 수도 있다.)로써 콜랙션을 저장한다. (번역자:뭔소린지 모르겠다)
app.param('collectionName', function(req, res, next, collectionName){
req.collection = db.collection(collectionName)
return next()
})
단지 유저지향적으로, 메시지와 함께 루트 라우트를 넣자.:
app.get('/', function(req, res) {
res.send('please select a collection, e.g., /collections/messages')
})
이제 진짜 할 일을 시작한다. 다수의 요소들중에 리스트를 어떻게 가져오는지 있다 (첫번째 파라메터는 빈 오브젝트{}이고 임의의 라는 뜻이다). 이 결과는 _id에(두번째 파라메터) 의해 정렬된 10개 제한으로 낼 것이다. find()메소드는 커서를 반환하고 우리는 toArray()를 불러 JavaScript/Node.js용 배렬로 만든다. :
app.get('/collections/:collectionName', function(req, res, next) {
req.collection.find({} ,{limit:10, sort: [['_id',-1]]}).toArray(function(e, results){
if (e) return next(e)
res.send(results)
})
})
URL 패턴 파라메너에서 :collectionName 스트링에대해 언급한적이 있나? 이것과 이전 app.param() middleware는 req.collection 객체를 우리에게 준다. 이 객체는 우리의 데이터베이스에서 특정 콜랙션을 가르키고 있다.
우리는 단지 MongoDB에서 전체적인 페이로드를 지나온 이후로 마지막 시점에 만들어진 이 객체는 조금 이해하기 쉽게 해준다. 이 메소드는 서버나 다른 것들의 데이터베스가 어떠한 데이터 스트럭쳐도 받아드릴수 있기 때문에 종종 free JSON REST API라 불린다. Parse.com과 다른 백엔드 서버 제공자는 free JSON 접근을 만들어낸다. 우리 Express.js 앱에서는 이것을 위해 req.body를 사용할 것이다.
app.get('/collections/:collectionName', function(req, res, next) {
req.collection.find({} ,{limit:10, sort: [['_id',-1]]}).toArray(function(e, results){
if (e) return next(e)
res.send(results)
})
})
함수들을 구하는 findById나 findOne과같이 생긴 단일 객체는 find()보다 빠르다. 그러나 그것들은 조금 다른 인터페이스를 사용한다 (그것들은 커서 대신에 진짜 오브젝트를 반환한다). 그러므로 그것을 기억하고 있어라. 추가적으로, 우리는 Express.js 마법에 의해 req.params.id 경로의 :id 부분으로부터 ID를 가져올 것이다.
app.get('/collections/:collectionName/:id', function(req, res, next) {
req.collection.findById(req.params.id, function(e, result){
if (e) return next(e)
res.send(result)
})
})
PUT 요청 핸들러는 update()가 증가된 객체를 반환하기때문에 더 흥미로운 것을 가져온다.
또한 {$set:req.body}는 값을 저장하는 기능을 가진 특별한 MongoDB 기능이 (보통 달러표시로 시작한다).
두번째 {safe:true, multi:false} 파라메터는 MongoDB에 callback 함수가 실행되기 전까지 동작을 멈추고 오직 한가지(첫번째) 아이탬만 처리하라고 알리는 옵션을 가진 객체이다.
app.put('/collections/:collectionName/:id', function(req, res, next) {
req.collection.updateById(req.params.id, {$set:req.body}, {safe:true, multi:false}, function(e, result){
if (e) return next(e)
res.send((result===1)?{msg:'success'}:{msg:'error'})
})
})
마지막으로 DELETE HTTP 함수는 app.del()에 의해 실행된다. 요청 핸들러에서, 우리는 그것이 그 동작을 하는 것처럼 보이는 removeById()를 사용한다. 그리고 커스텀 JSON success 메시지를 제거과정에서 내보낼것이다.:
app.del('/collections/:collectionName/:id', function(req, res, next) {
req.collection.remove({_id: req.collection.id(req.params.id)}, function(e, result){
if (e) return next(e)
res.send((result===1)?{msg:'success'}:{msg:'error'})
})
})
Note: delete는 JavaScript의 연산자이고 대신에 Express.js는 app.del을 사용한다.
아래의 경우 서버 3000포트를 시작하는 마지막라인이다.
단지 이 경우 뭔가 잘 실행되지 않을 수 있다. 여기 express.js 파일의 풀 소스가 있다.
var express = require('express')
, mongoskin = require('mongoskin')
, bodyParser = require('body-parser')
var app = express()
app.use(bodyParser())
var db = mongoskin.db('mongodb://@localhost:27017/test', {safe:true})
app.param('collectionName', function(req, res, next, collectionName){
req.collection = db.collection(collectionName)
return next()
})
app.get('/', function(req, res, next) {
res.send('please select a collection, e.g., /collections/messages')
})
app.get('/collections/:collectionName', function(req, res, next) {
req.collection.find({} ,{limit:10, sort: [['_id',-1]]}).toArray(function(e, results){
if (e) return next(e)
res.send(results)
})
})
app.post('/collections/:collectionName', function(req, res, next) {
req.collection.insert(req.body, {}, function(e, results){
if (e) return next(e)
res.send(results)
})
})
app.get('/collections/:collectionName/:id', function(req, res, next) {
req.collection.findById(req.params.id, function(e, result){
if (e) return next(e)
res.send(result)
})
})
app.put('/collections/:collectionName/:id', function(req, res, next) {
req.collection.updateById(req.params.id, {$set:req.body}, {safe:true, multi:false}, function(e, result){
if (e) return next(e)
res.send((result===1)?{msg:'success'}:{msg:'error'})
})
})
app.del('/collections/:collectionName/:id', function(req, res, next) {
req.collection.removeById(req.params.id, function(e, result){
if (e) return next(e)
res.send((result===1)?{msg:'success'}:{msg:'error'})
})
})
app.listen(3000)
코드를 저장하고 당신의 에디터를 닫아라, 우리의 소박한 Express.js REST API 서버가 완성되었다.
6. Express.js 4 랩을 실행하고 모카를 이용한 MongoDB 테스팅
이제 MongoDB가 설치되고 실행됬다는 가정($ mongod)하에 터미널에서 실행시켜볼 수 있다(modgod와 다른 창을 띄워라).
그리고 다른 창에서 아래 명령을 쳐라(처음 창은 닫으면 안된다):
혹은 모카를 전역으로 설치 하지 않은 경우.:
$ ./node_modules/mocha/bin/mocha express.test.js
만약 모카나 BDD 사용을 원치 않다면, CURL는 언제나 당신을 위해 있다. :-)
예를들어 POST 요청을 만들기 위해 CURL데이터이다. :
$ curl -X POST -d "name=azat" http://localhost:3000/collections/test13
그리고 결과는 아래처럼 나오게 될 것이다.:
{"name":"azat","_id":"535e180dad6d2d2e797830a5"}]
우리는 REST API 서버를 사용하기때문에 쉽게 이 객체를 확인할 수 있다.:
$ curl http://localhost:3000/collections/test13
Using CURL with Express 4 and MongoDB REST API
GET요청 또한 브라우저에서 동작할 수 있다. 예를들어, http://localhost:3000/collections/test13 링크를 당신의 로컬 서버가 포트 3000에서 돌아가고 있을때 열 수 있다.
혹은 서버의 결과를 신뢰하지 못한다면, MongoDB($ mongo)를 이용하여 데이터베이스를 확인할 수도 있다.:
Note: 만약 데이터베이스의 이름을 test라는 이름 대신에 다른 이름으로 바꾸고 싶으면, 명령 앞에 > use your_database_name 을 써라.
이 튜토리얼에서, 우리 테스트들은 실제 동작하는 어플리케이션 코드보다 길다. 몇몇에게는 테스트-기반-개발(Test-Driven Development)을 포기하고싶게 만들지도 모르겠지만, 당신이 어떠한 크고 복잡한 응용프로그램을 개발할 때에도 좋은 TDD 습관이 당신의 개발 시간을 줄여줄것이라 믿는다.
7. Express.js와 Node.js의 결론 및 확장성
Express.js 4와 MongoDB/Mongoskin 라이브러리는 간단하게 몇 줄 만에 REST API 서버를 구축하기에 정말 좋다. 후에, 당신이 라이브러리를 확장하고자 한다면, 그것들은 또한 당신의 코드를 구성하는 방법을 제공할 것이다.
NoSQL 데이터베이스는 MongoDB와같이 스키마를 정의할 필요 없고 어떠한 다양한 형태의 데이터를 넘기거나 저장할 수 있는 좋은 free-REST APIs이다.
Express.js나 다른 자바스크립트 라이브러리에대해 더 공부하고 싶다면, 아래 Azat의 책들을 보라.
- Practical Node.js: Building Real-world Scalable Web Apps
- Express.js Guide: The Comprehensive Book On Express.js
..
..
..
연관 글
[Node.js, MongoDB] Node.js 설치 및 실행
[Node.js, MongoDB] MongoDB 리눅스에 설치 및 실행
[Node.js, MongoDB] MongoDB 돌리기 (+백그라운드에서 돌리기)
> (번역) Express.js 4, Node.js and MongoDB REST API 강좌