Export from MongoClient.connect() is not being read in other module

I am successfully connecting to Mongo in my index.js file. From there I am trying to set the client to a variable and export it to another module. I have tried 3 ways and I am not wrapping my head around how to get the resolved promise exported for use in another module where my data access methods are that my controller uses.

Method 1. An async function and then function call.
index.js

const port = process.env.PORT || 8000
const uri = process.env.COOKIE_DB_URI
const client = new MongoClient(uri, {
  useNewUrlParser: true,
  poolSize: 50,
  useUnifiedTopology: true,
})

let db = null

async function connectToMongoDB() {
  try {
    await client.connect()
    await listDatabases(client) // just a local function to console databases

    app.listen(port, () => {
      console.log(`listening on port ${port}`)
    })
  } catch (e) {
    console.error(`Index.js ~~~ Unable to get database.collection: ${e}`)
  } finally {
    console.log('Mongo Connection is closing ~~~~')
    await client.close()
  // }
}

connectToMongoDB().catch(console.error)

I want to set db = client.connect() resolved promise, I wasn’t able to get it to work

Method 2. Mongo connect with callback

MongoClient.connect(
  uri,
  {
    useNewUrlParser: true,
    poolSize: 50,
    useUnifiedTopology: true,
  },
  function (err, database) {
    if (err) {
      console.error(err.stack)
      process.exit(1)
    }
    console.log(
      'line 72 index.js',
      database.db('cookieBiz').collection('orders') // can see db
    )
    listDatabases(database)
    OrdersDAO.injectDB(database)
    db = database // doesn't work
  }
)


export default db

According to the docs, “callback will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the initialized db object or null if an error occured.” So if the callback is executed after the Mongo.connect(), is the promise resolved by the time the callback is executed? If so why can I not set db variable to database param and then export it?

Method 3 is similar to above except I use a then()

MongoClient.connect(
  uri,
  {
    useNewUrlParser: true,
    poolSize: 50,
    useUnifiedTopology: true,
  })
.then(async (client) => {
  listDatabases(client)
  await OrdersDAO.injectDB(client)
  db = client // doesn't work
  app.listen(port, () => {
    console.log(`listening on port ${port}`)
  })
})
.catch((err) => {
  console.error(err.stack)
  process.exit(1)
})

export default db

And in my OrdersDAO.js, similar to M220js course

import db from '../index.js'

  static async getOrders() {
    try {
      // how do I get into the collection here? to get database and collection
      console.log(
        'OrdersDAO.getOrders ~~~ made it to the OrdersDAO step 2',
        db // nope, still not available!
      )

    } catch (e) {
      console.error(`OrdersDAO ~~~ Unable to get orders: ${e}`)
      return { e }
    }
  }

So with all three versions I am still not sure how to be able to use this connection in another file with a simple export? My understanding is that the connection is opened and the pool is used to so that there is not a constant opening and closing of connections hitting, in my case Atlas. I looked here, StackOverflow, the docs, docs, and I tried emulating the pattern from M200js in Mongo University. I am getting confused and moving in circles.

Rafael, I missed your reply. What were your thoughts?

Hello @Kevin_Turney, welcome back to MongoDB Community forum!

I have small demo of what you might be looking for. The app has two modules, the main index.js and a dbaccess.js . The dbaccess module exports the MongoDB clinet connection which can be used in other modules, like in index.js.

The example app just connects, reads from a collection, prints to the console and closes the connection.

index.js:

const client = require('./dbaccess');

(async function() {
	try {
		console.log('Accessed dbaccess module in index.js');
		// Substitute your db and collection names here...
		const db = client.db('test');
		const cursor = await db.collection('users').find();
		while(await cursor.hasNext()) {
			let doc = await cursor.next();
			console.log(doc);
		}
	} catch (err) {
		console.log(err.stack);
	}

	client.close(); // Close db connection
	console.log('Connection closed.');
})();

dbaccess.js

const MongoClient = require('mongodb').MongoClient;
const url = 'mongodb://localhost:27017';
const client = new MongoClient(url, { useUnifiedTopology: true } );
client.connect(function(err) {
	console.log('Connected successfully to server');
});
module.exports = client;

I don’t understand that line of code:

Database is not a client.
Did you mean to write this:

db = await client.db("your_db_name")

In addition I don’t understand why do you want to export db from your index.js.
after you run this line:

await OrdersDAO.injectDB(client)

a reference to the database will be saved in the variable (with file scope) which you assigned this value to it inside the function OrdersDAO.injectDB

Thanks,
Rafael,

Rafael, for line 1 referring to Method 3 in the post:

    db = client

My intention was after MongoClient.connect() promise is completed, in the “then async(client)”, I wanted to set the global variable, db, to the client and then export it. That was my intention. Yes client is injected into OrdersDAO.injectDB() method, but the static getOrders() method in the class is not getting client passed as an arg. Below is the class for OrdersDAO.js for context.

OrdersDAO.js

let orders

export default class OrdersDAO {
  static async injectDB(clientConnection) {
    if (orders) {
      return
    }
    try {
// this line does not allow the variable orders to be seen in getOrders()
      orders = await clientConnection.db('cookieBiz').collection('orders')
      console.log('OrdersDAO ~~~ line 10 ordersDAO connecting....',)

      const database = clientConnection.db('cookieBiz')
      const collection = database.collection('orders')

      const cursor = collection.find({})
      const allOrders = await cursor.toArray()
      console.log(`OrdersDAO ~~~ line 87`, allOrders) // everything is visible here, perfect!

      console.log(`You connected you magnificent bastard!!!`, collection)
    } catch (e) {
      console.error(`Unable to establish collection handles in OrdersDAO: ${e}`)
    }
  }

  static async getOrders() {
    try {
       orders = await clientConnection.db('cookieBiz').collection('orders')
      console.log(
        'OrdersDAO.getOrders ~~~ made it to the OrdersDAO step 2',
        orders)

      // how do I get into the collection here? injectDB() doesn't expose the client

    } catch (e) {
      console.error(`OrdersDAO ~~~ Unable to get orders: ${e}`)
      return { e }
    }
  }
}

This is the error when trying to read orders in getOrders()

OrdersController  ~~~ made it to the controller step 1
OrdersDAO ~~~ Unable to get orders: ReferenceError: clientConnection is not defined

If I do

db = await client.db('cookieBiz')

and try to export it, it still comes up as null in injectDB(). “db” is trying to be read before the promise is completed.

remove these lines from getOrders:

and add this:

let cursor = await orders.find()
let allOrders = cursor.toArray()

That leads to

OrdersDAO ~~~ Unable to get orders: TypeError: Cannot read property 'find' of undefined
line 12 ordersController {
  e: TypeError: Cannot read property 'find' of undefined

I think your problem comes from the misunderstanding of let vs var in JavaScript.

See let - JavaScript | MDN

In particular :

At the top level of programs and functions, let , unlike var , does not create a property on the global object. For example:

Did you remove the lines I mentioned from getOrders and kept them only in injectDB?

Thanks,
Rafael,

I am familiar and I did try it.

I removed what you said from getOrders() and added the 2 lines to getOrders(). There was not mention of injectDB.

Rafael, the pattern I was tring to copy and experiment with can be seen here, mongo university mflix

Specifically look at index.js in /src and then how the author gains access to the client in the /dao files (data access objects).

Also, note how in moviesDAO.js, in “getMoviesByCountry()” or “getMovies()”, author initializes ‘cursor’ and then

let cursor
try {
cursor = await movies.find(query)
...

movies?? how is the connected client available? Is it set in injectDB()? I’m literally trying the exact same thing am gettitng “OrdersDAO ~~~ Unable to get orders: TypeError: Cannot read property ‘find’ of undefined”, and am missing something.
Nothing is exported, and yet there is database access in the methods in movies, comments etc.

mflix-js/src/index.js:

// This is the code that creates the database connection
// at the start of the app:
...
await MoviesDAO.injectDB(client)
...

mflix-js/src/MoviesDAO.js:

// This is the code that uses the database connection 
// to access movies collection and query, ...
static async injectDB(conn) {
    ...
    movies = await conn.db(process.env.MFLIX_NS).collection("movies")
    ...

Note the DAO code runs when you try some functionality, like query by a movie name, etc. The client connection is by then created, when the app starts. So, the connection is available in the DAO class via the injectDB static method.


Nothing is exported, and yet there is database access in the methods in movies,…

Note the statement in the index.js which does the import of the class:

import MoviesDAO from "../src/dao/moviesDAO"

Prasad,

I understand that the connection is established when calling MongoClient.connect()… the response from that promise, client is passed to “then(client)”. It, client, is then passed as an arg to injectDB(client).

In the top of the MoviesDAO.js,

let movies

and then in injectDB,we have

 try {
      mflix = await conn.db(process.env.MFLIX_NS)
      movies = await conn.db(process.env.MFLIX_NS).collection("movies")

First MoviesDAO is imported to index.js just to be able to pass the argument. The connection is made by MongoClient.connect(). It appears that injectDB() is used as a proxy to recieve “client” and set that value to the movies variable declared at the top of MoviesDAO.js

Second calling getMovies() or ant other method makes no calls or references to injectDB(). InjectDB is merely to set movies var…as you can see “movies” being used elsewhere. So in my case, I am declaring the variable orders and in injectDB() trying to set its value to be used elsewhere, like movies. For me orders is still undefined when getOrders() is called.

Hello @Kevin_Turney,

In your first post, and the method 3 - you don’t need the following two lines of code.

db = client // doesn't work
export default db

The MongoClient.connect(... creates a database connection (in fact a pool of connections called as “connection pool”, with an initial default value of 10 (I think in case of NodeJS driver)). This single connection object, referred by the variable client, is to be used thru the application and in different modules, like OrdersDAO.

The following statement already uses the connection object in the OrdersDAO class, to create an instance to the orders collection defined as let orders;.

await OrdersDAO.injectDB(client)

If you want to use the same database connection in other modules (for example, in CustomersDAO), you have to do the following in the index.js. The CustomersDAO will have similar structure like in OrdersDAO (or MoviesDAO).

await CustomersDAO.injectDB(client)

But, that is already in the mflix-js application.


If you are looking to use a database connection throughout the application in various modules, you need to change your approach little bit.

  • Define a dbaccess module to store/create/maintain the single connection.
  • Initialize dbaccess to create a connection object in the main application startup program index.js (Note the connection object is stored within the dbaccess).
  • Then, use the initialized/created connection in all other modules throughout the application (by requiring the dbaccess).

I appreciate you staying with me on this Prasad. However you are saying things already evident and not coming up with a workable solution or why something is not working.

Here, Mongo docs, connection pooling… I tried the db approach as seen below.

var express = require('express');
var mongodb = require('mongodb');
var app = express();

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

// Initialize connection once
MongoClient.connect("mongodb://localhost:27017/integration_test", function(err, database) {
  if(err) throw err;

  db = database;

  // Start the application after the database connection is ready
  app.listen(3000);
  console.log("Listening on port 3000");
});

// Reuse database object in request handlers
app.get("/", function(req, res) {
  db.collection("replicaset_mongo_client_collection").find({}, function(err, docs) {
    docs.each(function(err, doc) {
      if(doc) {
        console.log(doc);
      }
      else {
        res.end();
      }
    });
  });
});

Then in my last reply to you, I already explained to you exactly how injectDB works.

" If you want to use the same database connection in other modules (for example, in CustomersDAO ), you have to do the following in the index.js . The CustomersDAO will have similar structure like in OrdersDAO (or MoviesDAO )."

Yes this is how they use it in this application, however using this EXACT same patten, I am not getting to work.

Using injectDB(client) saves creating another module to eventually do the same thing.

The connection object passed to the OrdersDAO class, is assigned to orders, YES I know that, but if you try this, and call getOrders(), orders will still be undefined.

OrdersDAO.getOrders() ~~~ Unable to get orders: TypeError: Cannot read property 'db' of undefined

My theory is this has to be due to the asynchronous nature of the the call and how the variable “orders” is being read before initialization. Feel free to try a solution, because I have been trying hard and I’m becoming skeptical of this injectDB() pattern.

I checked the versions in package.json, checked babel plugins and presets, checked the tests folder to see if I missed something, I retyped both files just in case, I even sent a linkedin message to the author in the mflix commits. I’m stuck.

In the OrdersDAO the let orders declaration - the orders is the collection instance. This collection instance is obtained from the client connection in the injectDB() method:

orders = await clientConnection.db('cookieBiz').collection('orders');

In the OrdersDAO.getOrders() method you can use the orders (the collection instance) to query upon. For example:

cursor = await orders.find().limit(10);
// and you use the cursor, to read the data, and use it in the app.

Note that the injectDB is a static method, and the getOrders is an instance method.

Prasad, I have tried that, please see earlier in the post, how do you think I got undefined

static async getOrders() {
    let cursor
    try {
      // await client.connect()
      const database = await orders.db('cookieBiz') // TypeError - cannot read db of undefined
      const collection = database.collection('orders')

      console.log(`OrdersDAO ~~~ 94`, db)
      let cursor = await orders.find({})
      let allOrders = cursor.toArray()
      console.log(
        'OrdersDAO.getOrders ~~~ made it to the OrdersDAO step 2',
        allOrders)

    } catch (e) {
      console.error(`OrdersDAO.getOrders() ~~~ Unable to get orders: ${e}`)
      return { e }
    }
  }

from the console…

OrdersDAO.getOrders() ~~~ Unable to get orders: TypeError: Cannot read property 'db' of undefined

The above two lines of code are not required. The orders variable already has a collection’s reference obtained in the injectDB (think orders is the collection on which you run the query; it is this Collection class type).

Just tried your line

cursor = await orders.find().limit(10);

orders is still undefined. I don’t know, dude : ( ??!!

orders is read perfectly in injectDB(), just not getting the collection reference to getOrders().

Before you use this getOrders method you have already executed the index.js (in which OrdersDao.injectDB is run, and you should have the orders variable have a value). That is the value used within the getOrders method.

Can you tell me how and where are you calling the getOrders method?