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