Search code examples
node.jsmongodbmongooseconnect-mongo

Timing issue using `connect-mongo` and Mongoose with MongoDB Atlas


I am using connect-mongo for my ExpressJS Session Store and Mongoose for my DB connection and am having, what I will call, a timing issue with it. Here is my code:

dbservice.js

const mongoose = require('mongoose');
const debug = require('debug')('app:core:db:service');
const chalk = require('chalk');
const config = require('config');

const mongoDbUrl = config.mongodb_url;

const mongoDbOptions = {
  useUnifiedTopology: true,
  useNewUrlParser: true,
  useCreateIndex: true,
  useFindAndModify: false,
};
(async () => {
  try {
    await mongoose.connect(mongoDbUrl, mongoDbOptions);
    debug(`Connected to ${chalk.green('MongoDB')}`);
  } catch (err) {
    debug(`${chalk.green('MongoDB')} connection ${chalk.red(`error`)}: ${err}`);
  }
})();
const db = mongoose.connection;
db.on('error', (err) => {
  debug(`${chalk.green('MongoDB')} connection ${chalk.red(`error`)}: ${err}`);
});
// For nodemon restarts
process.once('SIGUSR2', () => {
  const msg = 'Nodemon Restart';
  db.close()
    .then(() => {
      debug(`${chalk.green('MongoDB')} connection ${chalk.red(`closed`)}: ${msg}`);
      process.kill(process.pid, 'SIGUSR2');
    })
    .catch((err) => {
      debug(err);
    });
});
const dbClient = db.getClient();

module.exports = dbClient;

app.js

const dbClient = require('dbService');

// Using Express Session with Google Cloud Datastore to securly store session/cookie information
app.use(
  session({
    // Using MongoDB as session store
    store: MongoStore.create({
      client: dbClient, // Use Mongoose for DB Conection
      ttl: 1800000, // Ages session out at 30 minutes
      autoRemove: 'native', // Uses MongoDB's native Time to Live (TTL) to expire and remove records
      },
    }),
  }),
);

This code works great when using a local, on-network instance of MongoDB because there is such a small delay in connecting to MongoDB that it works. However, when using MongoDB Atlas, there is a one to two second delay in connection. So, I suspect (what I thought was brilliant) my line of code const dbClient = db.getClient() in dbservice.js is failing with connect-mongo throwing out error:

(node:1148) UnhandledPromiseRejectionWarning: MongoError: MongoClient must be connected before calling MongoClient.prototype.db` because Mongoose is not connected.

As a novice, I have been struggling to try and solve this issue. I was trying to find a way to use mongoose.connection.readyState to wait until it returned a 1 for connected but have failed.

My question is this: Is there a better way for me to return dbClient with it properly waiting for Mongoose to connect to MongoDB? Any help is greatly appreciated.


Solution

  • Taking cues from Nikita Mazur's response, I modified my dbservices.js to the following:

    const mongoose = require('mongoose');
    const debug = require('debug')('app:core:db:service');
    const chalk = require('chalk');
    const { mongoDbUrl } = require('../config/config');
    
    const mongoDbOptions = {
      useUnifiedTopology: true,
      useNewUrlParser: true,
      useCreateIndex: true,
      useFindAndModify: false,
    };
    async function dbConnection() {
      try {
        await mongoose.connect(mongoDbUrl, mongoDbOptions);
        debug(`Connected to ${chalk.green('MongoDB')}`);
      } catch (err) {
        debug(`${chalk.green('MongoDB')} connection ${chalk.red(`error`)}: ${err}`);
      }
      return mongoose.connection.getClient();
    }
    const db = mongoose.connection;
    db.on('error', (err) => {
      debug(`${chalk.green('MongoDB')} connection ${chalk.red(`error`)}: ${err}`);
    });
    // For nodemon restarts
    process.once('SIGUSR2', () => {
      const msg = 'Nodemon Restart';
      db.close()
        .then(() => {
          debug(`${chalk.green('MongoDB')} connection ${chalk.red(`closed`)}: ${msg}`);
          process.kill(process.pid, 'SIGUSR2');
        })
        .catch((err) => {
          debug(err);
        });
    });
    
    module.exports = dbConnection();