how to decouple mongodb atlas connection from the express server start - node.js

Im trying to decouple my express server start from the mongodb connection process .
mongodb.connect(process.env.CONNECTIONSTRING, { useNewUrlParser: true, useUnifiedTopology: true }, function (err, client) {
if (err) {
throw new Error(err)
}
module.exports = client
const server = require("./server")
server.start(opts, t => {
console.log(`server is up 4000`)
})
})
so instead of this single file I would like to have two files one used for mongodb connection , and other for starting the server . when i did this I got error related to mongodb, I think because the server started even before the mongodb conection was established.
any idea on how to solve this

Wrap it in a promise and call it wherever you want
Create a file name db.js it whatever else you want and require in the file that you need it. Then wrap the callback in a promise and export it for usage outside the file. Example above.
function initMongo() {
mongodb.connect(process.env.CONNECTIONSTRING, { useNewUrlParser: true, useUnifiedTopology: true }, function (err, client) {
return new Promise((resolve, reject) => {
if (err) {
return reject(err);
}
return resolve(client)
})
})
}
module.exports = { initMongo };
Then in your init function, you could call
const server = require("./server");
const mongoDb = require("./db");
async init() {
let client;
try {
client = await mongoDb.initMongo()
} catch(e) {
// could not connect to db
}
server.start(opts, t => {
console.log(`server is up 4000`)
})
}

Related

Node.js/Express: How Do I Share or Export Mongo Database Connection To Other Modules?

I am trying to share the Mongo connection with other modules in my Node.js project. I keep getting either undefined or is not a function when attempting to use the exported client. I also had a question around detecting if the connection is in fact open before performing operations on the database.
It seems like using the app.locals would be the proper way to share the connection but I could not get that working either. Below is what I have at the moment. I've tried this many ways. Most of what I can find online seems to export the Mongo Node driver's method, not the connection itself. The idea is to connect once and never disconnect until the app shuts down.
const client = new MongoClient(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
async function connect () {
app.locals.dbConnected = false;
try {
await client.connect();
app.locals.dbConnected = true;
module.exports = client;
} catch (e) {
console.error(e);
}
};
then in another module do something like:
await client.db('syslogs').collection('production').insertOne(doc);
Is it possible to share the connection?
Could do something like below:
const client = new MongoClient(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
let __inst = null;
export default new Promise((resolve, reject) => {
if (__inst !== null) resolve(__inst);
// the open event is the key here
// like this we can handle error, close etc thru events as well
client.open((err, mongoInst) => {
if (err) reject(err);
__inst = mongoInst;
resolve(__inst);
});
});
Then in other module you can use the export client like you want.
Thanks.
I just got it working using app.locals.
index.js
const { MongoClient } = require("mongodb");
const client = new MongoClient(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
(async () => {
app.locals.dbConnected = false;
try {
await client.connect();
console.log("Connected to DB");
app.locals.client = client;
app.listen(PORT, HOST, () => {
console.log(`Running on http://${HOST}:${PORT}`);
});
} catch (e) {
console.error(e);
}
})();
Then in my module:
async function index (req, res) {
try {
let db = req.app.locals.client.db("admin");
await db.command({ ping: 1 });
console.log("pinged admin database");
}catch(err) {
console.log(err);
}
}

Promise MongoDB Trying Connection

Is this a good way to connect to mongoDB from a nodejs dockerized application? Sometimes mongoDb docker is no ready, so I need to try to connect again to be sure mongoDb docker container is ready and keep executing the following promises.
docker_compose.yaml file already has a dependency defined between applicacion and mongoDB.
Cheers!
private connect() {
return new Promise((resolve, reject) => {
mongoose.connect(`mongodb://${process.env.DB_HOST}/${process.env.DB_NAME}`, {useNewUrlParser: true, useFindAndModify: false}, (err) => {
if (err) {
log.error(err.message);
setTimeout(this.connect, 1000);
} else {
log.info('--> App Connected to MongoDb');
resolve();
}
});
});
}
private init() {
const promises = [
() => this.connect(),
() => this.loadConfig(),
() => this._webHookService.init(),
() => this._cronService.init()
];
return promises.reduce((p, fn) => p.then(fn).catch(error => log.error(error)), Promise.resolve());
}

MongoDB Change Streams - Error [ERR_HTTP_HEADERS_SENT]

I'm trying to build an application which at the moment simply console logs Mongo documents in the client browser. I understand the Mongo change streams fine on the server side and have the following code to watch for inserts into the collection and when a change occurs, the dataset is reloaded:
MongoClient.connect(MongoConnection.url, { useNewUrlParser: true, useUnifiedTopology: true }, (err, client) => {
const db = client.db('test');
const collection = db.collection('options');
const changeStream = collection.watch();
changeStream.on('change', next => {
// If there is a change in the collection, reload the data.
reload();
});
});
function reload() {
MongoClient.connect(MongoConnection.url, { useNewUrlParser: true, useUnifiedTopology: true }, (err, client) => {
const db = client.db('test');
const collection = db.collection('options');
collection.find({}).toArray(function (err, docs) {
client.close();
console.log(docs)
});
});
};
However, I'm struggling to replicate the same on the client side. So far what I have done is created an XHR request in a client side js file which looks like this:
$(window).on('load', () => {
function load_options() {
var data = null;
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
if (this.responseText === "") {
} else {
data = $.parseJSON(this.responseText);
$.each(data, function (i, item) {
console.log(item)
});
}
}
});
xhr.open("GET", "/dashboard/load-options");
xhr.setRequestHeader("cache-control", "no-cache");
xhr.send(data);
};
load_options();
});
And I have changed the server side code to look like this:
router.get('/load-options', (req, res) => {
MongoClient.connect(MongoConnection.url, { useNewUrlParser: true, useUnifiedTopology: true }, (err, client) => {
const db = client.db('test');
const collection = db.collection('options');
const changeStream = collection.watch();
changeStream.on('change', next => {
// If there is a change in the collection, reload the data.
reload();
});
});
function reload() {
MongoClient.connect(MongoConnection.url, { useNewUrlParser: true, useUnifiedTopology: true }, (err, client) => {
const db = client.db('test');
const collection = db.collection('options');
collection.find({}).toArray(function (err, docs) {
client.close();
res.send(docs);
});
});
};
});
The desired outcome from this is for every time I insert a new document into the collection, the console logs the entire collection again with the new changes. However, This works for the first insert and the console logs the collection, but after that I'm getting the following error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
I know this is because my server side code now has
res.send(docs)
So I'm trying to send the headers again every time after the first request. My issue is that I don't know how I should be sending the data over from the server to the client to prevent this error.
Can anyone guide me in the right direction?
Sorry, too long answer for comments.
It means that your response has been sent with res.send(docs) once done, you canot modify the response (it has been sent).
Some possible solutions are :
use a websocket
use a polling interval on /load-options
use a stream on the output to send data to the client from the mongo stream.
The last solution which is non well known would be :
const out = new Readable();
const reload = () => {
MongoClient.connect(MongoConnection.url, { useNewUrlParser: true, useUnifiedTopology: true }, (err, client) => {
const db = client.db('test');
const collection = db.collection('options');
collection.find({}).toArray(function (err, docs) {
client.close();
out.push(docs);
});
});
};
changeStream.on('change', next => {
reload();
});
out._read = () => {};
res.type('application/json').send(out);
My advice would be also to avoid the mongo.connect each time a data is modified.

MongoDB-Express: Cannot get the client/db out of connect function

I am separating the connect function of MongoDB to a separate module, so that the mongoDB connection is reusable. The issue is, I could not get the client/DB variable outside the connect function. It shows undefined.
var MongoClient = require('mongodb').MongoClient;
var _client;
var mongoURL = "mongodb://localhost:27017/";
module.exports = {
connectToMongoServer: (callback) => {
MongoClient.connect(mongoURL,{ useNewUrlParser: true },function(err,client){
_client = client;
return callback(err);
});
},
getClient: () => {
return _client;
}
};
Within the connect function, the _client details contains the information, but if I return it using getClient, it shows undefined.
MongoDB - v3.6.5
Node - v9.9.0
I've made up a snippet which should work the same ad your code, and it works.
So I think the problem is how you are calling your function getClient(); are you sure you are calling it after it get connected?
var _client;
function someAsyncFunc(callback) {
setTimeout(() => callback(false, 'client'), 500);
}
const file = {
connectToMongoServer: (callback) => {
someAsyncFunc(function(err, client) {
_client = client;
return callback(err);
});
},
getClient: () => {
return _client;
}
};
console.log('display one :', file.getClient());
file.connectToMongoServer((err) => {
console.log('display error :', err);
console.log('display two :', file.getClient());
});

Check if mongoDB is connected

I have mongoDB in my app.
I want to check if mongoDB is connected, before I listen to the app.
Is it the best way for doing it?
This is my server.js file:
var express = require('express');
var mongoDb = require('./mongoDb');
var app = express();
init();
function init() {
if (mongoDb.isConnected()) {
app.listen(8080, '127.0.0.1');
}
else {
console.log('error');
}
}
isConnected runs getDbObject.
getDbObject connects to mongoDB and returns an object:
connected (true/false), db (dbObject or error).
Then, isConnected resolve/reject by connected property.
This is mongoDb.js file:
//lets require/import the mongodb native drivers.
var mongodb = require('mongodb');
// Connection URL. This is where your mongodb server is running.
var url = 'mongodb://localhost:27017/myDb';
var connectingDb; // promise
//We need to work with "MongoClient" interface in order to connect to a mongodb server.
var MongoClient = mongodb.MongoClient;
init();
module.exports = {
isConnected: isConnected
}
// Use connect method to connect to the Server
function init() {
connectingDb = new Promise(
function (resolve, reject) {
MongoClient.connect(url, function (err, db) {
if (err) {
console.log('Unable to connect to the mongoDB server. Error:', err);
reject(err);
}
else {
console.log('Connection established to', url);
//Close connection
//db.close();
resolve(db);
}
});
}
);
}
function getDbObject() {
return connectingDb().then(myDb => {
return {
connected: true,
db: myDb
}
}
)
.catch(err => {
return {
connected: false,
db: err
}
}
)
}
function isConnected() {
return new Promise(
function(resolve, reject) {
var obj = getDbObject();
if (obj.connected == true) {
console.log('success');
resolve(true);
}
else {
console.log('error');
reject(false);
}
}
)
}
Any help appreciated!
there are multiple ways depends on how your DB is configured. for a standalone (single) instance. You can use something like this
Db.connect(configuration.url(), function(err, db) {
assert.equal(null, err);
if you have a shared environment with config servers and multiple shards you can use
db.serverConfig.isConnected()
Let client be the object returned from MongoClient.connect:
let MongoClient = require('mongodb').MongoClient
let client = await MongoClient.connect(url ...
...
This is how i check my connection status:
function isConnected() {
return !!client && !!client.topology && client.topology.isConnected()
}
This works for version 3.1.1 of the driver.
Found it here.
From version 3.1 MongoClient class has isConnected method. See on https://mongodb.github.io/node-mongodb-native/3.1/api/MongoClient.html#isConnected
Example:
const mongoClient = new MongoClient(MONGO_URL);
async function init() {
console.log(mongoClient.isConnected()); // false
await mongoClient.connect();
console.log(mongoClient.isConnected()); // true
}
init();
There has been some changes since version 3, isConnected is no longer available in version 4. The correct way of dealing with an ambiguous connection is to just call MongoClient.connect() again. If you're already connected nothing will happen, it is a NOOP or no-operation, and if there is not already a connection you'll be connected (as expected). That said, if you really want to know if you have a connection try something like this:
const isConnected = async (db) => {
if (!db) {
return false;
}
let res;
try {
res = await db.admin().ping();
} catch (err) {
return false;
}
return Object.prototype.hasOwnProperty.call(res, 'ok') && res.ok === 1;
};

Resources