Using node-mongodb-native with express.js


I looked everywhere to find a good way to use node-mongodb-native with express.js and nothing looked right. I think its because everybody is using mongoose or mongoskin.

In node-mongodb-native, to get a database handle you want to use MongoClient.connect like this

MongoClient.connect(url, function(err, db) {

 // so here you have the db handle, now what?

});

I’ve seen some suggestions like this in app.js

MongoClient.connect(url, function(err, db) {
  app.set('db', db)
});

And now the handle is available in the routes like this

router.get('/', function(req, res, next) {

  var db = req.app.get('db');

});

But there’s a problem and you might never notice. MongoClient.connect is async. You have no idea that it will finish and assign db before the route gets called. In a live website this might never be a problem, but what if you use this code in testing where MongoClient.connect is called just milliseconds before router.get but the MongoClient.connect callback is called a few seconds after MongoClent.connect?

My Solution

MongoClient.connect(url, function(err, db) {
  app.set('db', db)
  // start express server here!
});

How is this possible without making a mess to the existing express.js code structure?

Its not that bad actually. First you have to take a sharp scalpel to the startup script bin/www

Change this

server.listen( port ); 
server.on( 'error', onError ); 
server.on( 'listening', onListening );  

To this ( simply add three lines )

require( '../lib/mongo_init' )( function( db ) {
  app.set( 'db', db );
  server.listen( port );
  server.on( 'error', onError );
  server.on( 'listening', onListening );
} )

And now create this file in lib/mongo_init.js. Please excuse all the bluebird and env stuff, its already been tested so I just copied it to here.

var MongoDB = require('mongodb');
var MongoClient = MongoDB.MongoClient;
require('bluebird').promisifyAll(MongoDB);
require('bluebird').promisifyAll(MongoClient);

module.exports = function(callback) {

  var env = process.env.NODE_ENV || 'development'; 
  var url = [ 'mongodb://localhost:27017', '/', 'tutorial', '-', env ].join( '' ); 

  console.log('mongo_init using ' + url )

  MongoClient.connectAsync( url ).then( function( db ) {   
    callback(db);  
  } );

}    

I took a shot at simplifying the above code but its untested.

module.exports = function(callback) {

  var url = 'mongodb://localhost:27017';
  MongoClient.connect( url , function( err, db ) {   
    callback(db); 
  } );

}

Testing scenario

This is how I’d use mongo_init with supertest and mocha.  (Note how done() is used to put the breaks on mongo_init before the unit test is run ( express.js doesn’t offer the done() because its just a kludge that uses setTimeout to force before() to take up to 2 seconds or until done() is called, I suppose this could have been used in express.js to some success)

var assert = require('chai').assert, 
var supertest = require('supertest'),
var mongo_init = require('../lib/mongo_init'), 
var app = require('../app')  

before( function( done ) {

  mongo_init(function(db){
    app.set('db', db);
    done();
  });

} );

test( 'super', function(done) {

  supertest( app )
  .get( '/api/shigoto' )
  .expect( 200 )
  .end( function( err, res ) {
    if ( err ) throw err;
      assert.equal('work', res.body.definition)
      done();
    } );

})
Advertisements

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 )

Google+ photo

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

Connecting to %s