Meteor v3 how to connect to a Remote database Collection

in v2, I was able to connect to a secondary MongoDB using this …

Settings.json

"private": {
  "connectionStringToRemoteDB": "mongodb://",
},

imports/api/Audits/Audits.js

import { Mongo, MongoInternals } from 'meteor/mongo';
import { Meteor } from 'meteor/meteor';

Meteor.methods({
  'get.privateKey': function getPrivateKey() {
    return Meteor.settings.private.connectionStringToRemoteDB;
  },
});

let driver;

if (Meteor.isServer) {
  const server = Meteor.call('get.privateKey');
  driver = new MongoInternals.RemoteCollectionDriver(server);
}
const Audits = new Mongo.Collection('audits', { _driver: driver });
export default Audits;

But I don’t know how to tweak this code for version 3, as Meteor.callAsync is now required, but I can’t get it working within the isServer section.

Any suggestions?

1 Like

Do you use Optimistic UI? (do you make any DB calls from the client)

1 Like

Optimistic UI - Yes
Yes - Client is requesting an aggregation

Ok, there is no difference between V2 and V3 in how DB connections are initiated.
However, in V3, not only Meteor.callAsync is required but also awaiting for it. However you don’t normally call a server side method from another server side method. You can just use a function instead and import/export it wherever you need it.

The best way to store secret DB connection credentials is with env vars (Environment Variables | Meteor API Docs). In this way you can change a variable without having to redeploy code. Most cloud hosts have a page/menu for these variables.
So I would say,

// Anywhere (Isomorphic)

// From here ....
import { Meteor } from 'meteor/meteor'
import { MongoInternals } from 'meteor/mongo'
const options = '?retryWrites=true' +
  '&maxIdleTimeMS=5000' +
  '&maxPoolSize=30' +
  '&readConcernLevel=majority' +
  '&readPreference=secondaryPreferred' +
  '&w=majority' +
  '&heartbeatFrequencyMS=15000'

const connectionUri = process.env.MONGO_YOUR_CONNECTION_URI
const liveConnectionUri = `mongodb+srv://${connectionUri}${options}`
const localConnectionUri = 'mongodb://127.0.0.1:3001/meteor' // or your local meteor mongo DB
const uri =  Meteor.isProduction ? liveConnectionUri : localConnectionUri

const driver = {
  _driver: new MongoInternals.RemoteCollectionDriver(uri, {})
}
// To here .... you can write in a separate file if you have multiple connections and want to keep them organized. Export { driverOne, driverTwo ... }

const Audits = new Mongo.Collection('audits', driver)
export default Audits

Hi @robgordon,

Why do you need to use a Meteor method to get the connectionStringToRemoteDB?

You could get the setting directly:

import { Mongo, MongoInternals } from 'meteor/mongo';
import { Meteor } from 'meteor/meteor';

let driver;

if (Meteor.isServer) {
  const server = Meteor.call('get.privateKey');
  driver = new MongoInternals.RemoteCollectionDriver(Meteor.settings.private.connectionStringToRemoteDB);
}
const Audits = new Mongo.Collection('audits', { _driver: driver });
export default Audits;

Hi @denyhs and @paulishca

Thanks for your responses.

You are correct I didn’t need a Meteor.call to obtain the driver AND I can access it via settings or environment variable; but the issue remains for me, the following code in V3 still does not connect to the secondary database.

When I map the collections it returns a list of collections from the primary database.

import { Mongo, MongoInternals } from 'meteor/mongo';
import { Meteor } from 'meteor/meteor';

let driver;

const mapCollections = async () => {
  const { db } = driver.mongo;
  const collections = await db.collections();
  collections.map((c) => console.log(c.collectionName));
};

if (Meteor.isServer) {
  // eslint-disable-next-line new-cap
  driver = new MongoInternals.defaultRemoteCollectionDriver(process.env.DB);
  mapCollections();
}
const Audits = new Mongo.Collection('audits', { _driver: driver });

export default Audits;

Did you check process.env.DB to see if it’s correct?

Another thing you can try is to use RemoteCollectionDriver instead of defaultRemoteCollectionDriver.

const uri = process.env.DB || Meteor.settings.private.secondaryDB;
driver = new MongoInternals.RemoteCollectionDriver(uri);

One last thing is to make sure the driver is ready before calling mapCollections:

if (Meteor.isServer) {
  const uri = process.env.DB || Meteor.settings.private.secondaryDB;
  driver = new MongoInternals.RemoteCollectionDriver(uri);

  driver.mongo.db.on('open', () => {
    mapCollections();
  });
}
if (Meteor.isServer) {
  // eslint-disable-next-line new-cap
  driver = new MongoInternals.defaultRemoteCollectionDriver(process.env.DB);
  mapCollections();
}
const Audits = new Mongo.Collection('audits', { _driver: driver });

I haven’t used isomorphism for at least 10 years and I don’t remember what is the correct way to initialize a new connection on both client and server. However, if you initialize your driver inside isServer what is the value of the driver otherwise (isClient)?
I think you don’t need to use that isServer at all and use env vars, which are only available on the server anyway.

Also, following the code, at the time you call mapCollections() you haven’t yet initialized the ‘audits’ collection. I suggest you start with less restrictions, run everything on the server side and then move to expanding to the client.

Thanks for your inputs. Managed to get it working with…

  driver = new MongoInternals.RemoteCollectionDriver(uri);