5

I have a db.js file in which I set up a MongoDB connection. I would like to export the database object into my main app.js file:

// db.js
require('mongodb').MongoClient.connect(/* the URL */, function (err, db) {
    module.exports = db;
});

// app.js
var db = require('./db');

app.get('/', function (req, res) {
    db.collection(/* … */); // throws error
});

The error is:

TypeError: Object # has no method 'collection'

So, how can I export the db object properly?

Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
  • 1
    How about exporting like `exports=require('mongodb').MongoClient.connect;`. Then execute the request, and do whatever inside the callback? – elclanrs Jan 05 '14 at 21:37
  • On first glance it seems it should work. What do you see if you do console.log(db) after var db=require('./db') and inside the app.get callback? And did you try a console.log(db) inside the db.js connect callback? – Jan Misker Jan 05 '14 at 21:38
  • @elclanrs Sorry, I don't follow. – Šime Vidas Jan 05 '14 at 21:42
  • @JanMisker The first two console.log statements (both in app.js) return `{}`. However, inside the connect callback, the correct object is logged (the log is very long). – Šime Vidas Jan 05 '14 at 21:46
  • 3
    The call to `connect` is asynchronous, yet the `require` of the `db.js` file is synchronous. So, it would never be set. – WiredPrairie Jan 05 '14 at 21:56
  • possible duplicate of [How to reuse mongodb connection in node.js](http://stackoverflow.com/questions/17647779/how-to-reuse-mongodb-connection-in-node-js) – moka Jan 06 '14 at 10:00

4 Answers4

3

The best option, as suggested in the comments by elclanrs, is to export a promise:

// database.js
var MongoClient = require('mongodb').MongoClient,
    Q = require('q'),
    connect = Q.nbind(MongoClient.connect, MongoClient);

var promise = connect(/* url */);        

module.exports = {
  connect: function () {
    return promise;
  }
}

// app.js
var database = require('./database');

database.connect()
  .then(function (db) {
    app.get('/', function (req, res) {
      db.collection(/* … */);
    });
  })
  .catch(function (err) {
    console.log('Error connecting to DB:', err);
  })
  .done();

(I'm using awesome Q library here.)


Below's the old version of my answer, left for the sake of history (but if you don't want to use promises, instead of going that road, you should use Matt's answer).

Its downside is that it will open a connection each time you require('database.js) (bummer!)

// DO NOT USE: left for the sake of history

// database.js
var MongoClient = require('mongodb').MongoClient;

function connect(cb) {
  MongoClient.connect(/* the URL */, cb);
}

module.exports = {
  connect: connect
}

// app.js
var database = require('./database');

database.connect(function (err, db) {
  app.get('/', function (req, res) {
      db.collection(/* … */);
  });
});
Community
  • 1
  • 1
Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
2

You can't do it as you want to do it, because, quoting the docs:

Note that assignment to module.exports must be done immediately. It cannot be done in any callbacks.

Instead however, you can assign a property of module.exports in a callback, therefore this will work;

// db.js
require('mongodb').MongoClient.connect(/* the URL */, function (err, db) {
    module.exports.instance = db;
});

// app.js
var db = require('./db');

// some time later (when `.instance` is available)
app.get('/', function (req, res) {
    db.instance.collection(/* … */);
});

However, the some time later is a bit of a pain, so you may just want to use some sort of callback;

// db.js
var queue = [];
var instance = null;

require('mongodb').MongoClient.connect(/* the URL */, function (err, db) {
    instance = db;

    while (queue.length) {
        queue.pop()(instance);
    }
});

module.exports.done = function (callback) {
    if (instance === null) {
        queue.push(callback);
    } else {
        callback(instance);
    }
};

// app.js
require('./db').done(function (db) { 
    app.get('/', function (req, res) {
        db.collection(/* … */);
    });
});

The above also handles cases where handlers via done() are attached after the connection has already been made.

Matt
  • 74,352
  • 26
  • 153
  • 180
0

The servers typically have 3 phases: init, serve and uninit. This seems obvious but when you start writing servers from scratch (ie. in Java you start inheriting from HttpServlet) sometimes you forget how to do the things...

In the startup phase you must open the db connection (pool) and save the object somewhere (typically in your db.js module). Then in the service phase retrieve the mongodb connection from db.js.

Related: How to get a instance of db from node-mongo native driver?

Community
  • 1
  • 1
Gabriel Llamas
  • 18,244
  • 26
  • 87
  • 112
0

In your code:

// db.js
require('mongodb').MongoClient.connect(/* the URL */, function (err, db) {
    module.exports = db;
});

// app.js
var db = require('./db');

app.get('/', function (req, res) {
    db.collection(/* … */); // throws error
});

You've called connect in the db.js class, yet it's asynchronous.

The call in app.js to require is synchronous in behavior though, so it will always receive an undefined value (as the exports will not be assigned to a value at the time the db.js has finished executing).

I'd suggest keeping things simple.

The option I usually use is something where the app code makes the connection and doesn't start listening for HTTP connections until it is complete. Then, I'll initialize each route file by calling a named method and pass the database connection to it.

Or, you could just always call connect in each module, yet cache the value. (The connect call would need to be called within the route callback code so that the routes were defined immediately and not when the connection was actually established).

// db.js
var _db = null;
exports = function(callback) {
    if (!_db) {
        _db = {};   // only one connection, so we'll stop others from trying
        require('mongodb').MongoClient.connect(/* the URL */, function (err, db) {
            _db = db;
            callback(err, db);
        });
    } else {
        callback(null, _db);
    } 
};

// app.js

var db = require('./db');
db(function(err, connection) {
  // store the connection value here and pass around, or ...
  // call this always in each file ...
});
/// or ...

app.get('/', function (req, res) {
    db(function(err, connection) {
       connection.collection(/* … */); 
    });
});

Or, you could use MongooseJS (a wrapper for the native NodeJS MongoDB driver) where commands, etc. are queued if the connection isn't available yet ....

WiredPrairie
  • 58,954
  • 17
  • 116
  • 143