Using MongoDB and Node.js with Express-Resource to create a REST service

A while back I had a problem I was trying solve.

I was using node.js with express-resource to create a REST (ish) web service and return data from mongodb.

My app looked something like this:

app.js (server)

var express = require('express');
var Resource = require('express-resource');
var app = express.createServer();

// create express-resource handler  which essentially does app.get('things', ...)
var things = app.resource('things', require('./things.js'));

app.listen(port);

things.js (request handler)

require('./sendThings');

// handle request  'http://example.com/things'
exports.index = function(request, response) {
  sendThings(db, response);
};

sendThings.js (handles mongodb queries)

var mongodb = require('mongodb');

// create database connection
var server = new mongodb.Server(host, port, {auto_reconnect: true});
var db = new mongodb.Db(dbName, server);

db.open(function (err, db) {
  if (err) { }
  // auto_reconnect will reopen connection when needed
});

function sendThings(db, response) {
  db.collection('things', function(err, collection) {
    collection.find(function(err, cursor) {
      cursor.toArray(function(err, things) {
        response.send(things);
      });
    });
  });
}

module.exports.sendThings = sendThings;

My main issue was that I had to pass my response to my mongodb function in order to send it — node.js being asynchronous.  Then my db handling function had to know what to do to send a response.  Not something I wanted, and very untestable with tight coupling.

I posted a question on StackOverflow.com describing the problem.
how to use events keep mongodb logic out of node.js request handlers

I only got one response that was somewhat less than useless. I reached out to co-workers who said things like “try these other frameworks“.

What I was really looking for was a way to register requests with an event handler so that the DB could then send a notification when it was done. Of course this would mean passing the db result to the event handler. A co-worker suggested looking at EventEmitter2. I was a bit worried that I would be reinventing the node.js event processing.

I settled on a strategy suggested by a co-worker of wrapping the response in a closure, and then adding functionality that knows how to send the response, handle errors, no result found, etc. So instead of passing the response, I pass a callback that contains the response. Not ideal, but good enough.

Here is what I came up with:

I used mongojs which greatly simplifies the mongodb interface –at the cost of flexibility in configuration– but it hides the nested callbacks the mongodb driver requires. It also makes the syntax much more like the mongo client.

I then wrap the HTTP Response object in a closure and pass this closure to the mongodb query method in a callback.

var MongoProvider = require('./MongoProvider');

exports.index = function(request, response){
  function sendResponse(err, data) {
    if (err) { 
      response.send(500, err);
    }    
    response.send(data);
  };
  MongoProvider.fetchAll(things, sendResponse);
};

It is still essentially just passing the response object to the database provider, but by wrapping it in a closure that knows how to handle the response, it keeps that logic out of my database module.

A slight improvement is to use a function to create a response handler closure outside my request handler:

function makeSendResponse(response){
  return function sendResponse(err, data) {
    if (err) {
      console.warn(err);
      response.send(500, {error: err});
      return;
    }

    response.send(data);
  };
}

So now my request handler just looks like this:

exports.index = function(request, response) {
  response.send(makeSendResponse(response));
}

And my MongoProvider looks like this:

var mongojs = require('mongojs');

MongoProvider = function(config) {
  this.configure(config);
  this.db = mongojs.connect(this.url, this.collections);
}

MongoProvider.prototype.configure = function(config) {
  this.url = config.host + "/" + config.name;
  this.collections = config.collections;
}

MongoProvider.prototype.connect = function(url, collections) {
  return mongojs.connect(this.url, this.collections);
}

MongoProvider.prototype.fetchAll = function fetchAll(collection, callback) {
  this.db(collection).find(callback);
}

MongoProvider.prototype.fetchById = function fetchById(id, collection, callback) {
  var objectId = collection.db.bson_serializer.ObjectID.createFromHexString(id.toString());
  this.db(collection).findOne({ "_id": objectId }, callback);
}

MongoProvider.prototype.fetchMatches = function fetchMatches(json, collection, callback) {
  this.db(collection).find(Json.parse(json), callback);
}

module.exports = MongoProvider;

I can also extend MongoProvider for specific collections to simplify the API and do additional validation:

ThingsProvider = function(config) {
  this.collection = 'things';
  this.mongoProvider = new MongoProvider(config);
  things = mongoProvider.db.collection('things');
}

ThingsProvider.prototype.fetchAll = function(callback) {
  things.fetchAll(callback);
}

//etc...

module.exports = ThingsProvider;

(I originally used util.extend, but decided that composition was more reliable because extend breaks instanceof and is unreliable)

I might continue looking for an event handling solution or build one myself. It seems a common enough use case, that I’m surprised there’s not a notification based web server framework

  • onRequest – register an EventHandler, create a Response. If no other events are registered send the “done” event which sends the Response and flushes the EventQueue.
  • onTimeout – send the Response, either all that has been built at this point, or an error — configurable. Possibly also send “202 Accepted”.
  • onEventQueueEmpty – send “done” Event and send the Response. Send Document Empty if response has had nothing added (204 No Content) and has no defaults.
  • registerEvent – add an Event to the Queue.
  • unregisterEvent – fails if unable to stop processing

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s