What is the proper way to reuse a MongoDB connection? - javascript

I am needing to reuse a MongoDB connection in multiple different files for my Electron app but am having an issue that I don't understand. This led me to create a module to handle this.
db.js
const MongoClient = require('mongodb').MongoClient;
require('dotenv').config();
let db;
function connect () {
return MongoClient.connect(`mongodb://${process.env.DB_HOST}?authSource=${process.env.DB_NAME}`, {
auth: {
user: process.env.DB_USER,
password: process.env.DB_PASS
},
useNewUrlParser: true
}).then(client => {
db = client.db(process.env.DB_NAME);
}).catch(error => {
console.error(error);
});
}
function getDB () {
return db;
}
module.exports = { connect, getDB };
I them required this in my main file which is one of the files it is needed in.
app.js
const mongoDB = require(path.resolve(`${__dirname}/assets/js/db`));
let db;
app.on('ready', async () => {
await mongoDB.connect();
db = mongoDB.getDB();
setTimeout(createWindow, 0);
});
This works and I can use it in the createWindow function. a few seconds after, the app load the index.html file along with the index.js file and creates the window. index.js is another file I need to use it in so I require it there as well.
index.js
const mongoDB = require(path.resolve(`${__dirname}/js/db`));
const db = mongoDB.getDB();
console.log(db);
This results in db being undefined. Isn't the first require in app.js supposed to be cached?

Related

Not able to connect mongodb to nodejs

I have installed mongoose using npm install mongoose .But when i try to run the js file its not connecting.
see my code below.
const c = require('config')
const express= require('express')
const app= express()
const port=3000
const mongoose = require('mongoose')
const url = "mongodb+srv://mongouser:mongouser#cluster0.jpdno.mongodb.net/myFirstDatabase?retryWrites=true&w=majority"
const connectdb = async () => {
await mongoose.connect(url)
console.log('db connected')
}
app.listen(port,() => console.log("listening..."))
I am not getting any errors but after listening.. nothing is showing.
I am new to this please help me.
You are on the right path. To check whether you successfully connected to database use:
mongoose.connection.on('connected', () => { console.log('Successfully connected to database' });
Also you want to return your connection so you could use it later for database manipulation. It should look like this:
const connectdb = async () => {
await mongoose.connect(url);
return mongoose.connection; // Please note that you are returning reference
}
Last notes, I believe async/await is redundant when dealing with mongoose inside Node.js and you should probably want to include some option argument on your connect method:
mongoose.connect(url, {useNewUrlParser: true, useUnifiedTopology: true} );
Now you should be good to go!

How to use Jest global Setup and Teardown in a nodeJS project?

I added tests to my node js project using jest but for each test suite there's a beforeAll method that creates a new test server and connects to a mongo database and an afterAll method that closes both test server and the database. I would like to perform the above tasks globally for all the test suites not one at a time. Below is a sample of my code.
app.js
const express = require("express");
const app = express();
const { connectToDb } = require("./startup/db");
require("./startup/routes")(app);
connectToDb();
...
const port = process.env.PORT || 3000;
if (process.env.NODE_ENV !== "test") {
app.listen(port, () => winston.info(`Listening on port ${port}...`));
}
module.exports = app;
auth.test.js
const request = require("supertest");
const http = require("http");
const { disconnectDb } = require("../../startup/db");
describe("auth middleware", () => {
let server;
beforeAll((done) => {
const app = require("../../app");
server = http.createServer(app);
server.listen(done);
});
afterAll((done) => {
server.close(done);
disconnectDb();
});
it("should return 401 if no token is provided", async () => {
const res = request(server)
.post("/api/genres")
.set("x-auth-token", "")
.send({ name: "genre1" });
expect(res.status).toBe(401);
});
...
jest.config.js
module.exports = {
testEnvironment: "node",
};
Try with this jest.config.js:
module.exports = {
testEnvironment: "node",
globalSetup: '<rootDir>/src/testSetup.ts'
};
And in testSetup.ts you can do:
// testSetup.ts
module.exports = async () => {
const app = require("../../app");
server = http.createServer(app);
server.listen(done);
};
use this config: setupFiles: ['./tests/setup.js']
your setup file should look like this:
// setup.js
(async () => {
const app = require('../app.js')
global.app = app
})()
then you will be able to use app globally in every test suite
I had the same problem, I wanted to make one database connection before all test files and close the connection after all tests in all files.
But....I did not achieve what I wanted and MAYBE we don't need to do this.
I found a solution to launch functions beforeAll(),afterAll() etc... really before ALL TEST FILES and after ALL TEST FILES etc..
So you define these functions once in a certain file and they run for every test file.
To do that, all we need is to create a setupFile.ts and add path to this file in jest.config or in package.json "setupFilesAfterEnv": ["<rootDir>/__tests__/settings/setupTests.ts"],
Here is an example of my jest configuration.
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"setupFilesAfterEnv": ["<rootDir>/__tests__/settings/setupTests.ts"],
"rootDir": "src",
"verbose": true,
"clearMocks": true,
"testMatch": [
"**/**/*.test.ts"
]
},
Here is an example of setupFile.ts
import usersCollection from "../../database/user-schema";
import mongoose from "mongoose";
beforeAll(async () => {
try {
await mongoose.connect(process.env.MONGODB_URL!);
await usersCollection.deleteMany({});
} catch (error) {
console.log(error);
}
});
afterAll(async () => {
try {
await mongoose.disconnect();
} catch (error) {
console.log(error);
}
});
It means that we will establish a connection to the database FOR EVERY TEST FILE BEFORE ALL TESTS IN THAT FILE and close connection after all tests in every test file.
What I realized for myself:
In real life we have many test files and not every file needs a connection to a database.
It's perfectly fine to open a connection to a database in files which need a connection and close after all tests in that file, for example integration tests when we test API endpoints.
In other tests to not use real database for many unit tests we can consider to mock(simulate) a database. It's another very interesting topic 😊
If I say something wrong you can correct me
P.S
I also want to mention what is written in the Mongoose documentation
Do not use globalSetup to call mongoose.connect() or
mongoose.createConnection(). Jest runs globalSetup in a separate
environment, so you cannot use any connections you create in
globalSetup in your tests.
https://mongoosejs.com/docs/jest.html

Javascript: exporting objects initialized asynchronously [duplicate]

I'm kinda new to module creation and was wondering about module.exports and waiting for async functions (like a mongo connect function for example) to complete and exporting the result. The variables get properly defined using async/await in the module, but when trying to log them by requiring the module, they show up as undefined. If someone could point me in the right direction, that'd be great. Here's the code I've got so far:
// module.js
const MongoClient = require('mongodb').MongoClient
const mongo_host = '127.0.0.1'
const mongo_db = 'test'
const mongo_port = '27017';
(async module => {
var client, db
var url = `mongodb://${mongo_host}:${mongo_port}/${mongo_db}`
try {
// Use connect method to connect to the Server
client = await MongoClient.connect(url, {
useNewUrlParser: true
})
db = client.db(mongo_db)
} catch (err) {
console.error(err)
} finally {
// Exporting mongo just to test things
console.log(client) // Just to test things I tried logging the client here and it works. It doesn't show 'undefined' like test.js does when trying to console.log it from there
module.exports = {
client,
db
}
}
})(module)
And here's the js that requires the module
// test.js
const {client} = require('./module')
console.log(client) // Logs 'undefined'
I'm fairly familiar with js and am still actively learning and looking into things like async/await and like features, but yeah... I can't really figure that one out
You have to export synchronously, so its impossible to export client and db directly. However you could export a Promise that resolves to client and db:
module.exports = (async function() {
const client = await MongoClient.connect(url, {
useNewUrlParser: true
});
const db = client.db(mongo_db);
return { client, db };
})();
So then you can import it as:
const {client, db} = await require("yourmodule");
(that has to be in an async function itself)
PS: console.error(err) is not a proper error handler, if you cant handle the error just crash
the solution provided above by #Jonas Wilms is working but requires to call requires in an async function each time we want to reuse the connection. an alternative way is to use a callback function to return the mongoDB client object.
mongo.js:
const MongoClient = require('mongodb').MongoClient;
const uri = "mongodb+srv://<user>:<pwd>#<host and port>?retryWrites=true";
const mongoClient = async function(cb) {
const client = await MongoClient.connect(uri, {
useNewUrlParser: true
});
cb(client);
};
module.exports = {mongoClient}
then we can use mongoClient method in a diffrent file(express route or any other js file).
app.js:
var client;
const mongo = require('path to mongo.js');
mongo.mongoClient((connection) => {
client = connection;
});
//declare express app and listen....
//simple post reuest to store a student..
app.post('/', async (req, res, next) => {
const newStudent = {
name: req.body.name,
description: req.body.description,
studentId: req.body.studetId,
image: req.body.image
};
try
{
await client.db('university').collection('students').insertOne({newStudent});
}
catch(err)
{
console.log(err);
return res.status(500).json({ error: err});
}
return res.status(201).json({ message: 'Student added'});
};

ReferenceError when using MongoDB Collection variable in external resolver file that was imported via mergeResolvers

This is a totally reduced example to better explain the issue! So when I use the resolver Query getAllUsers, the MongoDB Collection Users is not available in the external resolver file user.js. So when I send that query I get:
ReferenceError: Users is not defined
That's a correct behaviour. But I do not want to include all the resolvers in my index.js, because I have a better modularization in this way. So I have all my typedefs and resolvers in external files like this.
Current file structure
index.js
/graphql
/typdef
user.graphql
/resolver
user.js
The user.graphql schema is correctly working. It is just the user.js that is producing the error when I execute the query with the not available Users variable, as already said.
Here the index.js and user.js.
index.js
import express from 'express'
import cors from 'cors'
const app = express()
app.use(cors())
import bodyParser from 'body-parser'
import {graphqlExpress, graphiqlExpress} from 'graphql-server-express'
import {makeExecutableSchema} from 'graphql-tools'
import {fileLoader, mergeTypes, mergeResolvers} from 'merge-graphql-schemas';
import {writeFileSync} from 'fs'
const typeDefs = mergeTypes(fileLoader(`${__dirname}/graphql/typedef/*.graphql`), { all: true })
writeFileSync(`${__dirname}/graphql/typedef.graphql`, typeDefs)
export const start = async () => {
try {
const MONGO_URL = 'mongodb://localhost:27017'
const MongoClient = require('mongodb').MongoClient;
MongoClient.connect(MONGO_URL, function(err, client) {
console.log("Connected successfully to server");
const db = client.db('project');
const Users = db.collection('user')
});
const URL = 'http://localhost'
const homePath = '/graphql'
const PORT = 3001
app.use(
homePath,
bodyParser.json(),
graphqlExpress({schema})
)
app.use(homePath,
graphiqlExpress({
endpointURL: homePath
})
)
app.listen(PORT, () => {
console.log(`Visit ${URL}:${PORT}${homePath}`)
})
} catch (e) {
console.log(e)
}
}
user.js
export default {
Query: {
getAllUsers: async () => {
return (await Users.find({}).toArray()).map(prepare)
}
}
}
What is the best way to pass the MongoDB or the Users collection to the resolver files. Or is there an even better solution for this issue?
First of all, this is NOT a proper solution, because declaring global variables while outsourcing schema is a bad design at all. But it works out and maybe this way someone gets an idea about how to improve this fix.
So to solve the issue all I had to do is changing the variable from local const to global.
So in index.js const Users = db.collection('user') is rewritten by global.Users = db.collection('user').
Same for the user.js. Here return (await Users.find({}).toArray()).map(prepare) is rewritten by return (await global.Users.find({}).toArray()).map(prepare).

mongodb: cannot define variable [duplicate]

I'm using the node-mongodb-native driver with MongoDB to write a website.
I have some questions about how to manage connections:
Is it enough using only one MongoDB connection for all requests? Are there any performance issues? If not, can I setup a global connection to use in the whole application?
If not, is it good if I open a new connection when request arrives, and close it when handled the request? Is it expensive to open and close a connection?
Should I use a global connection pool? I hear the driver has a native connection pool. Is it a good choice?
If I use a connection pool, how many connections should be used?
Are there other things I should notice?
The primary committer to node-mongodb-native says:
You open do MongoClient.connect once when your app boots up and reuse
the db object. It's not a singleton connection pool each .connect
creates a new connection pool.
So, to answer your question directly, reuse the db object that results from MongoClient.connect(). This gives you pooling, and will provide a noticeable speed increase as compared with opening/closing connections on each db action.
Open a new connection when the Node.js application starts, and reuse the existing db connection object:
/server.js
import express from 'express';
import Promise from 'bluebird';
import logger from 'winston';
import { MongoClient } from 'mongodb';
import config from './config';
import usersRestApi from './api/users';
const app = express();
app.use('/api/users', usersRestApi);
app.get('/', (req, res) => {
res.send('Hello World');
});
// Create a MongoDB connection pool and start the application
// after the database connection is ready
MongoClient.connect(config.database.url, { promiseLibrary: Promise }, (err, db) => {
if (err) {
logger.warn(`Failed to connect to the database. ${err.stack}`);
}
app.locals.db = db;
app.listen(config.port, () => {
logger.info(`Node.js app is listening at http://localhost:${config.port}`);
});
});
/api/users.js
import { Router } from 'express';
import { ObjectID } from 'mongodb';
const router = new Router();
router.get('/:id', async (req, res, next) => {
try {
const db = req.app.locals.db;
const id = new ObjectID(req.params.id);
const user = await db.collection('user').findOne({ _id: id }, {
email: 1,
firstName: 1,
lastName: 1
});
if (user) {
user.id = req.params.id;
res.send(user);
} else {
res.sendStatus(404);
}
} catch (err) {
next(err);
}
});
export default router;
Source: How to Open Database Connections in a Node.js/Express App
Here is some code that will manage your MongoDB connections.
var MongoClient = require('mongodb').MongoClient;
var url = require("../config.json")["MongoDBURL"]
var option = {
db:{
numberOfRetries : 5
},
server: {
auto_reconnect: true,
poolSize : 40,
socketOptions: {
connectTimeoutMS: 500
}
},
replSet: {},
mongos: {}
};
function MongoPool(){}
var p_db;
function initPool(cb){
MongoClient.connect(url, option, function(err, db) {
if (err) throw err;
p_db = db;
if(cb && typeof(cb) == 'function')
cb(p_db);
});
return MongoPool;
}
MongoPool.initPool = initPool;
function getInstance(cb){
if(!p_db){
initPool(cb)
}
else{
if(cb && typeof(cb) == 'function')
cb(p_db);
}
}
MongoPool.getInstance = getInstance;
module.exports = MongoPool;
When you start the server, call initPool
require("mongo-pool").initPool();
Then in any other module you can do the following:
var MongoPool = require("mongo-pool");
MongoPool.getInstance(function (db){
// Query your MongoDB database.
});
This is based on MongoDB documentation. Take a look at it.
Manage mongo connection pools in a single self contained module. This approach provides two benefits. Firstly it keeps your code modular and easier to test. Secondly your not forced to mix your database connection up in your request object which is NOT the place for a database connection object. (Given the nature of JavaScript I would consider it highly dangerous to mix in anything to an object constructed by library code). So with that you only need to Consider a module that exports two methods. connect = () => Promise and get = () => dbConnectionObject.
With such a module you can firstly connect to the database
// runs in boot.js or what ever file your application starts with
const db = require('./myAwesomeDbModule');
db.connect()
.then(() => console.log('database connected'))
.then(() => bootMyApplication())
.catch((e) => {
console.error(e);
// Always hard exit on a database connection error
process.exit(1);
});
When in flight your app can simply call get() when it needs a DB connection.
const db = require('./myAwesomeDbModule');
db.get().find(...)... // I have excluded code here to keep the example simple
If you set up your db module in the same way as the following not only will you have a way to ensure that your application will not boot unless you have a database connection you also have a global way of accessing your database connection pool that will error if you have not got a connection.
// myAwesomeDbModule.js
let connection = null;
module.exports.connect = () => new Promise((resolve, reject) => {
MongoClient.connect(url, option, function(err, db) {
if (err) { reject(err); return; };
resolve(db);
connection = db;
});
});
module.exports.get = () => {
if(!connection) {
throw new Error('Call connect first!');
}
return connection;
}
If you have Express.js, you can use express-mongo-db for caching and sharing the MongoDB connection between requests without a pool (since the accepted answer says it is the right way to share the connection).
If not - you can look at its source code and use it in another framework.
You should create a connection as service then reuse it when need.
// db.service.js
import { MongoClient } from "mongodb";
import database from "../config/database";
const dbService = {
db: undefined,
connect: callback => {
MongoClient.connect(database.uri, function(err, data) {
if (err) {
MongoClient.close();
callback(err);
}
dbService.db = data;
console.log("Connected to database");
callback(null);
});
}
};
export default dbService;
my App.js sample
// App Start
dbService.connect(err => {
if (err) {
console.log("Error: ", err);
process.exit(1);
}
server.listen(config.port, () => {
console.log(`Api runnning at ${config.port}`);
});
});
and use it wherever you want with
import dbService from "db.service.js"
const db = dbService.db
I have been using generic-pool with redis connections in my app - I highly recommend it. Its generic and I definitely know it works with mysql so I don't think you'll have any problems with it and mongo
https://github.com/coopernurse/node-pool
I have implemented below code in my project to implement connection pooling in my code so it will create a minimum connection in my project and reuse available connection
/* Mongo.js*/
var MongoClient = require('mongodb').MongoClient;
var url = "mongodb://localhost:27017/yourdatabasename";
var assert = require('assert');
var connection=[];
// Create the database connection
establishConnection = function(callback){
MongoClient.connect(url, { poolSize: 10 },function(err, db) {
assert.equal(null, err);
connection = db
if(typeof callback === 'function' && callback())
callback(connection)
}
)
}
function getconnection(){
return connection
}
module.exports = {
establishConnection:establishConnection,
getconnection:getconnection
}
/*app.js*/
// establish one connection with all other routes will use.
var db = require('./routes/mongo')
db.establishConnection();
//you can also call with callback if you wanna create any collection at starting
/*
db.establishConnection(function(conn){
conn.createCollection("collectionName", function(err, res) {
if (err) throw err;
console.log("Collection created!");
});
};
*/
// anyother route.js
var db = require('./mongo')
router.get('/', function(req, res, next) {
var connection = db.getconnection()
res.send("Hello");
});
If using express there is another more straightforward method, which is to utilise Express's built in feature to share data between routes and modules within your app. There is an object called app.locals. We can attach properties to it and access it from inside our routes. To use it, instantiate your mongo connection in your app.js file.
var app = express();
MongoClient.connect('mongodb://localhost:27017/')
.then(client =>{
const db = client.db('your-db');
const collection = db.collection('your-collection');
app.locals.collection = collection;
});
// view engine setup
app.set('views', path.join(__dirname, 'views'));
This database connection, or indeed any other data you wish to share around the modules of you app can now be accessed within your routes with req.app.locals as below without the need for creating and requiring additional modules.
app.get('/', (req, res) => {
const collection = req.app.locals.collection;
collection.find({}).toArray()
.then(response => res.status(200).json(response))
.catch(error => console.error(error));
});
This method ensures that you have a database connection open for the duration of your app unless you choose to close it at any time. It's easily accessible with req.app.locals.your-collection and doesn't require creation of any additional modules.
Best approach to implement connection pooling is you should create one global array variable which hold db name with connection object returned by MongoClient and then reuse that connection whenever you need to contact Database.
In your Server.js define var global.dbconnections = [];
Create a Service naming connectionService.js. It will have 2 methods getConnection and createConnection.
So when user will call getConnection(), it will find detail in global connection variable and return connection details if already exists else it will call createConnection() and return connection Details.
Call this service using <db_name> and it will return connection object if it already have else it will create new connection and return it to you.
Hope it helps :)
Here is the connectionService.js code:
var mongo = require('mongoskin');
var mongodb = require('mongodb');
var Q = require('q');
var service = {};
service.getConnection = getConnection ;
module.exports = service;
function getConnection(appDB){
var deferred = Q.defer();
var connectionDetails=global.dbconnections.find(item=>item.appDB==appDB)
if(connectionDetails){deferred.resolve(connectionDetails.connection);
}else{createConnection(appDB).then(function(connectionDetails){
deferred.resolve(connectionDetails);})
}
return deferred.promise;
}
function createConnection(appDB){
var deferred = Q.defer();
mongodb.MongoClient.connect(connectionServer + appDB, (err,database)=>
{
if(err) deferred.reject(err.name + ': ' + err.message);
global.dbconnections.push({appDB: appDB, connection: database});
deferred.resolve(database);
})
return deferred.promise;
}
In case anyone wants something that works in 2021 with Typescript, here's what I'm using:
import { MongoClient, Collection } from "mongodb";
const FILE_DB_HOST = process.env.FILE_DB_HOST as string;
const FILE_DB_DATABASE = process.env.FILE_DB_DATABASE as string;
const FILES_COLLECTION = process.env.FILES_COLLECTION as string;
if (!FILE_DB_HOST || !FILE_DB_DATABASE || !FILES_COLLECTION) {
throw "Missing FILE_DB_HOST, FILE_DB_DATABASE, or FILES_COLLECTION environment variables.";
}
const client = new MongoClient(FILE_DB_HOST, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
class Mongoose {
static FilesCollection: Collection;
static async init() {
const connection = await client.connect();
const FileDB = connection.db(FILE_DB_DATABASE);
Mongoose.FilesCollection = FileDB.collection(FILES_COLLECTION);
}
}
Mongoose.init();
export default Mongoose;
I believe if a request occurs too soon (before Mongo.init() has time to finish), an error will be thrown, since Mongoose.FilesCollection will be undefined.
import { Request, Response, NextFunction } from "express";
import Mongoose from "../../mongoose";
export default async function GetFile(req: Request, res: Response, next: NextFunction) {
const files = Mongoose.FilesCollection;
const file = await files.findOne({ fileName: "hello" });
res.send(file);
}
For example, if you call files.findOne({ ... }) and Mongoose.FilesCollection is undefined, then you will get an error.
npm i express mongoose
mongodb.js
const express = require('express');
const mongoose =require('mongoose')
const app = express();
mongoose.set('strictQuery', true);
mongoose.connect('mongodb://localhost:27017/db_name', {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => console.log('MongoDB Connected...'))
.catch((err) => console.log(err))
app.listen(3000,()=>{ console.log("Started on port 3000 !!!") })
node mongodb.js
Using below method you can easily manage as many as possible connection
var mongoose = require('mongoose');
//Set up default mongoose connection
const bankDB = ()=>{
return mongoose.createConnection('mongodb+srv://<username>:<passwprd>#mydemo.jk4nr.mongodb.net/<database>?retryWrites=true&w=majority',options);
}
bankDB().then(()=>console.log('Connected to mongoDB-Atlas bankApp...'))
.catch((err)=>console.error('Could not connected to mongoDB',err));
//Set up second mongoose connection
const myDB = ()=>{
return mongoose.createConnection('mongodb+srv://<username>:<password>#mydemo.jk4nr.mongodb.net/<database>?retryWrites=true&w=majority',options);
}
myDB().then(()=>console.log('Connected to mongoDB-Atlas connection 2...'))
.catch((err)=>console.error('Could not connected to mongoDB',err));
module.exports = { bankDB(), myDB() };

Categories

Resources