25

I using Node-Mongo-Native and trying to set a global connection variable, but I am confused between two possible solutions. Can you guys help me out with which one would be the good one? 1. Solution ( which is bad because every request will try to create a new connection.)

var express = require('express');  
var app = express();  
var MongoClient = require('mongodb').MongoClient;  
var assert = require('assert');

// Connection URL
var url = '[connectionString]]';

// start server on port 3000
app.listen(3000, '0.0.0.0', function() {  
  // print a message when the server starts listening
  console.log("server starting");
});

// Use connect method to connect to the server when the page is requested
app.get('/', function(request, response) {  
  MongoClient.connect(url, function(err, db) {
    assert.equal(null, err);
    db.listCollections({}).toArray(function(err, collections) {
        assert.equal(null, err);
        collections.forEach(function(collection) {
            console.log(collection);
        });
        db.close();
    })
    response.send('Connected - see console for a list of available collections');
  });
});
  1. Solution ( to connect at app init and assign the connection string to a global variable). but I believe assigning connection string to a global variable is a not a good idea.

    var mongodb; var url = '[connectionString]'; MongoClient.connect(url, function(err, db) {
    assert.equal(null, err); mongodb=db; } );

I want to create a connection at the app initialization and use throughout the app lifetime.

Can you guys help me out? Thanks.

NitinD
  • 489
  • 2
  • 7
  • 21
  • you can make one file which will only contain the db connection,then import that connection variable and use that imported variable wherever required – Udit Kumawat Mar 21 '18 at 05:43
  • @UditKumawat Yes I did that but this mongo library for Node has a callback function for the connection I need to use that so again I need to wait for it connect and then start the application I think. – NitinD Mar 21 '18 at 05:50
  • you can declare a global variable and then use that after initialize to connection variable – Udit Kumawat Mar 21 '18 at 05:56
  • Yes, I thought about it. But, I came across this [Using Global Variables in Node.js](http://stackabuse.com/using-global-variables-in-node-js/) – NitinD Mar 21 '18 at 06:00

7 Answers7

43

Create a Connection singleton module to manage the apps database connection.

MongoClient does not provide a singleton connection pool so you don't want to call MongoClient.connect() repeatedly in your app. A singleton class to wrap the mongo client works for most apps I've seen.

const MongoClient = require('mongodb').MongoClient

class Connection {

    static async open() {
        if (this.db) return this.db
        this.db = await MongoClient.connect(this.url, this.options)
        return this.db
    }

}

Connection.db = null
Connection.url = 'mongodb://127.0.0.1:27017/test_db'
Connection.options = {
    bufferMaxEntries:   0,
    reconnectTries:     5000,
    useNewUrlParser:    true,
    useUnifiedTopology: true,
}

module.exports = { Connection }

Everywhere you require('./Connection'), the Connection.open() method will be available, as will the Connection.db property if it has been initialised.

const router = require('express').Router()
const { Connection } = require('../lib/Connection.js')

// This should go in the app/server setup, and waited for.
Connection.open()

router.get('/files', async (req, res) => {
   try {
     const files = await Connection.db.collection('files').find({})
     res.json({ files })
   }
   catch (error) {
     res.status(500).json({ error })
   }
})

module.exports = router
Matt
  • 68,711
  • 7
  • 155
  • 158
  • Yes this is one of the methods to connect but I need to initialize this connection at app initialization and keep it alive for the app lifetime. With above method, you can connect, but you need call **Connection.connectToMongo()** function to connect on each request. – NitinD Mar 21 '18 at 07:53
  • 2
    You only need to call `connect` once in app initialisation, around `server.listen` or `app.listen`. Then the class acts as a singleton, in each place you `require` the connection class, the same (already connected) `database` property is available to use. – Matt Mar 21 '18 at 11:31
  • 1
    Thanks @Matt. appreciate it. – NitinD Mar 22 '18 at 02:21
  • I believe the line `if ( this.database ) return Promise.resolve(this.database)` will always resolve to false – Osama Salama Oct 25 '18 at 14:56
  • @OsamaSalama yep, fixed – Matt Oct 25 '18 at 21:13
  • 1
    Connection.db.collection('files').find({}) ^ TypeError: Cannot read property 'collection' of null – Gustavo Piucco Feb 09 '19 at 21:18
  • @GustavoPiucco I'm getting the same error which you got! Have you resolved this? `TypeError: Cannot read property 'collection' of undefined` – Kuppusamy Jan 25 '20 at 07:06
  • @Kuppusamy It's likely you haven't called the `connectToMongo` function before trying to access `Connection.db.collection` – Matt Jan 27 '20 at 23:32
  • is it possible to tweak this so that the database is not 'hardcoded' in the connection, eg for a local connection `Connection.url` would be `mongodb://localhost:27017`? so that the database value could be passed through to `Connection` just as the `collection()` value is? – user1063287 Nov 27 '21 at 14:12
  • update: fyi, i had a go at that tweak, it was too long for a comment, so i posted it as an answer, feel free to add the content to your answer if you like, as the logic is all from your answer. thanks. – user1063287 Nov 27 '21 at 15:01
  • The problem seemed to be that the db function is not being called so instead of Connection.db.collection you need to call the db as Connection.db.db().collection() – yousouf9 Dec 10 '21 at 16:57
  • This does not close the connection – Iglesias Leonardo Mar 05 '22 at 23:17
4

Another more straightforward method is to utilise Express's built in feature to share data between routes and modules within your app. There is an object called app.locals. We can attach properties to it and access it from inside our routes. To use it instantiate your mongo connection in your app.js file.

var app = express();

MongoClient.connect('mongodb://localhost:27017/')
.then(client =>{
  const db = client.db('your-db');
  const collection = db.collection('your-collection');
  app.locals.collection = collection;
});
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              // view engine setup
app.set('views', path.join(__dirname, 'views'));

This database connection, or indeed any other data you wish to share around the modules of you can now be accessed within your routes with req.app.locals as below without the need for creating and requiring additional modules.

app.get('/', (req, res) => {
  const collection = req.app.locals.collection;
  collection.find({}).toArray()
  .then(response => res.status(200).json(response))
  .catch(error => console.error(error));
});

This method ensures that you have a database connection open for the duration of your app unless you choose to close it at any time. It's easily accessible with req.app.locals.your-collection and doesn't require creation of any additional modules.

Hoppo
  • 1,130
  • 1
  • 13
  • 32
  • is there anyway you could instantiate mongo connection outside of `app.js`, for example in `config/database.js` (just to keep the file 'cleaner')? i have tried, but i get stuck thinking about how to access `app` within `config/database.js`. – user1063287 Nov 27 '21 at 12:40
2

This is how i did.

// custom class
const MongoClient = require('mongodb').MongoClient
const credentials = "mongodb://user:pass@mongo"

class MDBConnect {
    static connect (db, collection) {
        return MongoClient.connect(credentials)
            .then( client => {
                return client.db(db).collection(collection);
            })
            .catch( err => { console.log(err)});
    }
    static findOne(db, collection, query) {
        return MDBConnect.connect(db,collection)
            .then(c => {
                return c.findOne(query)
                            .then(result => {
                                return result;
                            });
            })
    }
    // create as many as you want
    //static find(db, collection, query)
    //static insert(db, collection, query)
    // etc etc etc
}
module.exports = MDBConnect;


// in the route file
var express = require('express');
var router = express.Router();
var ObjectId = require('mongodb').ObjectId; 
var MDBConnect =  require('../storage/MDBConnect');

// Usages
router.get('/q/:id', function(req, res, next) {
    let sceneId = req.params.id;
    
    // user case 1
    MDBConnect.connect('gameapp','scene')
        .then(c => {
            c.findOne({_id: ObjectId(sceneId)})
                .then(result => {
                    console.log("result: ",result);
                    res.json(result);
                })
        });
    // user case 2, with query
    MDBConnect.findOne('gameapp','scene',{_id: ObjectId(sceneId)})
        .then(result => {
            res.json(result);
        });
});
Ashish Kamble
  • 2,555
  • 3
  • 21
  • 29
Rolly
  • 3,205
  • 3
  • 26
  • 35
1

module version ^3.1.8

Initialize the connection as a promise:

const MongoClient = require('mongodb').MongoClient
const uri = 'mongodb://...'
const client = new MongoClient(uri)
const connection = client.connect()

And then summon the connection whenever you wish you perform an action on the database:

app.post('/insert', (req, res) => {
    const connect = connection
    connect.then(() => {
        const doc = { id: 3 }
        const db = client.db('database_name')
        const coll = db.collection('collection_name')
        coll.insertOne(doc, (err, result) => {
            if(err) throw err
        })
    })
})  
Henry Bothin
  • 243
  • 3
  • 6
  • Wont this create a new connection for every request? Idea was to re-use same connection across request/routes @henry-bothin – Vikalp Jain Oct 12 '19 at 07:45
0

In Express you can add the mongo connection like this

import {MongoClient} from 'mongodb';
import express from 'express';
import bodyParser from 'body-parser';
    let mongoClient = null;
    MongoClient.connect(config.mongoURL, {useNewUrlParser: true, useUnifiedTopology: true},function (err, client) {
        if(err) {
          console.log('Mongo connection error');
        } else {
          console.log('Connected to mongo DB');
          mongoClient = client;
        }
    })
let app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.use((req,res,next)=>{
    req.db = mongoClient.db('customer_support');
    next();
});

and later you can access it as req.db

router.post('/hello',async (req,res,next)=>{
    let uname = req.body.username;
    let userDetails = await getUserDetails(req.db,uname)
    res.statusCode = 200;
    res.data = userDetails;
    next();
});
sahad kkt
  • 3
  • 1
  • 4
0

I did a lot of research on the answer but couldn't find a solution that would convince me so I developed my own.

const {MongoClient} = require("mongodb");

class DB {
    static database;
    static client;

    static async setUp(url) {
        if(!this.client) {
            await this.setClient(url);
            await this.setConnection();
        }

        return this.database;
    }

    static async setConnection() {
        this.database = this.client.db("default");
    }

    static async setClient(url) {
        console.log("Connecting to database");
        const client = new MongoClient(url);

        await client.connect();

        this.client = client;
    }
}

module.exports = DB;

Usage:

const DB = require("./Path/to/DB");
(async () => {
  const database = await DB.setUp();
  const users = await database.collection("users").findOne({ email: "" });
});
0

Here is a version of Matt's answer that lets you define database as well as collection when using the connection. Not sure if it is as 'water tight' as his solution, but it was too long for a comment.

I removed Connection.options as they were giving me errors (perhaps some options are deprecated?).

lib/Connection.js

const MongoClient = require('mongodb').MongoClient;
const { connection_string } = require('./environment_variables');

class Connection {
  static async open() {
    if (this.conn) return this.conn;
    this.conn = await MongoClient.connect(connection_string);
    return this.conn;
  }
}

Connection.conn = null;
Connection.url = connection_string;

module.exports = { Connection };

testRoute.js

const express = require('express');
const router = express.Router();
const { Connection } = require('../lib/Connection.js');

Connection.open();

router.route('/').get(async (req, res) => {
    try {
        const query = { username: 'my name' };
        const collection = Connection.conn.db('users').collection('users');
        const result = await collection.findOne(query);
        res.json({ result: result });
    } catch (error) {
        console.log(error);
        res.status(500).json({ error });
    }
});

module.exports = router;

If you would like to take the middleware out of the route file:

testRoute.js becomes:

const express = require('express');
const router = express.Router();
const test_middleware_01 = require('../middleware/test_middleware_01');

router.route('/').get(test_middleware_01);

module.exports = router;

And the middleware is defined in middleware/test_middleware_01.js:

const { Connection } = require('../lib/Connection.js');

Connection.open();

const test_middleware_01 = async (req, res) => {
    try {
        const query = { username: 'my name' };
        const collection = Connection.conn.db('users').collection('users');
        const result = await collection.findOne(query);
        res.json({ result: result });
    } catch (error) {
        console.log(error);
        res.status(500).json({ error });
    }
};

module.exports = test_middleware_01;
user1063287
  • 10,265
  • 25
  • 122
  • 218