Close connection in mongodb - app is stuck - is it good? - node.js

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

Related

Promise.all() throwing error while connecting to database Error: timeout exceeded when used with Node-Postgres

I have Node.js express app with Postgres as a database. I'm using pg for database communication from the app.
This is how my db.service looks like
import { Pool } from 'pg';
const dbConfig = {/*my valid db configuration*/};
const pool = new Pool(dbConfig);
export const connectDB = async () => {
let client;
try {
client = await pool.connect();
} catch (error) {
console.error('error while connecting to database', error);
process.exit(1);
}
return client;
};
I have two queries as below
#1.
export const fetchUser = async (email) => {
const client = await connectDB();
const query = `
SELECT full_name FROM app.users
WHERE email = '${email}'
`;
let result;
try {
result = await client.query(query);
if (result.rowCount) {
return result.rows[0].full_name;
}
} catch (error) {
} finally {
await client.release();
}
return result;
};
#2
export const fetchWallet = async (email) => {
const client = await connectDB();
const query = `
SELECT wallet_money FROM app.user_wallet
WHERE email = '${email}'
`;
let result;
try {
result = await client.query(query);
if (result.rowCount) {
return result.rows[0].wallet_money;
}
} catch (error) {
} finally {
await client.release();
}
return result;
};
Now from one of my controller.js if I call these function separate await, no issues
ctrl.js
const fullName = await fetchUser('some#gmail.com');
const walletMoney = await fetchWallet('some#gmail.com');
No issues this way, however if I merge them into a single promise
const $0= fetchUser('some#gmail.com');
const $1= fetchWallet('some#gmail.com');
const result = await Promise.all([$0, $1]);
this throws the below error
error while connecting to database Error: timeout exceeded when trying
to connect at Error
Please suggest why this error is popping up & how can I get rid of it?
Thanks!
That's happening because you are trying to connect to DB separately for every query.Try to create one connection and use it for all queries!

Cannot close and open a new database connection with mongoose

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

Why does mongodb's official npm package(mongodb) can not catch some errors?

I am using the official mongodb driver package to develop some interfaces for operating the database, but I found that even if I use try and catch, I still cannot catch some errors. I use the windows operating system for development. If I want to connect to my database, I have to start the mongdb service first, but when I do not start the service and then try to use the official mongodb driver package to connect to the database, this npm package will not throw any error, this is just one of the cases where no error is thrown. Does this mongodb npm package provide any other way for users to catch errors?
This is the npm package I use: mongodb
This is my code:
db_config.js
const defaultUrl = 'mongodb://localhost:27017'
const defaultName = 'tumbleweed'
class DataBaseConfig {
#dbUrl
#dbName
constructor() {
this.#dbUrl = this.dbConnectUrl
this.#dbName = this.dbConnectName
}
get dbConnectUrl() {
return this.#dbUrl === undefined ? defaultUrl : this.#dbUrl
}
set dbConnectUrl(value) {
this.#dbUrl = value === undefined ? defaultUrl : value
}
get dbConnectName() {
return this.#dbName === undefined ? defaultName : this.#dbName
}
set dbConnectName(value) {
this.#dbName = value === undefined ? defaultName : value
}
}
const DataBaseShareConfig = new DataBaseConfig()
export default DataBaseShareConfig
db.js
import { MongoClient } from "mongodb";
import DataBaseShareConfig from "./db_config.js";
class DataBase {
#db
constructor() {
this.#db = null
}
async #connect() {
return new Promise(async (resolve, reject)=> {
try {
console.log(`begain to connecting: ${DataBaseShareConfig.dbConnectUrl}`)
const client = await MongoClient.connect(DataBaseShareConfig.dbConnectUrl)
this.#db = client.db(DataBaseShareConfig.dbConnectName)
console.log(`db: ${DataBaseShareConfig.dbConnectName} connected succeed`)
resolve(this.#db)
} catch (error) {
reject(error)
}
})
}
async find(collectionName, json) {
console.log("begain to find...")
return new Promise(async (resolve, reject)=> {
try {
if(!this.#db) {
await this.#connect()
const collection = this.#db.collection(collectionName)
const result = await collection.find(json).toArray()
resolve(result)
} else {
const collection = this.#db.collection(collectionName)
const result = await collection.find(json).toArray()
resolve(result)
}
} catch (error) {
reject(error)
}
})
}
}
const DataBaseShareInstance = new DataBase()
export default DataBaseShareInstance
main.js
import DataBaseShareInstance from "./db/db.js"
import DataBaseShareConfig from "./db/db_config.js"
DataBaseShareConfig.dbConnectUrl = 'mongodb://localhost:27017'
DataBaseShareConfig.dbConnectName = 'tumbleweed'
const main = (function () {
DataBaseShareInstance.find("users", {name: 'fq'}).then(result => {
console.log(result)
}).catch(error => {
console.log(error)
})
})()

Fastify Plugin Performance

I created a plugin for simple queries with caching and connection pooling. When i respond with that plugin (function), response is slower than before. So i wonder if I got the plugin thing wrong. Is this a correct use or am I making a mistake somewhere?
db.js
const fp = require('fastify-plugin')
const oracledb = require('oracledb');
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT;
oracledb.autoCommit = true;
module.exports = fp(async function (fastify, opts) {
fastify.decorate('simpleSelectWithCache', async function (key, ttl, sql) {
let cached = await fastify.cache.get(key);
if (cached) {
console.log('Cached:', cached.item);
return cached.item;
} else {
let connection;
try {
connection = await oracledb.getConnection();
const data = await connection.execute(sql);
fastify.cache.set(key, data.rows, ttl);
console.log('Real:', data.rows);
return data.rows;
// oracledb.getPool()._logStats(); // show pool statistics. _enableStats must be true
} catch (error) {
console.error(err);
} finally {
if (connection) await connection.close();
}
}
})
})
api.js
module.exports = async function (fastify, opts) {
fastify.get(
'/cached',
{
schema: {
description: 'Shared Api',
tags: ['Shared'],
},
},
async function (req, reply) {
const data = await fastify.simpleSelectWithCache('shared-cached', 60*1000, 'SELECT id FROM users WHERE id < 50')
reply.send(data);
}
);
};
Is this a correct use or am I making a mistake somewhere?
The connection is a heavy operation and, for every query, a new connection (aka a new socket) is created between your server and DB.
To optimize your plugin you need to create the connection pool at start:
module.exports = fp(async function (fastify, opts) {
await oracledb.createPool({
user: opts.user,
password: opts.password,
connectString: opts.connectString
})
fastify.decorate('simpleSelectWithCache', async function (key, ttl, sql) {
const cached = await fastify.cache.get(key)
if (cached) {
console.log('Cached:', cached.item)
return cached.item
} else {
let connection
try {
connection = await oracledb.getConnection()
const data = await connection.execute(sql)
fastify.cache.set(key, data.rows, ttl)
console.log('Real:', data.rows)
return data.rows
// oracledb.getPool()._logStats(); // show pool statistics. _enableStats must be true
} catch (error) {
console.error(error)
} finally {
if (connection) await connection.close()
}
}
})
fastify.addHook('onClose', (instance, done) => {
oracledb.getPool().close(10)
.then(done)
.catch(done)
})
})
// then register your plugin
fastify.register(myOraclePlugin, {
user: 'ora'
password: '1234',
connectString: 'foo'
})

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

Resources