What is best way to handle global connection of Mongodb in NodeJs
Asked Answered
S

7

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.

Slavism answered 21/3, 2018 at 3:23 Comment(4)
you can make one file which will only contain the db connection,then import that connection variable and use that imported variable wherever requiredVidette
@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.Slavism
you can declare a global variable and then use that after initialize to connection variableVidette
Yes, I thought about it. But, I came across this Using Global Variables in Node.jsSlavism
G
44

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
Garibaldi answered 21/3, 2018 at 7:16 Comment(12)
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.Slavism
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.Garibaldi
Thanks @Matt. appreciate it.Slavism
I believe the line if ( this.database ) return Promise.resolve(this.database) will always resolve to falseLimn
@OsamaSalama yep, fixedGaribaldi
Connection.db.collection('files').find({}) ^ TypeError: Cannot read property 'collection' of nullBlankenship
@GustavoPiucco I'm getting the same error which you got! Have you resolved this? TypeError: Cannot read property 'collection' of undefinedLivingstone
@Livingstone It's likely you haven't called the connectToMongo function before trying to access Connection.db.collectionGaribaldi
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?Sanson
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.Sanson
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()Hectograph
This does not close the connectionDesex
P
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.

Ploughboy answered 3/6, 2020 at 9:29 Comment(1)
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.Sanson
M
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);
        });
});
Mills answered 16/3, 2019 at 15:37 Comment(0)
A
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
        })
    })
})  
Arioso answered 13/10, 2018 at 8:12 Comment(1)
Wont this create a new connection for every request? Idea was to re-use same connection across request/routes @henry-bothinDialyse
L
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();
});
Lawman answered 2/12, 2020 at 18:47 Comment(0)
S
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: "" });
});
Semination answered 14/10, 2021 at 15:6 Comment(0)
S
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;
Sanson answered 27/11, 2021 at 14:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.