Module.export a 'default' function - node.js

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));
}

Related

Calling a function return Promise

I was trying to implement Promises when I get error saying
TypeError: productService.getSwapItems is not a function
I have created a DB class as
const mysql = require('mysql')
class Database {
constructor( config ) {
this.connection = mysql.createConnection( config );
}
query(sql, args) {
return new Promise( (resolve, reject) => {
this.connection.query(sql, args, (error, result) => {
if(error) {
return reject(error);
}
resolve(result);
});
});
}
close() {
return new Promise( (resolve, reject) => {
this.connection.end( err => {
if(err) {
return reject(err);
}
resolve();
})
});
}
}
module.exports = Database;
And then a service class
let Database = require('./mysql.db');
const database = new Database({
host: "db-host",
user: "username",
password: "password",
database: "databasename"
});
class ProductService {
getSwapItems(item_ids_array) {
this.queryStr = "SELECT * FROM Item WHERE from_item_id IN (" + item_ids_array + ")";
return database.query(this.queryStr, null); // database.query returns a promise, so getSwapItems() will also return promise??
}
}
module.exports = ProductService;
When productService using code below, I get error.
var item_ids_array = <Array Values>;
productService.getSwapItems(item_ids_array)
.then( rows => {
console.log(rows);
}).catch( err => {
console.log(err);
});
Error is
productService.getSwapItems(item_ids_array)
TypeError: productService.getSwapItems is not a function
Your module exports a class ProductService with regular method getSwapItems. Classes are first-class citizens in JS, so you can assign them (as value) to any variable any way you like - including require-ing a module.
const ProductService = require('./db/product.service');
But the class is like a recipe for creating an instance of the class you can use, not an instance of the class itself. To actually create an instance of the class, use the new keyword:
const productService = new ProductService();
Only after that you'll be able to use methods of this class the usual way.

MongoDB in NodeJS Class Structure

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));
}
});
})
}
}

Exporting a function declared inside asynchronous function

I have a file in which mongoose is setup
const keys = require('../chat/config/keys');
const mongoose = require('mongoose');
const dbURI = keys.mongoURI;
mongoose.connect(dbURI, { useNewUrlParser: true });
mongoose.set('debug', true);
let fetchVideo;
mongoose.connection.once('open', function () {
console.log('Mongoose default connection open to ' + dbURI);
let connectToDB = mongoose.connection.db;
let videoChatDB = connectToDB.collection('videochat');
fetchVideo = ({ id }) => {
if (id !== '100') {
videoChatDB.findOne({'studentID': parseInt(id)}).then((user) => {
if (user) {
console.log(user);
return true;
} else {
console.log(user);
return false;
}
});
}
}
});
module.exports = { fetchVideo };
And I am requiring that file inside my index.js file like so:
let db = require('./db');
In my index file I have a socket connection and I need to check the database when a new user comes.
socket.on('new-user', async (user) => {
let checkAvail = await db.fetchVideo(user);
});
But I am getting this error:
TypeError: db.fetchVideo is not a function
I am guessing it is undefined since it is declared inside an asynchronous function.
How would I make this to work?
Because the function is created asynchronously, one option is to export a Promise that resolves to fetchVideo function. Because mongoose.connection.once is callback-based, you'll have to transform it into a Promise.
If you want fetchVideo to resolve to something (rather than nothing), you also have to properly chain the findOne call with the promise chain; to fix that, return videoChatDB.findOne....
const fetchVideoProm = new Promise((res, rej) => {
mongoose.connection.once('open', function () {
console.log('Mongoose default connection open to ' + dbURI);
let connectToDB = mongoose.connection.db;
let videoChatDB = connectToDB.collection('videochat');
const fetchVideo = ({ id }) => {
if (id !== '100') {
return videoChatDB.findOne({'studentID': parseInt(id)}).then((user) => {
if (user) {
console.log(user);
return true;
} else {
console.log(user);
return false;
}
});
}
}
res(fetchVideo);
});
});
module.exports = { fetchVideoProm };
Consume it by awaiting the creation of the fetchVideo function, and then calling it:
socket.on('new-user', async (user) => {
const fetchVideo = await db.fetchVideoProm;
const checkAvail = await fetchVideo(user);
});

how can access property of class in promise?

I have a class that have one method .I want change return type of my method to promise . but in promise can not access to properties of class .how can resolve this .But get this exception
Reason: TypeError: Cannot read property 'bot' of undefined
const SDK = require('balebot');
const Promise = require('bluebird');
import incoming from './incoming';
const _ = require('lodash');
class Bale {
constructor(bp, config) {
if (!bp || !config) {
throw new Error('You need to specify botpress and config');
}
this.bot = null;
this.connected = false;
this.bot = new SDK.BaleBot(config.botToken);
bp.logger.info('bale bot created');
}
setConfig(config) {
this.config = Object.assign({}, this.config, config);
}
sendText(chat, text, options) {
let msg = new SDK.TextMessage(text);
return new Promise(function (resolve, reject) {
var response = this.bot.send(msg, receiver);
if (response) {
reject(err);
} else {
resolve(response);
}
});
}
}
module.exports = Bale;
You need to bind this or use Arrow functions to preserve this context:
const SDK = require('balebot');
const Promise = require('bluebird');
import incoming from './incoming';
const _ = require('lodash');
class Bale {
constructor(bp, config) {
if (!bp || !config) {
throw new Error('You need to specify botpress and config');
}
this.bot = null;
this.connected = false;
this.bot = new SDK.BaleBot(config.botToken);
bp.logger.info('bale bot created');
}
setConfig(config) {
this.config = Object.assign({}, this.config, config);
}
sendText(chat, text, options) {
let msg = new SDK.TextMessage(text);
// Use an arrow function instead of function
return new Promise((resolve, reject) => {
var response = this.bot.send(msg, receiver);
if (response) {
reject(err);
} else {
resolve(response);
}
});
}
}
module.exports = Bale;
This would work
sendText() {
return new Promise((resolve, reject) => {
console.log(this.bot); // it will not be undefined
});
}
The reason this works is because arrow functions lexically bind their context so this actually refers to the originating context.

how to connect to mongodb synchronously in nodejs

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 => {...});

Resources