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));
}
});
})
}
}
Related
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 });
}
}
}
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 want to write a module that exports a default function and a list of properties (they are functions too).
Basically this module will allows this usage consumer-side:
let db_connection = fake_database_connection_instance()
let db = require('./db.js')(db_connection)
db.find({id:1})
So the default function should only pass the database connection instance to the module.
This is the not working code
module.exports = {
//init should go away and replaced by a 'default' function when this module is called
init: function (connection) {
this.connection= connection;
return this;
},
find: function (query) {
return new Promise(function (resolve, reject) {
this.connection.find(query, function (err, docs) {
if (err) {
return reject(err);
}
return resolve(docs);
});
});
}
}
I want to avoid the new keyword (consumer-side), so I have to remove those this, I know. The problem here are 2:
how to export a default function called on require('./db.js')() and other functions e.g. require('./db.js').find()?
how pass the connection instance from the default function to find()?
EDIT following the #Igor Raush ES6 Class solution I wrote this, but still db is not defined
class DB {
constructor(db) {
this.db = db;
}
find(query) {
return new Promise( (resolve, reject) => {
this.db.find(query, function (err, docs) {
if (err) {
return reject(err);
}
return resolve(docs);
});
});
}
}
You can add a default function to module.exports and use an external variable _conn to store the connection:
let _conn;
module.exports = (connection) => {
_conn = connection;
};
After that you could add find function to module.exports object:
module.exports.find = (query) => {
return new Promise(function(resolve, reject) {
_conn.find(query, function(err, docs) {
if (err) {
return reject(err);
}
resolve(docs);
});
});
}
One option is to create a class and export an instance factory function.
// db.js
function DB(connection) {
this.connection = connection;
}
DB.prototype.find = function find(query) {
let connection = this.connection;
// ...
}
// export instance factory function
module.exports = function (connection) { return new DB(connection); };
or, if you're in an ES6 environment,
// db.js
class DB {
constructor(connection) {
this.connection = connection;
}
find(query) {
let connection = this.connection;
// ...
}
}
// export instance factory function
module.exports = connection => new DB(connection);
Then you can do
let db = require('./db')(db_connection);
db.find({ id: 1 });
If you'd like to enforce the singleton pattern (only one DB instance can exist), lazily create a module-local instance and return the reference on subsequent calls:
let _instance = null;
module.exports = connection => {
// return existing instance, or create a new one
return _instance || (_instance = new DB(connection));
}
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 => {...});
I am trying to get few values from redis, combine them and eventually send. But I just can't make those promises work.
This is the simple get functions from redis
client.get('user:1:id',function(err,data){
// here I have data which contains user ID
});
client.get('user:1:username',function(err,data){
// here I have data which contains username
});
Now I want to get ID and username and send them, but I have no idea how to make that work. I manage to make it work with callbacks but it is very messy result, so then i tried to wrap anonymous functions into Q.fcall and after call .then which looks something like that
client.get('user:1:id',Q.fcall(function(err,data){
return data;
}).then(function(val) {
// do something
}));
but that gives me error for too many arguments been passed and I'm not even sure if that would help me even if it would work.
Q.all([Q.ninvoke(client, 'get', 'user:1:id'),
Q.ninvoke(client, 'get', 'user:1:username')]).then(function (data) {
var id = data[0];
var username = data[1];
// do something with them
});
See https://github.com/kriskowal/q#adapting-node
I use a simple RequireJS module using node-redis and whenjs to create a lifted redis wrapper:
define [
'redis/lib/commands'
'when'
'when/node/function'
], (Commands, When, NodeFn) ->
'use strict'
lift = (redis) ->
wrapped = {}
Commands.map (cmd) ->
wrapped[cmd] = (args...) ->
def = When.defer()
args.push NodeFn.createCallback def.resolver
redis[cmd].apply redis, args
def.promise
wrapped
{lift}
Usage is straightforward:
client = lift redis.createClient()
client.get("hello").then console.log, console.error
Using Promise, Bluebird and node_redis:
import { RedisClient, createClient, ClientOpts } from "redis";
import { promisifyAll, PromisifyAllOptions } from "bluebird";
export module FMC_Redis {
export class Redis {
opt: ClientOpts;
private rc: RedisClient;
private rcPromise: any;
private static _instance: Redis = null;
public static current(_opt?: ClientOpts): Redis {
if (!Redis._instance) {
Redis._instance = new Redis(_opt);
Redis._instance.redisConnect();
}
return Redis._instance;
}
public get client(): RedisClient {
if (!this.rc.connected) throw new Error("There is no connection to Redis DB!");
return this.rc;
}
/******* BLUEBIRD ********/
public get clientAsync(): any {
// promisifyAll functions of redisClient
// creating new redis client object which contains xxxAsync(..) functions.
return this.rcPromise = promisifyAll(this.client);
}
private constructor(_opt?: ClientOpts) {
if (Redis._instance) return;
this.opt = _opt
? _opt
: {
host: "127.0.0.1",
port: 6379,
db: "0"
};
}
public redisConnect(): void {
this.rc = createClient(this.opt);
this.rc
.on("ready", this.onReady)
.on("end", this.onEnd)
.on("error", this.onError);
}
private onReady(): void { console.log("Redis connection was successfully established." + arguments); }
private onEnd(): void { console.warn("Redis connection was closed."); }
private onError(err: any): void { console.error("There is an error: " + err); }
/****** PROMISE *********/
// promise redis test
public getRegularPromise() {
let rc = this.client;
return new Promise(function (res, rej) {
console.warn("> getKeyPromise() ::");
rc.get("cem", function (err, val) {
console.log("DB Response OK.");
// if DB generated error:
if (err) rej(err);
// DB generated result:
else res(val);
});
});
}
/******* ASYNC - AWAIT *******/
// async - await test function
public delay(ms) {
return new Promise<string>((fnResolve, fnReject) => {
setTimeout(fnResolve("> delay(" + ms + ") > successfull result"), ms);
});
}
public async delayTest() {
console.log("\n****** delayTest ")
let a = this.delay(500).then(a => console.log("\t" + a));
let b = await this.delay(400);
console.log("\tb::: " + b);
}
// async - await function
public async getKey(key: string) {
let reply = await this.clientAsync.getAsync("cem");
return reply.toString();
}
}
}
let a = FMC_Redis.Redis.current();
// setTimeout(function () {
// console.warn(a.client.set("cem", "naber"));
// console.warn(a.client.get("cem"));
// console.warn(a.client.keys("cem"));
// }, 1000);
/***** async await test client *****/
a.delayTest();
/** Standart Redis Client test client */
setTimeout(function () {
a.client.get("cem", function (err, val) {
console.log("\n****** Standart Redis Client")
if (err) console.error("\tError: " + err);
else console.log("\tValue ::" + val);
});
}, 100)
/***** Using regular Promise with Redis Client > test client *****/
setTimeout(function () {
a.getRegularPromise().then(function (v) {
console.log("\n***** Regular Promise with Redis Client")
console.log("\t> Then ::" + v);
}).catch(function (e) {
console.error("\t> Catch ::" + e);
});
}, 100);
/***** Using bluebird promisify with Redis Client > test client *****/
setTimeout(function () {
var header = "\n***** bluebird promisify with Redis Client";
a.clientAsync.getAsync("cem").then(result => console.log(header + result)).catch(console.error);
}, 100);