How To Write A Simple Node.js/MongoDB Web Service for an iOS App

Learn how to create a simple Node.js and MongoDB web service for an iOS app in this tutorial. By Michael Katz.

Leave a rating/review
Save for later
Share
You are currently viewing page 5 of 6 of this article. Click here to view the first page.

Using Your Collection Driver

To call your collectionDriver, first add the following line to the dependencies section of package.json:

    "mongodb":"1.3.23"

Execute the following command in Terminal:

npm update

This downloads and installs the MongoDB package.

Execute the following command in Terminal:

edit views/data.jade

Now add the following code to data.jade, being mindful of the indentation levels:

body
    h1= collection
    #objects
        table(border=1)
          if objects.length > 0
              - each val, key in objects[0]
                  th= key 
          - each obj in objects
            tr.obj
              - each val, key in obj
                td.key= val

This template renders the contents of a collection in an HTML table to make them human-readable.

Add the following code to index.js, just beneath the line path = require('path'),:

MongoClient = require('mongodb').MongoClient,
Server = require('mongodb').Server,
CollectionDriver = require('./collectionDriver').CollectionDriver;

Here you include the MongoClient and Server objects from the MongoDB module along with your newly created CollectionDriver.

Add the following code to index.js, just after the last app.set line:

var mongoHost = 'localHost'; //A
var mongoPort = 27017; 
var collectionDriver;

var mongoClient = new MongoClient(new Server(mongoHost, mongoPort)); //B
mongoClient.open(function(err, mongoClient) { //C
  if (!mongoClient) {
      console.error("Error! Exiting... Must start MongoDB first");
      process.exit(1); //D
  }
  var db = mongoClient.db("MyDatabase");  //E
  collectionDriver = new CollectionDriver(db); //F
});

Line A above assumes the MongoDB instance is running locally on the default port of 27017. If you ever run a MongoDB server elsewhere you’ll have to modify these values, but leave them as-is for this tutorial.

Line B creates a new MongoClient and the call to open in line C attempts to establish a connection. If your connection attempt fails, it is most likely because you haven’t yet started your MongoDB server. In the absence of a connection the app exits at line D.

If the client does connect, it opens the MyDatabase database at line E. A MongoDB instance can contain multiple databases, all which have unique namespaces and unique data. Finally, you create the CollectionDriver object in line F and pass in a handle to the MongoDB client.

Replace the first two app.get calls in index.js with the following code:

app.get('/:collection', function(req, res) { //A
   var params = req.params; //B
   collectionDriver.findAll(req.params.collection, function(error, objs) { //C
    	  if (error) { res.send(400, error); } //D
	      else { 
	          if (req.accepts('html')) { //E
    	          res.render('data',{objects: objs, collection: req.params.collection}); //F
              } else {
	          res.set('Content-Type','application/json'); //G
                  res.send(200, objs); //H
              }
         }
   	});
});
 
app.get('/:collection/:entity', function(req, res) { //I
   var params = req.params;
   var entity = params.entity;
   var collection = params.collection;
   if (entity) {
       collectionDriver.get(collection, entity, function(error, objs) { //J
          if (error) { res.send(400, error); }
          else { res.send(200, objs); } //K
       });
   } else {
      res.send(400, {error: 'bad url', url: req.url});
   }
});

This creates two new routes: /:collection and /:collection/:entity. These call the collectionDriver.findAll and collectionDriver.get methods respectively and return either the JSON object or objects, an HTML document, or an error depending on the result.

When you define the /collection route in Express it will match “collection” exactly. However, if you define the route as /:collection as in line A then it will match any first-level path store the requested name in the req.params. collection in line B. In this case, you define the endpoint to match any URL to a MongoDB collection using findAll of CollectionDriver in line C.

If the fetch is successful, then the code checks if the request specifies that it accepts an HTML result in the header at line E. If so, line F stores the rendered HTML from the data.jade template in response. This simply presents the contents of the collection in an HTML table.

By default, web browsers specify that they accept HTML in their requests. When other types of clients request this endpoint such as iOS apps using NSURLSession, this method instead returns a machine-parsable JSON document at line G. res.send() returns a success code along with the JSON document generated by the collection driver at line H.

In the case where a two-level URL path is specified, line I treats this as the collection name and entity _id. You then request the specific entity using the get() collectionDriver‘s method in line J. If that entity is found, you return it as a JSON document at line K.

Save your work, restart your Node instance, check that your mongod daemon is still running and point your browser at http://localhost:3000/items; you’ll see the following page:

web_emptyitems

Hey that’s a whole lot of nothing? What’s going on?

Oh, wait — that’s because you haven’t added any data yet. Time to fix that!

Working With Data

Reading objects from an empty database isn’t very interesting. To test out this functionality, you need a way to add entities into the database.

Add the following method to CollectionDriver.js and add the following new prototype method just before the exports.CollectionDriver line:

//save new object
CollectionDriver.prototype.save = function(collectionName, obj, callback) {
    this.getCollection(collectionName, function(error, the_collection) { //A
      if( error ) callback(error)
      else {
        obj.created_at = new Date(); //B
        the_collection.insert(obj, function() { //C
          callback(null, obj);
        });
      }
    });
};

Like findAll and get, save first retrieves the collection object at line A. The callback then takes the supplied entity and adds a field to record the date it was created at line B. Finally, you insert the modified object into the collection at line C. insert automatically adds _id to the object as well.

Add the following code to index.js just after the string of get methods you added a little while back:

app.post('/:collection', function(req, res) { //A
    var object = req.body;
    var collection = req.params.collection;
    collectionDriver.save(collection, object, function(err,docs) {
          if (err) { res.send(400, err); } 
          else { res.send(201, docs); } //B
     });
});

This creates a new route for the POST verb at line A which inserts the body as an object into the specified collection by calling save() that you just added to your driver. Line B returns the success code of HTTP 201 when the resource is created.

There’s just one final piece. Add the following line to index.js just after the app.set lines, but before any app.use or app.get lines:

app.use(express.bodyParser());

This tells Express to parse the incoming body data; if it’s JSON, then create a JSON object with it. By putting this call first, the body parsing will be called before the other route handlers. This way req.body can be passed directly to the driver code as a JavaScript object.

Restart your Node instance once again, and execute the following command in Terminal to insert a test object into your database:

curl -H "Content-Type: application/json" -X POST -d '{"title":"Hello World"}' http://localhost:3000/items

You’ll see the record echoed back to you in your console, like so:

term_create

Now head back to your browser and reload http://localhost:3000/items; you’ll see the item you inserted show up in the table:

web_createitem