First things first. Let’s import our dependencies into the application:
var express = require('express'), mongoskin = require('mongoskin'), bodyParser = require('body-parser'), logger = require('morgan')
After version 3.x, Express.js streamlined the instantiation of its app instance so that the following line gives us a server object:
var app = express()
To extract parameters and data from the requests, let’s use bodyParser.urlencoded() and bodyParser.json()
middleware. We apply them with app.use(), and the code looks more like configuration statements:
app.use(bodyParser.urlencoded()) app.use(bodyParser.json()) app.use(logger())
express.logger() is optional middleware that allows us to monitor requests. Middleware (in this
(http://expressjs.com/api.html#app.use) and other forms (http://expressjs.com/api.html#middleware)) is a powerful and convenient pattern in Express.js and Connect to organize and reuse code.
As with the express.urlencoded() and express.json() methods, which save us from the hurdles of parsing a body object of an HTTP request, Mongoskin makes it possible to connect to the MongoDB database in one effortless line of code:
var db = mongoskin.db('mongodb://@localhost:27017/test', {safe:true})
Note
■
if you wish to connect to a remote database (e.g., MongohQ (
https://www.mongohq.com/home)), substitute
the string with your username, password, host, and port values. here is the format of the uniform resource identifier (uri)
string (no spaces):
mongodb://[username:password@] host1[:port1][,host2[:port2],... [,hostN[:portN]]] [/[database][?options]]The next statement is a helper function that converts hex strings into MongoDB ObjectID data types:
var id = mongoskin.helper.toObjectID
The app.param() method is another form of Express.js middleware. It basically says: Do something every time there is this value in the URL pattern of the request handler. In our case, we select a particular collection when a request pattern contains a string collectionName prefixed with a colon (we see this when we examine routes):
app.param('collectionName', function(req, res, next, collectionName){ req.collection = db.collection(collectionName)
return next() })
182
To be user friendly, let’s include a root route with a message that asks users to specify a collection name in their URLs:
app.get('/', function(req, res, next) {
res.send('Select a collection, e.g., /collections/messages') })
Now the real work begins. Here is how we retrieve a list of items sorted by _id that has a limit of 10:
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) })
})
Have you noticed a :collectionName string in the URL pattern parameter? This and the previous app.param()
middleware are what give us the req.collection object, which points to a specified collection in our database. The object-creating end point (POST /collections/:collectionName) is slightly easier to grasp because we just pass the whole payload to the MongoDB.
app.post('/collections/:collectionName', function(req, res, next) { req.collection.insert(req.body, {}, function(e, results){
if (e) return next(e) res.send(results) })
})
This approach, or architecture, is often called free JSON REST API, because clients can throw data structured in any way and the server handles it perfectly (a good example is a back-end as a service called Parse.com, recently acquired by Facebook).
Single-object retrieval functions are faster than find(), but they use a different interface (they return an object directly instead of a cursor—please be aware). We’re also extracting the ID from the :id part of the path with
req.params.id Express.js magic:
app.get('/collections/:collectionName/:id', function(req, res, next) { req.collection.findOne({
_id: id(req.params.id) }, function(e, result){ if (e) return next(e) res.send(result) })
})
The PUT request handler gets more interesting because update() doesn’t return the augmented object. Instead, it returns a count of affected objects. Also, {$set:req.body} is a special MongoDB operator (operators tend to start with a dollar sign) that sets values.
The second {safe:true, multi:false} parameter is an object with options that tell MongoDB to wait for the execution before running the callback function and to process only one (the first) item.
183
app.put('/collections/:collectionName/:id', function(req, res, next) { req.collection.update({
_id: id(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'}) }
); })
Last, the DELETE method, which also outputs a custom JSON message (JSON object with msg equals either a
success string or the encountered error message):
app.del('/collections/:collectionName/:id', function(req, res, next) { req.collection.remove({
_id: id(req.params.id) },
function(e, result){ if (e) return next(e)
res.send((result === 1) ? {msg:'success'} : {msg:'error'}) }
); })
Note
■
app.del()is an alias for
app.delete()method in express.js.
The last line that actually starts the server, on port 3000 in this case, is
app.listen(3000, function(){ console.log ('Server is running') })
Just in case something is not working well, here is the full code of the Express.js 4.1.2 REST API server from the
ch8/rest-express/index.js file:
var express = require('express'), mongoskin = require('mongoskin'), bodyParser = require('body-parser'), logger = require('morgan')
var app = express()
app.use(bodyParser.urlencoded()) app.use(bodyParser.json()) app.use(logger())
var db = mongoskin.db('mongodb://@localhost:27017/test', {safe:true}) var id = mongoskin.helper.toObjectID
184
app.param('collectionName', function(req, res, next, collectionName){ req.collection = db.collection(collectionName)
return next() })
app.get('/', function(req, res, next) {
res.send('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.findOne({_id: id(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.update({_id: id(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.remove({_id: id(req.params.id)}, function(e, result){ if (e) return next(e)
res.send((result === 1) ? {msg:'success'} : {msg:'error'}) })
})
app.listen(3000, function(){ console.log ('Server is running') })
185
Exit your editor and run this command in your terminal:
$ node .
This is equivalent to $ node index.
Then, in a different terminal window (without closing the first one), execute the tests:
$ mocha test
A slightly better execution is as follows (Figure 8-1):
$ mocha test -R nyan
Figure 8-1. Who wouldn’t like a library with Nyan Cat?
If you really don’t like Mocha and/or BDD (and TDD), CURL is always there for you. :-) For example, CURLing is done with the following, as shown in Figure 8-2:
186
Figure 8-2. A GET request made with CURL
Note
■
get requests also work in the browser. For example, open
http://localhost:3000/testwhile your server
is running.
CURLing data to make a POST request is easy (Figure 8-3):
187
DELETE or PUT can be sent with --request NAME and the ID in the URL, such as:
$ curl --request DELETE http://localhost:3000/collections/curl-test/52f6828a23985a6565000008 For a short, nice tutorial on the main CURL commands and options, take a look at CURL Tutorial with Examples of Usage (http://www.yilmazhuseyin.com/blog/dev/curl-tutorial-examples-usage/).
In this chapter, our tests are longer than the app code itself, so abandoning TDD might be tempting, but believe me, the good habits of TDD save you hours and hours of work during any serious development, when the complexity of the application on which you are working is high.
You might wonder: Why spend time on TDD in the chapter about REST APIs? The answer is mainly because REST APIs don’t have UIs in the form of web pages. APIs are intended for consumption by other programs
(i.e., consumers or clients). We, as developers, don’t have much choice when it comes to using APIs. We either have to write a client application, or manually send execute CURLs (or jQuery $.ajax() calls from the browser console). But, the best way is to use tests, which act as small client apps, if we think categorically!
However, this is not the whole story. TDD is great when it comes to refactoring. The next section is spent changing from Express.js to Hapi. And after we’re done, we can rest assured, by running the same tests, that the functionality isn’t broken or changed.