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();
} );
})