I'm using a lambda function with nodejs and mongoose that uses a client file for connections. I work with two databases, the first is to read data, after this i need close the current connection for open a new, but i have problems and errors with this
I tried adding mongoose.connection.close(), mongoose.disconnect(), overwriting directly the connection
But i get the follow responses:
"mongoclient must be connected to perform this operation"
"can't call 'openuri()' on an active connection with different connection strings"
This is my code
let conn = null;
let mongoURI = '';
class MongoDB {
// execute mongo connection
static initMongo(context) {
if (conn == null) {
mongoLogger.info(context, 'trying_connect_mongo', {});
conn = mongoose.connect(mongoURI, {});
// On db connection
mongoose.connection.on('connected', () => {
mongoLogger.info(context, 'connection_mongo_successful', {});
});
// On db error
mongoose.connection.on('error', (err) => {
mongoLogger.error(context, 'connection_mongo_failed', { err: err });
});
} else {
mongoLogger.info(context, 'connection_mongo_already_exists', {});
}
}
// setUp mongoConnection
static async setUp(context) {
// Get secret from mongo uri
try {
mongoURI = await clientSecrets.getSecret(MONGO_URI);
this.initMongo(context);
} catch (err) {
mongoLogger.error(context, 'get_mongo_uri_secret_failed', { err: err });
}
}
// setUp mongoConnection analytics
static async setUpAnalytics(context) {
// Get secret from mongo uri
try {
mongoURI = await clientSecrets.getSecret(MONGO_ANALYTICS_URI);
this.initMongo(context);
} catch (err) {
mongoLogger.error(context, 'get_mongo_analytics_uri_secret_failed', { err: err });
}
}
// close current connection and open a new specified connection
static async closeConnection(context, type) {
// type 1 insights database
// type 2 anlytics database
try {
await mongoose.connection.close();
conn = null;
if (type === 1) {
await this.setUp(context);
} else if (type === 2) {
await this.setUpAnalytics(context);
}
} catch (err) {
mongoLogger.error(context, 'cancel_current_connection_failed', { err: err });
}
}
}
Related
I have a NodeJS application and I have a problem using nano to create/delete a database. I want to check if there is a database with the users name. If there is a database I would like to clear it so I destroy it and create a new one.
module.exports.createDB = function () {
try {
nano.db.list().then((body) => {
// body is an array
body.forEach((db) => {
if(db == getUser.username){
nano.db.destroy(getUser.username)
}
});
});
nano.db.create(getUser.username).then((body) => {
nano.use(getUser.username).insert(
{ "views":
{ "webview":
{ "map": "function (doc) {\n emit(doc.msg, 1);\n}", "reduce": "_count" }
}
}, '_design/output', function (error, response) {
console.log("Design Created");
});
})
} catch (error) {
console.log(error);
}
}
If there is no database with this name it works fine but if there is a database with the same name i get the error:
(node:33354) UnhandledPromiseRejectionWarning: Error: The database could not be created, the file already exists.
But I don't know why because I destroy this database before?
You can check whether a database exists by calling db.info(). If it exists, you can then destroy it and re-create it. If it doesn't exist, just create it:
const Nano = require('nano')
const nano = Nano(process.env.COUCH_URL)
const main = async () => {
const DBNAME = 'mydb'
const db = nano.db.use(DBNAME)
try {
const info = await db.info()
console.log('DB Exists!', info)
await nano.db.destroy(DBNAME)
} catch (e) {
console.log('DB does not exist!')
}
await nano.db.create(DBNAME)
}
main()
Is there a way to use MongoDB in a class structure in NodeJS?
I understand you can do CRUD operations on the DB within the connection method like
mongo.connect(url, function(err, client){//do some CRUD operation});
but I was wondering if there was a way to open the connection to the DB, have access to it across the class, then close it when you are done working with the class.
For example:
class MyClass {
constructor(databaseURL) {
this.url = databaseURL;
}
async init() {
//make connection to database
}
async complete_TaskA_onDB() {
//...
}
async complete_TaskB_onDB() {
//...
}
async close_connection() {
//close connection to database
}
}
Edit:
I just came across more information in the Node.JS Mongo docs. Maybe something along the lines of this would work?
//constructor()
this.db = new MongoClient(new Server(dbHost, dbPort));
//init()
this.db.open();
//taskA()
this.db.collection(...).update(...);
//close_connection()
this.db.close();
You can create a class of which will act as a wrapper on any core lib, doing so will give you below advantages:
Wrapping any core module with your own service will allow you to:
Create a reusable service that you could use on multiple components in your app.
Normalize the module's API, and add more methods your app needs and the module doesn’t provide.
Easily replace the DB module you chose with another one (if needed).
I have created that service which I use it in my projects for MongoDB:
var mongoClient = require("mongodb").MongoClient,
db;
function isObject(obj) {
return Object.keys(obj).length > 0 && obj.constructor === Object;
}
class mongoDbClient {
async connect(conn, onSuccess, onFailure){
try {
var connection = await mongoClient.connect(conn.url, { useNewUrlParser: true });
this.db = connection.db(conn.dbName);
logger.info("MongoClient Connection successfull.");
onSuccess();
}
catch(ex) {
logger.error("Error caught,", ex);
onFailure(ex);
}
}
async getNextSequence(coll) {
return await this.db.collection("counters").findOneAndUpdate({
_id: coll
},
{$inc: {seq: 1}},
{projections: {seq: 1},
upsert: true,
returnOriginal: false
}
);
}
async insertDocumentWithIndex(coll, doc) {
try {
if(!isObject(doc)){
throw Error("mongoClient.insertDocumentWithIndex: document is not an object");
return;
}
var index = await this.getNextSequence(coll);
doc.idx = index.value.seq;
return await this.db.collection(coll).insertOne(doc);
}
catch(e) {
logger.error("mongoClient.insertDocumentWithIndex: Error caught,", e);
return Promise.reject(e);
}
}
async findDocFieldsByFilter(coll, query, projection, lmt) {
if(!query){
throw Error("mongoClient.findDocFieldsByFilter: query is not an object");
}
return await this.db.collection(coll).find(query, {
projection: projection || {},
limit: lmt || 0
}).toArray();
}
async findDocByAggregation(coll, query) {
if(!query.length){
throw Error("mongoClient.findDocByAggregation: query is not an object");
}
return this.db.collection(coll).aggregate(query).toArray();
}
async getDocumentCountByQuery(coll, query) {
return this.db.collection(coll).estimatedDocumentCount(query || {})
}
async findOneAndUpdate(coll, query, values, option) {
if(!(isObject(values) && isObject(query))){
throw Error("mongoClient.UpdateDocument: values and query should be an object");
}
return this.db.collection(coll).findOneAndUpdate(query, {$set : values}, option || {})
}
async modifyOneDocument(coll, query, values, option) {
if(!(isObject(values) && isObject(query))){
throw Error("mongoClient.ModifyOneDocument: values, query and option should be an object");
}
return await this.db.collection(coll).updateOne(query, values, option || {})
}
async close() {
return await this.db.close();
}
}
module.exports = {
mongoDbClient: mongoDbClient
}
For my complete lib access you can refer here
Yes, you can do all of this inside a class, but you cannot set a member variable like db after the constructor has been set. You can make it a global variable, but you cannot set the variable.
const MongoClient = require('mongodb').MongoClient;
var database; //global
class DB {
constructor(url, dbName) {
this.url = url;
this.dbName = dbName;
}
connect() {
console.log('connecting to database ' + this.dbName + ' with URL ' + this.url);
return new Promise((resolve, reject) => {
MongoClient.connect(this.url, (err, client) => {
if (err) {
reject(err);
} else {
database = client.db(this.dbName);
resolve(client.db(this.dbName));
}
});
})
}
}
I have the following mongo class to handle connection
export default class MongoService {
constructor ({ config }) {
this.options = {}
this.connection = null
this.config = config
Object.assign(this.options, config.get('mongo'))
}
async closeConnection(){
this.connection.close()
}
async connect(){
if (!this.options.url) {
throw new Error('Db error')
}
return MongoClient.connect(this.options.url,{ useNewUrlParser: true })
}
async getConnection() {
if (!this.connection) {
this.connection = await this.connect()
}
return this.connection
}
async getCollection(collectionName) {
await this.getConnection()
let db = this.connection.db(this.options.dbname)
return await db.collection(collectionName)
}
How I use in other file :
try {
var mongo = container.cradle.mongoService
let collection = await mongo.getCollection('test')
const cursor = collection.find({})
}catch(e){
throw new Error(e)
}finally {
await mongo.closeConnection()
}
My questions
1 - my service is good enough?
2- do I need to user mongo.closeConnection in each time when I ask for collection ?
if I do not use the close function (that I read is good) the app is stuck and not close
for example I got resposne but nothing close it(if is needed)
or I just need to remove the finally and forget from close
I'm building an application that closes it's database connection after a certain time of inactivity. The problem is, I can't kill it's connection with MongoClient.close() because it's always returns that a error "TypeError: MongoClient.close is not a function". What am I doing wrong?
const MongoClient = require("mongodb").MongoClient;
let mongo = {};
let db;
let disconnectTimer;
let retryTimer;
let disconnect; //Used to prevent mongo from reconnect.
mongo.disconnect = () => {
disconnect = true;
try {
MongoClient.close();
} catch (error) {
console.warn("Error closing connection to Database =", error);
throw {
statusCode: 404,
error: error.toString(),
reason: "",
details: ""
};
}
}
mongo.getDB = () => {
if (typeof db !== "undefined" && !disconnect)
return db;
else
return connect().catch((error) => {
throw error
}).then((db) => {return db});
}
module.exports = mongo;
If db is used as the Mongo DB connection instance,then please try to use dB.close() instead of MongoClient.close().
module.exports = (callback, collectionName) => {
mongoClient.connect(connectionUrl, {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then((connection) => {
const db = connection.db(dbName);
const collection = db.collection(collectionName);
callback(collection, ObjectId);
connection.close();
});
}
I want to make use of the promises feature where in I can connect to mongodb synchronously and I can reuse the connection by passing it on to different modules.
Here is something that I came up with
class MongoDB {
constructor(db,collection) {
this.collection = db.collection(collection);
}
find(query, projection) {
if(projection)
return this.collection.find(query, projection);
else
return this.collection.find(query);
}
}
class Crew extends MongoDB {
constructor(db) {
super(db,'crews');
}
validate() {
}
}
I want to setup a connection somewhere in my initial code like the one below and then reuse the connection for different classes, just like how mongoose or monk does but using only the node-mongodb-native package.
MongoClient.connect(url)
.then( (err,dbase) => {
global.DB = dbase;
});
var Crew = new CrewModel(global.DB);
Crew.find({})
.then(function(resp) {
console.log(resp);
});
Right now, the db returns undefined inside the main MongoDB class and am not able to debug this one out through google or the documentation.
Edit: I had assumed that a promise was synchronous but that is not the case.
To reuse the connection I would create a module like this.
module.exports = {
connect: function(dbName, callback ) {
MongoClient.connect(dbName, function(err, db) {
_db = db;
return callback( err );
});
},
getDb: function() {
return _db;
}
};
After that you can connect to the database before starting your application
MongoConnection.connect("mongodb://localhost:27017/myDatabase", function(err){
app.listen(3000, function () {
// you code
});
});
Considering you created the module in a js file you can simply use require to get the databaseConnection
var dbConnection = require("./myMongoConnection.js");
and to get the connection use
var db = MongoConnection.getDb();
Another option using ES6 classes creates a singleton object that you can access repeatedly. It's inspired by #user3134009's answer here.
const EventEmitter = require('events');
const MongoClient = require('mongodb').MongoClient;
const config = require('config');
let _db = null;
class MongoDBConnection extends EventEmitter {
constructor() {
super();
this.emit("dbinit", this);
if (_db == null) {
console.log("Connecting to MongoDB...");
MongoClient.connect(config.dbs.mongo.url, config.dbs.mongo.options,
(err, db) => {
if (err) {
console.error("MongoDB Connection Error", err);
_db = null;
} else {
console.log("Connected to MongoDB", config.dbs.mongo.url);
db.on('close', () => { console.log("MongoDB closed", arguments); _db = null; });
db.on('reconnect', () => { console.log("MongoDB reconnected", arguments); _db = db; });
db.on('timeout', () => { console.log("MongoDB timeout", arguments); });
_db = db;
this.emit('dbconnect', _db);
}
});
}
}
getDB() {
return _db;
}
}
module.exports = new MongoDBConnection();
I have been struggling with this problem for a while, and in particular with setting up and persisting MongoDb connection in AWS lambda functions across invocations.
Thanks to #toszter answer I've finally come up with the following solution:
const mongodb = require('mongodb');
const config = require('./config.json')[env];
const client = mongodb.MongoClient;
const mongodbUri = `mongodb://${config.mongo.user}:${config.mongo.password}#${config.mongo.url}/${config.mongo.database}`;
const options = {
poolSize: 100,
connectTimeoutMS: 120000,
socketTimeoutMS: 1440000
};
// connection object
let _db = null;
class MongoDBConnection {
constructor() {}
// return a promise to the existing connection or the connection function
getDB() {
return (_db ? Promise.resolve(_db) : mConnect());
}
}
module.exports = new MongoDBConnection();
// transforms into a promise Mongo's client.connect
function mConnect() {
return new Promise((resolve, reject) => {
console.log('Connecting to Mongo...');
client.connect(mongodbUri, options, (error, db) => {
if (error) {
_db = null;
return reject(error);
}
else {
console.log('Connected to Mongo...');
_db = db;
resolve(db);
}
});
});
}
To use it in a controller or app.js:
const mongoConfig = require('mongoConfig');
mongoConfig.getDB()
.then(db => db.collection('collection').find({}))
.catch(error => {...});