Build an API with Node.js, Express, MongoDB and Cloud Foundry

I was finalist in the LinkedIn Hackday hackathon last November with my "Mobile Chow Finder" app using jQuery Mobile, Database.com and Ruby on Rails on Heroku. I really liked the Chow Finder use case but wanted to make it more of a finished application; not just something I threw together in a matter of hours. So I decided to start off by writing an API with Node.js and MongoDB and host it on Cloud Foundry. I'm going to build a website running off that API with something like Sinatra or Rails. I also want to eventually build HTML5, Android and iOS applications running off this API too.

So I put together a video of the initial process of building the API. It's definitely a work in progress. You can clone the code from this repo if you find it useful. It assumes that you have Node.js, MongoDB, Express and the Cloud Foundry Command-Line Interface (vmc) installed.

The API has the following methods around two objects: locations and facilities.

GET /locations -- returns a list of locations
GET /locations/:id -- returns a location
POST /locations -- creates a location
GET /locations/favorites -- returns list of favorite locations for a user
POST /locations/favorites -- creates a new favorite for a user
GET /locations/:id/facilities -- returns a list of facilities for a location
POST /locations/:id/facilities -- creates a new facility for a location
GET /locations/:id/facilities/:id -- returns a facility
PUT /locations/:id/facilities/:id -- updates a facility

You can check out code for app.js on github, but most of the interesting stuff is around the connection to MongoDB and the code for the actual methods.

Once you installed the MongoDB native Node.js driver, you just need to create your connection to either your localhost or Mongo running on Cloud Foundry in app.js.

if(process.env.VCAP_SERVICES){
 var env = JSON.parse(process.env.VCAP_SERVICES);
 var mongo = env['mongodb-1.8'][0]['credentials'];
}
else{
 var mongo = {
  "hostname":"localhost",
  "port":27017,
  "username":"",
  "password":"",
  "name":"",
  "db":"db"
 }
}

var generate_mongo_url = function(obj){
 obj.hostname = (obj.hostname || 'localhost');
 obj.port = (obj.port || 27017);
 obj.db = (obj.db || 'test');

 if(obj.username && obj.password){
  return "mongodb://" + obj.username + ":" + obj.password + "@" + obj.hostname + ":" + obj.port + "/" + obj.db;
 }
 else{
  return "mongodb://" + obj.hostname + ":" + obj.port + "/" + obj.db;
 }
}

var mongourl = generate_mongo_url(mongo);

Now the API itself. What's great about Node and Mongo are that they both talk JSON. So in this method, we POST some JSON and simply insert it into the 'locations' collection.

// creates a location in the 'locations' collection
app.post('/v.1/locations', function(req, res){
 require('mongodb').connect(mongourl, function(err, conn){
  conn.collection('locations', function(err, coll){
 coll.insert( req.body, {safe:true}, function(err){
 res.writeHead(200, {
  "Content-Type": "application/json",
  "Access-Control-Allow-Origin": "*"
 });
 res.end(JSON.stringify(req.body));
 });
  });
 });
 });

To return all of the locations in the collection, we issue the find() command which returns a cursor object to the callback which is passed to the responses as an array of documents.

// returns list of locations
app.get('/v.1/locations', function(req, res){

 require('mongodb').connect(mongourl, function(err, conn){
  conn.collection('locations', function(err, coll){
 coll.find(function(err, cursor) {
 cursor.toArray(function(err, items) {
  res.writeHead(200, {
   "Content-Type": "application/json",
   "Access-Control-Allow-Origin": "*"
  });
  res.end(JSON.stringify(items));
 });
 });
  });
 });

});

To return a specific location (as a document) from Mongo, we use the findOne() command and pass in the location's id from the URL.

// returns a specific location by id
app.get('/v.1/locations/:location_id', function(req, res){

 var ObjectID = require('mongodb').ObjectID;

 require('mongodb').connect(mongourl, function(err, conn){
  conn.collection('locations', function(err, coll){
 coll.findOne({'_id':new ObjectID(req.params.location_id)}, function(err, document) {
 res.writeHead(200, {
  "Content-Type": "application/json",
  "Access-Control-Allow-Origin": "*"
 });
 res.end(JSON.stringify(document));
 });
  });
 });

});

To create a facility for a location, we POST the entire JSON for the facility and then add the id for the parent location from the URL before inserting it into the collection.

// creates a new facility for a location
app.post('/v.1/locations/:location_id/facilities', function(req, res){

 // add the location id to the json
 var facility = req.body;
 facility['location'] = req.params.location_id;

 require('mongodb').connect(mongourl, function(err, conn){
  conn.collection('facilities', function(err, coll){
 coll.insert( facility, {safe:true}, function(err){
 res.writeHead(200, {
  "Content-Type": "application/json",
  "Access-Control-Allow-Origin": "*"
 });
 res.end(JSON.stringify(facility));
 });
  });
 });

});

The last method updates a facility document with the findAndModify() method. The method finds the facility by id and updates the data PUT in the JSON payload.

// updates a facility
app.put('/v.1/locations/:location_id/facilities/:facility_id', function(req, res){

 var ObjectID = require('mongodb').ObjectID;

 require('mongodb').connect(mongourl, function(err, conn){
  conn.collection('facilities', function(err, coll){
 coll.findAndModify({'_id':new ObjectID(req.params.facility_id)}, [['name','asc']], { $set: req.body }, {}, function(err, document) {
 res.writeHead(200, {
  "Content-Type": "application/json",
  "Access-Control-Allow-Origin": "*"
 });
 res.end(JSON.stringify(document));
 });
  });
 });

});