15

I am having trouble understanding node.js.

Example, MongoDB access, here's what I've got (mydb.js):

var mongodb = require('mongodb'),
    server = new mongodb.Server('staff.mongohq.com', 10030, {
        auto_reconnect: true
    }),
    db = new mongodb.Db('mydb', server);

function authenticateAndGo(db, handle) {
    db.authenticate('username', 'password', function(err) {
        if (err) {
            console.log(err);
            return;
        }
        console.log('Database user authenticated');

        var collection = new mongodb.Collection(db, 'test');

        handle(collection);
    });
}

function query(handle) {
    db.open(function(err, db) {
        if( err ) {
            console.log(err);
            return;
        }
        console.log('Database connected');

        authenticateAndGo(db, handle);
    });
};
exports.query = query;

So, if I want to use it later, I would

var mydb = require('./mydb');
mydb.query(function(collection) {
    collection.find({}, {
        limit: 10
    }).toArray(function(err, docs) {
        console.log(docs);
    });
});

But, If I do multiple calls, like so:

var mydb = require('./mydb');
mydb.query(function(collection) {
    collection.find({}, {
        limit: 10
    }).toArray(function(err, docs) {
        console.log(docs);
    });
});
mydb.query(function(collection) {
    collection.find({}, {
        limit: 10
    }).toArray(function(err, docs) {
        console.log(docs);
    });
});

I get an exception:

Error: db object already connecting, open cannot be called multiple times

I think that there is really something fundamental that I do not understand about all this and it is probable that this question is stupid ...

Anyway, all help is welcome.

Thanks in advance.

pour toi
  • 1,026
  • 2
  • 13
  • 25

3 Answers3

10

mydb.js:

var mongodb= require('mongodb'),
  server = new mongodb.Server('staff.mongohq.com', 10030, {
    auto_reconnect: true
  }),
  db1 = new mongodb.Db('mydb', server);


// callback: (err, db)
function openDatabase(callback) {
  db1.open(function(err, db) {
    if (err)
      return callback(err);

    console.log('Database connected');

    return callback(null, db);
  });
}

// callback: (err, collection)
function authenticate(db, username, password, callback) {
  db.authenticate(username, password, function(err, result) {
    if (err) {
      return callback (err);
    }
    if (result) {
      var collection = new mongodb.Collection(db, 'test');

      // always, ALWAYS return the error object as the first argument of a callback
      return callback(null, collection);
    } else {
      return callback (new Error('authentication failed'));
    }
  });
}

exports.openDatabase = openDatabase;
exports.authenticate = authenticate;

use.js:

var mydb = require('./mydb');
// open the database once
mydb.openDatabase(function(err, db) {
  if (err) {
    console.log('ERROR CONNECTING TO DATABASE');
    console.log(err);
    process.exit(1);
  }

  // authenticate once after you opened the database. What's the point of 
  // authenticating on-demand (for each query)?
  mydb.authenticate(db, 'usernsame', 'password', function(err, collection) {
    if (err) {
      console.log('ERROR AUTHENTICATING');
      console.log(err);
      process.exit(1);
    }

    // use the returned collection as many times as you like INSIDE THE CALLBACK
    collection.find({}, {limit: 10})
    .toArray(function(err, docs) {
      console.log('\n------ 1 ------');
      console.log(docs);
    });

    collection.find({}, {limit: 10})
    .toArray(function(err, docs) {
      console.log('\n------ 2 ------');
      console.log(docs);
    });
  });
});

Result:

on success:

 Database connected
 Database user authenticated

------ 1 ------
[ { _id: 4f86889079a120bf04e48550, asd: 'asd' } ]

------ 2 ------
[ { _id: 4f86889079a120bf04e48550, asd: 'asd' } ]

on failure:

Database connected
{ [MongoError: auth fails] name: 'MongoError', errmsg: 'auth fails', ok: 0 }

[Original Answer]:

You're opening the db multiple times (once in each query). You should open the database just once, and use the db object in the callback for later use.

You're using the same variable name multiple times, and that might've caused some confusion.

var mongodb = require('mongodb'),
    server = new mongodb.Server('staff.mongohq.com', 10030, {
        auto_reconnect: true
    }),
    db1 = new mongodb.Db('mydb', server);

function authenticateAndGo(db, handle) {
    db.authenticate('username', 'password', function(err) {
        if (err) {
            console.log(err);
            return;
        }
        console.log('Database user authenticated');

        var collection = new mongodb.Collection(db, 'test');

        handle(collection);
    });
}

function query(handle) {
    db1.open(function(err, db2) {
        if( err ) {
            console.log(err);
            return;
        }
        console.log('Database connected');

        authenticateAndGo(db2, handle);
    });
};
exports.query = query;

I've changed the above code a little (db1 for the original db, db2 for the opened db). As you can see, you're opening db1 multiple times, which is not good. extract the code for opening into another method and use it ONCE and use the db2 instance for all your queries/updates/removes/...

Pooria Azimi
  • 8,585
  • 5
  • 30
  • 41
  • They are all in a different scope!? – pour toi Apr 12 '12 at 06:58
  • @TupakGoliam: yes, I meant they might've caused *you* confusion *mentally* (looking at code and understanding it). I'll change my answer in a bit for a complete and working example. – Pooria Azimi Apr 12 '12 at 07:30
  • @TupakGoliam I changes my answer now. Let me know if it's not working. I added a lot of comments and changed the structure of your code and made it more *standard*. – Pooria Azimi Apr 12 '12 at 09:18
8

You can only call "open" once. When the open callback fires, you can then do your queries on the DB object it returns. So one way to handle this is to queue up the requests until the open completes. e.g MyMongo.js

var mongodb = require('mongodb');

function MyMongo(host, port, dbname) {
    this.host = host;
    this.port = port;
    this.dbname = dbname;

    this.server = new mongodb.Server(
                              'localhost', 
                              9000, 
                              {auto_reconnect: true});
    this.db_connector = new mongodb.Db(this.dbname, this.server);

    var self = this;

    this.db = undefined;
    this.queue = [];

    this.db_connector.open(function(err, db) {
            if( err ) {
                console.log(err);
                return;
        }
        self.db = db;
        for (var i = 0; i < self.queue.length; i++) {
            var collection = new mongodb.Collection(
                                 self.db, self.queue[i].cn);
            self.queue[i].cb(collection);
        }
        self.queue = [];

    });
}
exports.MyMongo = MyMongo;

MyMongo.prototype.query = function(collectionName, callback) {
    if (this.db != undefined) {
        var collection = new mongodb.Collection(this.db, collectionName);
        callback(collection);
        return;
    }
    this.queue.push({ "cn" : collectionName, "cb" : callback});
}

and then a sample use:

var MyMongo = require('./MyMongo.js').MyMongo;

var db = new MyMongo('localhost', 9000, 'db1');
var COL = 'col';

db.query(COL, function(collection) {
    collection.find({}, {
        limit: 10
    }).toArray(function(err, docs) {
        console.log("First:\n", docs);
    });
});


db.query(COL, function(collection) {
    collection.find({}, {
        limit: 10
    }).toArray(function(err, docs) {
        console.log("\nSecond:\n", docs);
    });
});
Tom J
  • 101
  • 1
  • 1
    This kinda complicates, what I thought to be, a simple task... Queue the requests by myself... doesn't MongoDB/node-mongodb-native support this sort of stuff? – pour toi Apr 12 '12 at 07:01
  • 1
    The problem is the mongodb.js only allows "open" to be called once, and you can not use that connection until the open completes. So you need some way to track if open has been called, and to avoid using the connection until it is ready. So you either need to manage this by the control flow in your app (e.g. doing everything in the open callback) of you keep some extra state around to manage it for you (the object/queue above) – Tom J Apr 12 '12 at 13:37
  • 1
    +1 because it fixed an annoying problem I was having, good answer. – Eoin Murray Jun 06 '12 at 15:30
1

I simply call the open function once directly after the db init:

var mongodb = require('mongodb');
var server = new mongodb.Server('foo', 3000, {auto_reconnect: true});
var db = new mongodb.Db('mydb', server);   
db.open(function(){});

After that I do not have to care about that anymore because of auto_reconnect is true.

db.collection('bar', function(err, collection) { [...] };
user470370
  • 572
  • 1
  • 5
  • 17
  • I don't think the code works, since I have checked it several times. The db.open need time to execute, so it could't use it. so you have to use it in the callback method. – ricky Aug 24 '13 at 15:35