0

I'm working on a project that will be a multi-tenant Saas application, and am having difficulty implementing a way to log into various databases depending on the user login info. Right now, I just want to split traffic between a Sandbox database (for demo purposes, and will be wiped on a regular basis), and an Alpha database (for current client testing and development). I have written the middleware below, config.js, that detects the user ID on login and assigns a database object using mongoose.createConnection(). This key-value pair is then added to a store using memory-cache. Here is the config.js code:

var mcache = require('memory-cache'),
    Promise = require("bluebird"),
    mongoose = require('mongoose');

Promise.promisifyAll(require("mongoose"));
(function () {

    'use strict';

    var dbSand = mongoose.createConnection(process.env.DB_SAND);
    var dbAlpha = mongoose.createConnection(process.env.DB_ALPHA);

    function dbPathConfigMiddlewareWrapper (){
        return function  setDbPath(req, res, next){
            if ( req ){
                if (!mcache.get(req.session.id) && req.body.email){
                    var login = req.body.email;
                    if (login === 'demo@mysite.com'){
                        mcache.put(req.session.id, dbSand);
                    } else {
                        mcache.put(req.session.id, dbAlpha);
                    }
                } 
                req.dbPath = mcache.get(req.session.id);

                next();
            }

        };

    }

module.exports = dbPathConfigMiddlewareWrapper;

}());

So far so good. But I have been unsuccessful in calling the correct database in my routes. When I was just using a single database, I could easily use this:

  var connStr = process.env.DBPATH;

  if(mongoose.connection.readyState === 0){
      mongoose.connect(connStr, function(err) {
          if (err) throw err;
          console.log('Successfully connected to MongoDB');
      });
    }

Now, I'm trying this to no avail:

  var connStr = req.dbPath;  //where req.dbPath is assigned in the config middleware above.

  if(connStr.connection.readyState === 0){
      mongoose.connect(req.dbPath, function(err) {
          if (err) throw err;
          console.log('Successfully connected to MongoDB');
      });
    }

Any guidance here would be greatly appreciated. This seems like it should be much more straightforward, and the documentation alludes to it but does not elaborate.

  • You should read [this](http://stackoverflow.com/a/19475270/893780). Basically, when you have different database connections, you need to register your models against each connection separately. `mongoose.connect()` doesn't come into play, because you're using `mongoose.createConnection()`. – robertklep Apr 10 '17 at 16:37
  • Thanks @robertklep, I did read that but was unclear on the implementation for two reasons. 1. The schema for my databases are identical, and there is obviously more that just a simple inline model. This seems unscalable. What is your recommended approach to use the same schema (5 models currently) as I refactor across the app? 2. Once the connection is created using `mongoose.createConnection()`, should I immediately connect to both? In that case, how do I route to the correct connection later? Or, should I open and close the connections explicitly for each operation? Thanks – espressoAndCode Apr 10 '17 at 17:03

2 Answers2

0

Here, I think, the problem is you are saving a database object to your key value storage. mcache.put(req.session.id, dbSand);. Which caused error in if(connStr.connection.readyState === 0).

You can stringify your object. mcache.put(req.session.id, JSON.stringify(dbSand));. And get the object's string and parse it into JSON like var connStr = JSON.parse(req.dbPath);.

Tolsee
  • 1,695
  • 14
  • 23
  • Sorry, this doesn't appear to be the correct solution. Causes an immediate 'failed to connect' error and crashes the server. – espressoAndCode Apr 10 '17 at 17:09
  • ok. It might be not the full solution. Are you sure that your key-value(memory-cache) storage is saving objects diresctly? – Tolsee Apr 11 '17 at 03:32
0

You don't call mongoose.connect() if you're manually creating connections.

Instead, you have to register your models for each connection, which is a bit of a PITA but as far as I know there's no way around that. It may require some restructuring of your code.

Here's some untested code on how you could set something like that up.

Your middleware file:

// Create connections
const registerModels = require('./register-models');
let dbSand = mongoose.createConnection(process.env.DB_SAND);
let dbAlpha = mongoose.createConnection(process.env.DB_ALPHA);

// Register your models for each connection.
registerModels(dbSand);
registerModels(dbAlpha);

function dbPathConfigMiddlewareWrapper() { ... }

register-models.js:

const mongoose = require('mongoose');
const Schema   = mongoose.Schema;

let UserSchema = new mongoose.Schema(...);

module.exports = function(conn) {
  conn.model('User', UserSchema);
};

This does mean that you can't use User.find(...) in your routes, because that only works when you're using a single connection (the default one that gets created with mongoose.connect(), which you're not using).

Instead, you should use something like this in your routes:

req.dbPath.model('User').find(...);
robertklep
  • 198,204
  • 35
  • 394
  • 381