1

I am trying to get MongoDB to work with Express.js. I have followed this article and created a file db/index.js that provides functions for creating a database connection and returning the connection instance. In app.js I require that file first, then call its function connect, which successfully connects to the database.

However, when I require the file db/index.js later in another file routes/users.js, the database reference it returns is null, although I expected it to be the same reference that I got when I connected in app.js. Doesn't require always return the same instance of the module that is returned on the first call to require? What am I doing wrong?

EDIT: I understand that because the call db.connect is asynchronous, the value of db in users.js might in theory be null before the connecting function returns. However, I have verified via testing that even if connection is successfully created, db is still null when I do a POST request to path /users.

Here are the 3 relevant files.

app.js (execution starts here)

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

// configuration  environment based on process.env.NODE_ENV environment variable
var config = require('./config');
var db = require('./db');

var app = express();

var routes = require('./routes/index');
var users = require('./routes/users');
app.use('/', routes);
app.use('/users', users);

// Connect to Mongo on start
db.connect(config.dbUrl, function (err) {
    if (err) {
        console.log('Unable to connect to ' + config.dbUrl);
        process.exit(1);
    } else {
        console.log('Connected to ' + config.dbUrl);
    }
});

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/tests', express.static(__dirname + '/test'));
app.use('/lib/jasmine-core', express.static(__dirname + '/node_modules/jasmine-core/lib/jasmine-core'));

// catch 404 and forward to error handler
app.use(function (req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
    app.use(function (err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

// production error handler
// no stacktraces leaked to user
app.use(function (err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});


module.exports = app;

db/index.js

var MongoClient = require('mongodb').MongoClient;

var state = {
    db: null
};

exports.connect = function (url, done) {
    if (state.db)
        return done();

    MongoClient.connect(url, function (err, db) {
        if (err)
            return done(err);
        state.db = db;
        done();
    })
}

exports.get = function () {
    return state.db;
}

exports.close = function (done) {
    console.log('db close');
    if (state.db) {
        state.db.close(function (err, result) {
            state.db = null;
            done(err);
        })
    }
}

exports.createIndexes = function () {
    if (state.db) {
        state.db.collection('events').createIndex(
                {'id': 1},
        {unique: true});
    }
}

routes/users.js

var express = require('express');
var router = express.Router();
var db = require('../db').get();

router.post('/', function (req, res, next) {
    //
    // Problem: Every time this function is called, db is null!
    //
    var users = db.collection('users');
    var userToAdd = req.body;

    users.findOne({username: userToAdd.username}).then(function (foundUser) {
        if (foundUser.length === 0) {
            res.status(400).json({error: 'Username is already in use'});
        } else {
            // TODO: ADD USER
            res.json(foundUser);
        }
    })
    .catch(function (err) {
        console.log(err);
        res.status(500).json({error: 'Database error.'});
    });
});

module.exports = router;
  • Possible duplicate of [How do I return the response from an asynchronous call?](http://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Blakes Seven Feb 26 '16 at 23:19
  • I don't think this is a duplicate. I edited the question a little. –  Feb 26 '16 at 23:33
  • @BenjaminGruenbaum there is no call to `.connect()` and an assumption that `db` is intialized. Nothing in the listing attempts to do anything other than `require('../db').get();`. So I don't believe the OP understands how to place the async call at all. Nor do I see anything to prompt an answer that is anything else other than "understand async calls". – Blakes Seven Feb 26 '16 at 23:50
  • @BlakesSeven My perhaps incorrect understanding of node modules is that you get the same instance when you require it twice, so even though the reference is null at first, when the connect function returns, other modules (users.js) should see the new value. Is this not so? I wrote this a little lazily because I am only trying this thing now. I know that it is not guaranteed that connection gets established, but I have checked that it happens in less than a second and the reference is still null after that. –  Feb 27 '16 at 00:10
  • 1
    I get that you are asking for this to "persist", so I either make a choice to broaden your understanding of callbacks ( ie all your code in one option should be contained within the database connection call ) or B. Note that "designing" code to your purpose is very very **broad** as a topic. So it's "pick a card" for a close reason. If you are set on a "magic persisted connection" then spend hours studying the [mongoose codebase](https://github.com/Automattic/mongoose). Then you might get some concept of the things that are not happening like you think they are. – Blakes Seven Feb 27 '16 at 00:16
  • Ok, so the connection is not kept open when connect() returns, and the reference to that connection is assigned null. I must investigate this more. Thanks! –  Feb 27 '16 at 01:00
  • For someone reading this later -- my reasoning above is incorrect. [This](http://stackoverflow.com/questions/35663465/i-seem-to-get-a-new-module-instance-when-calling-require#answer-35666131) fixed the problem. –  Feb 27 '16 at 19:26

2 Answers2

1

For the record, you are not receiving "new" instance, following explanation will help you make sense what is happening.

Notice that you are requiring your routes before connecting to your db via db.connect. i.e.

//Called before db.connect is called.
//so at this point in time, your db.get will return null, make sense?
//because in your users.js your are directly calling var db = require('../db').get();
var routes = require('./routes/index');
var users = require('./routes/users');
app.use('/', routes);
app.use('/users', users); 

// Connect to Mongo on start
db.connect(config.dbUrl, function (err) {
    if (err) {
        console.log('Unable to connect to ' + config.dbUrl);
        process.exit(1);
    } else {
        //after this point in execution, calling db.get will return actual db and not null.
        console.log('Connected to ' + config.dbUrl);
    }
});

There are two solutions to your problem.

1.

Restructure your app.js code like this will solve your issue.

// Connect to Mongo on start
db.connect(config.dbUrl, function (err) {
    if (err) {
        console.log('Unable to connect to ' + config.dbUrl);
        process.exit(1);
    } else {
        //As now we have db initialised, lets load routes.
        var routes = require('./routes/index');
        var users = require('./routes/users');
        app.use('/', routes);
        app.use('/users', users);
        console.log('Connected to ' + config.dbUrl);
    }
});

2

Leave your app.js as it is and make changes in your routes, for e.g., users.js use db as following,

var express = require('express');
var router = express.Router();
var dbFetcher = require('../db');

router.post('/', function (req, res, next) {
    //
    // Problem: Every time this function is called, db is null!

    var db = dbFetcher.get();
    var users = db.collection('users');
    var userToAdd = req.body;

    users.findOne({username: userToAdd.username}).then(function (foundUser) {
        if (foundUser.length === 0) {
            res.status(400).json({error: 'Username is already in use'});
        } else {
            // TODO: ADD USER
            res.json(foundUser);
        }
    })
    .catch(function (err) {
        console.log(err);
        res.status(500).json({error: 'Database error.'});
    });
});

module.exports = router;

I hope it makes sense and helps!

Dave Amit
  • 2,299
  • 12
  • 17
  • This helps a lot, thank you! Great explanation, now I see clearly what's going on. Solution #2 is exactly the fix I was trying to find. I had tried something like #1 before, but it had been too late to add routes to Router there, and I got 404's, because `http.server` had already been started elsewhere. But I see that it is more robust to start accepting http connections after the database connection has been created, and so I wrapped the call `http.server.listen()` (in a startup script `bin/www`) inside the callback for `db.connect()`. –  Feb 27 '16 at 19:15
  • @typhon I'm glad I was able to help! Keep up the good work ... :) – Dave Amit Feb 27 '16 at 19:17
-1

The line var db = require('./db'); declares a new variable which only exists within the scope of that file. When you make similar declaration in your routes/user file, you create a completely new db variable, which means that the connect method was never run and so the state property was never changed.

Chandler Freeman
  • 899
  • 1
  • 10
  • 25
  • 1
    I checked this from the docs and I think you get the exactly same "instance of the module" that you *export*, when you *require* it. I also tried it by assigning `state.a = 1` in the initial assignment of `state`, incrementing `state.a` by one and printing it in every call of an exported function of the module, and I could confirm that different files see the same state of the module.. –  Feb 27 '16 at 01:22
  • @typhon you are correct, require always returns same exported object (singleton). Look at my answer for explanation and solution to your problem. – Dave Amit Feb 27 '16 at 09:03