how to use the connection.db.collection function? - node.js

I have implemented the following code from this link:
What is best way to handle global connection of Mongodb in NodeJs
to create a class for the connection of MongoDB. But when I try to call the singleton class in the router, I get the following error:
TypeError: Connection.db.collection is not a function
mongodb.js
const MongoClient = require('mongodb').MongoClient
const url = '...';
class Connection {
static connectToDB() {
if ( this.database ) return Promise.resolve(this.database)
return MongoClient.connect(this.url, {useNewUrlParser: true}, (err, db) => {
if (err) console.log(err);
else {
this.db = db;
console.log('MongoDB connected');
}
})
}
}
Connection.db = null
Connection.url = url
Connection.options = {
bufferMaxEntries: 0,
reconnectTries: 5000,
}
module.exports = Connection;
app.js
const express = require('express');
const app = express();
let bodyParser = require('body-parser')
// mongodb config
const Connection = require('../config/mongodb');
const server = app.listen(3000, () => {
Connection.connectToDB(); // output: MongoDB connected
console.log(`listening to port: ${port} on http://127.0.0.1:3000}/`); // output: listening to port: 3000 on http://127.0.0.1:3000/
});
router.js
const router = require('express').Router();
let Connection = require('../config/mongodb');
router.post('/data', (req, res) => {
Connection.db.collection('tweets').find({}) // output: TypeError: Connection.db.collection is not a function
.then(tweets => console.log(tweets))
.catch(err => console.log(err));
});

Try once to package.json, change mongodb line to "mongodb": "^2.2.33". You will need to npm uninstall mongodb; then npm install to install this version.

The question you linked uses promises throughout, whereas you're using the callback version of connect.
return MongoClient.connect(this.url, {useNewUrlParser: true}, (err, db) => ...
You then call this without returning in your server:
Connection.connectToDB();
console.log(`listening to port: ${port} on http://127.0.0.1:3000}/`);
There is therefore no guarantee that your connection will have been made by the time your first api request comes in. In fact, if you did:
Connection.connectToDB();
console.log(`listening to port: ${port} on http://127.0.0.1:3000}/`);
Connection.db.collection('tweets').find({});
It would fail every time as Connection.db will still be null.
In the example you linked, using Promises protect against that. Note in particular the connect method:
static connectToDB() {
if ( this.database ) return Promise.resolve(this.database)
// ** USING THE PROMISE VERSION OF CONNECT **
return MongoClient.connect(this.url, this.options)
.then(db => this.db = db)
}
and your usage code should also use promises:
return Connection.connectToDB()
.then(() => {
// do something here
});

Related

MERN Tutorial Issue: Cannot read properties of undefined (reading 'collection')

I am following the MongoDB MERN tutorial and when my front-end tries to connect to the DB to pull documents it errors out. I have pulled the official version of their GitHub repo and added my connection information and it works properly with theirs. The only differences I can find is theirs uses mongoose, which the tutorial doesn't reference, and the versions of the packages are older.
Tutorial: https://www.mongodb.com/languages/mern-stack-tutorial
npm version: 9.4.1
Error
$ npm start
> server#1.0.0 start
> node server.js
Server is running on port: 5000
TypeError: Cannot read properties of undefined (reading 'collection')
at D:\MERN\mine\server\routes\record.js:19:6
at Layer.handle [as handle_request] (D:\MERN\mine\server\node_modules\express\lib\router\layer.js:95:5)
at next (D:\MERN\mine\server\node_modules\express\lib\router\route.js:144:13)
at Route.dispatch (D:\MERN\mine\server\node_modules\express\lib\router\route.js:114:3)
at Layer.handle [as handle_request] (D:\MERN\mine\server\node_modules\express\lib\router\layer.js:95:5)
at D:\MERN\mine\server\node_modules\express\lib\router\index.js:284:15
at Function.process_params (D:\MERN\mine\server\node_modules\express\lib\router\index.js:346:12)
at next (D:\MERN\mine\server\node_modules\express\lib\router\index.js:280:10)
at Function.handle (D:\MERN\mine\server\node_modules\express\lib\router\index.js:175:3)
at router (D:\MERN\mine\server\node_modules\express\lib\router\index.js:47:12)
See attached below image and code for line 19 of record.js.
https://media.discordapp.net/attachments/1072903958386983022/1072903958974189619/image.png
// This section will help you get a list of all the records.
recordRoutes.route("/record").get(function (req, res) {
let db_connect = dbo.getDb("employees");
db_connect
.collection("records")
.find({})
.toArray(function (err, result) {
if (err) throw err;
res.json(result);
});
});
Image showing WinMerge comparison of my repo vs GitHub repo.
https://media.discordapp.net/attachments/1072903958386983022/1072903959297146980/image.png?width=1017&height=389
I know that my connection credentials are fine as I have used them with MongoDB Compass and their GitHub repo.
I have added numerous console.log commands in places to try and determine what is being set when the server runs.
Adding console.logs within the connectToServer anonymous function never triggers even though it should occur within server.js on line 14.
server.js
const express = require("express");
const app = express();
const cors = require("cors");
require("dotenv").config({ path: "./config.env" });
const port = process.env.PORT || 5000;
app.use(cors());
app.use(express.json());
app.use(require("./routes/record"));
// get driver connection
const dbo = require("./db/conn");
app.listen(port, () => {
// perform a database connection when server starts
dbo.connectToServer(function (err) {
if (err) console.error(err);
});
console.log(`Server is running on port: ${port}`);
});
record.js - partial
const express = require("express");
// recordRoutes is an instance of the express router.
// We use it to define our routes.
// The router will be added as a middleware and will take control of requests starting with path /record.
const recordRoutes = express.Router();
// This will help us connect to the database
const dbo = require("../db/conn");
// This help convert the id from string to ObjectId for the _id.
const ObjectId = require("mongodb").ObjectId;
// This section will help you get a list of all the records.
recordRoutes.route("/record").get(function (req, res) {
let db_connect = dbo.getDb("employees");
db_connect
.collection("records")
.find({})
.toArray(function (err, result) {
if (err) throw err;
res.json(result);
});
});
Note: I did try installing mongoose npm install mongoose on the server and it didn't change the results.
If I modify the conn.js file to use async and await I can get details from the db such as a count of records from employees collection. However, none of the routes work properly for the React frontend, though they don't throw errors either.
Revamped conn.js
const { MongoClient } = require("mongodb");
const Db = process.env.ATLAS_URI;
const client = new MongoClient(Db, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
let _db;
module.exports = {
connectToServer: async function (callback) {
console.log("test");
try {
await client.connect();
} catch (e) {
console.error(e);
}
_db = client.db("employees");
try {
var count = await _db.collection("records").countDocuments();
console.log(count);
} catch (e) {
console.error(e);
}
if(_db !== undefined){
return true;
}
},
getDb: function () {
return _db;
},
};
connectToServer is asynchronous. That means that the code will continue to execute without waiting for this function to complete.
This function is where the _db value is set. Because of the way your code is executing, you're attempting to access the _db value via getDB() before it has been set. As a result, you get back an undefined value.
You'll need to await the connectToServer function, or provide a fallback inside of getDB to handle the scenario where the DB has not yet been initialized.
Thanks to Jake Haller-Roby I was led down the right path. This had to do with async and await.
However, the GitHub repo from the tutorial doesn't rely upon async and await and works fine. I am going to assume that some newer versions of mongodb or express with nodejs changes how things work.
Here is the code I ended up using.
server.js
const express = require("express");
const app = express();
const cors = require("cors");
require("dotenv").config({ path: "./config.env" });
const port = process.env.PORT || 5000;
app.use(cors());
app.use(express.json());
app.use(require("./routes/record"));
// get driver connection
const dbo = require("./db/conn");
app.listen(port, async () => {
// perform a database connection when server starts
await dbo.connectToServer(function (err) {
if (err) console.error(err);
});
console.log(`Server is running on port: ${port}`);
});
conn.js
const { MongoClient } = require("mongodb");
const Db = process.env.ATLAS_URI;
const client = new MongoClient(Db, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
let _db;
module.exports = {
connectToServer: async function (callback) {
try {
await client.connect();
} catch (e) {
console.error(e);
}
_db = client.db("employees");
return (_db === undefined ? false : true);
},
getDb: function () {
return _db;
},
};
Within the recordRoutes route for getting the list of records I ran into an issue with toArray where it was never returning its promise. After googling for a bit I found there are multiple ways of handling this. Using .then after toArray works as well as storing the results from the toArray in a variable and using an await on its call. Below are the two examples.
.then
// This section will help you get a list of all the records.
recordRoutes.route("/record").get(async function (req, response) {
let db_connect = dbo.getDb();
db_connect
.collection("records")
.find({})
.toArray()
.then((data) => {
console.log(data);
response.json(data);
});
});
try and await
// This section will help you get a list of all the records.
recordRoutes.route("/record").get(async function (req, response) {
let db_connect = dbo.getDb();
try {
var records = await db_connect
.collection("records")
.find({})
.toArray();
response.json(records);
} catch (e) {
console.log("An error occurred pulling the records. " + e);
}
});

Connection Not established in Mocha to MongoDB Atlas

When starting my application it connects to MongoDB Atlas as logged, however, when running mocha tests it does not even try to connect to the DB.
here is my server.js file
require('dotenv').config()
const express = require('express');
const connectDB = require('./DB/connection')
const app = express();
app.use(express.json())
const PORT = process.env.PORT
connectDB();
app.listen(PORT, () => console.log(`Server started at ${PORT}`))
and this is the connection..js file
const mongoose = require('mongoose');
const URI = `mongodb+srv://${process.env.DB_USERNAME}:${process.env.DB_PASSWORD}#betacluster.7jf4v.mongodb.net/servicenowclone?retryWrites=true&w=majority`
const connectDB = async () => {
try {
mongoose.connect(URI, {
useUnifiedTopology: true,
useNewUrlParser: true
});
mongoose.connection.once('open',function() {
console.log('connection established');
}).on('error',() => console.log('gi atay way connection sa database man'))
} catch (err) {
console.log(err)
}
}
which logs
Server started at 3000
connection established
so I know it connects to the DB successfully, however when creating tests using mocha, it doesn't even try to connect to the DB, here is the complete test file.
const mocha = require('mocha');
const assert = require('assert');
const ticketInstance = require('../models/ticket')
//describe tests
describe('saving a ticket', function () {
it('create ticket', async function (done) {
const newTicket = new ticketInstance({
number: 1,
type: 'Request',
customer: 'Carlo Principe',
description: 'first ticket created from a test',
subject:'test subject'
})
newTicket.save().then(function (){
assert(newTicket.isNew === false);
done()
})
});
})
Am I missing something, it logs timeout exceeded and does not show the connection established console.log I created in connection.js
Thanks!
The best way is mocha --delayed switch. mocha doc says
If you need to perform asynchronous operations before any of your suites are run (e.g., for dynamically generating tests), you may delay the root suite. Run mocha with the --delay flag.
For example, use mocha in this way mocha --recursive --exit --delay --ui tdd tests.js and --delayed enable you to trigger running the root suite via calling run() explicitly.
const fn = async x => {
return new Promise(resolve => {
setTimeout(resolve, 1500, 2 * x);
});
};
(async function() {
const z = await fn(3);
suite("won't run until run() executes", () => {})
run();
})();
For more information, please read https://mochajs.org/#delayed-root-suite.

Make post request to Mongodb Atlas using Nodejs

I was familiar with MongodB for CRUD operation. Here, I'm trying to make simple post request on mongodB atlas but I want to know where I have done error for the connection and posting data to MongodB atlas.
Model.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
let quizSchema = new Schema({
title: {
type: String,
},
description: {
type: Number,
},
question: {
type: String,
},
});
const Quiz = mongoose.model("Quiz", quizSchema);
module.exports = Quiz;
index.js
I'm trying to create the database collection name "QuizDatabase" and insert the data to it.
var express = require("express");
var bodyParser = require("body-parser");
const Quiz = require("./views/model/model");
var Request = require("request");
var app = express();
app.use(bodyParser.json());
const MongoClient = require("mongodb").MongoClient;
const uri =
"mongodb+srv://username:password#cluster0.iom1t.mongodb.net/QuizDatabase?retryWrites=true&w=majority";
const client = new MongoClient(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
mongoose.connect(uri);
var server = app.listen(process.env.PORT || 8080, function () {
var port = server.address().port;
console.log("App now running on port", port);
});
app.post("/new/", function (req, res) {
Quiz.collection("QuizDatabase").insertMany(req.body, function (err, doc) {
if (err) {
handleError(res, err.message, "Failed to create new quiz.");
} else {
res.status(201).send(JSON.stringify(body));
}
});
});
function handleError(res, reason, message, code) {
console.log("ERROR: " + reason);
res.status(code || 500).json({ error: message });
}
You dont have to use mongo client if you are already using mongoose.
In index.js file just import the model
const Quiz = require("./model");
And you are already using mongoose to connect to db when you write mongoose.connect(uri); You don't have to use client.connect() again.
Query to insert -
Quiz.insertMany(req.body);
Your index file should look like this -
const Quiz = require("./views/model/model");
var Request = require("request");
var app = express();
app.use(bodyParser.json());
const uri =
"mongodb+srv://username:password#cluster0.iom1t.mongodb.net/QuizDatabase?retryWrites=true&w=majority";
mongoose.connect(uri);
var server = app.listen(process.env.PORT || 8080, function () {
var port = server.address().port;
console.log("App now running on port", port);
});
app.post("/new/", function (req, res) {
Quiz.insertMany(req.body, function (err, doc) {
if (err) {
handleError(res, err.message, "Failed to create new quiz.");
} else {
res.status(201).send(JSON.stringify(body));
}
});
});
function handleError(res, reason, message, code) {
console.log("ERROR: " + reason);
res.status(code || 500).json({ error: message });
}
There are several reasons.
Connection Issues to the MongoDB database.
To check this insert app.listen() into mongoose connect. This would make sure you can only run development on your preferred PORT only when it has successfully connected to your Database. e.g From your code
mongoose.connect(uri)
.then(() => {
//listen for PORT request
var server = app.listen(process.env.PORT || 8080, function () {
var port = server.address().port;
console.log("App now running on port", port);
});
}).catch((error) => {
console.log(error);
});
Try purposely using the wrong Username or Password and see if you get this error:
MongoServerError: bad auth : Authentication failed.
at Connection.onMessage (/Users/user/Documents/..<pathway>../connection.js:207:30)
*
*
*
*
ok: 0,
code: 8000,
codeName: 'AtlasError',
[Symbol(errorLabels)]: Set(1) { 'HandshakeError' } }
If you don't get this error then you have a connection problem. To solve this, I added my current IP ADDRESS and 0.0.0.0/0 (includes your current IP address) at the Network Access page. So you click on MY CURRENT IP ADDRESS and confirm upon setting up the network. Go to NETWORK ACCESS, click on add new IP ADDRESS, input 0.0.0.0/0 and confirm. Then try using the wrong username or password in the URI link given to you to see if it gives the above-expected error, then you can now correct the Username and Password, and npm run dev or npm start (However you configured it in your package.json file).
Code issues
First of I would correct your Model.js file from this:
const Quiz = mongoose.model("Quiz", quizSchema);
module.exports = Quiz;
to this:
module.exports = mongoose.model("Quiz", quizSchema);
I can see why yours can work, but it may be an issue as you want to get the schema upon accessing the whole file.
Secondly, I would correct the code for Posting and you can do that in 2 ways using the asynchronous method. Which depends on the method of assigning the req.body.
Way 1:
app.post("/new/", async (req, res) => {
const { title, description, question } = req.body;
//adds doc to db
try {
const quiz = await Quiz.create({ title, description, question });
res.status(200).json(quiz);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
OR
Way2:
app.post("/new/", async (req, res) => {
const quiz = new Quiz(req.body);
//adds doc to db
try {
const savePost = await quiz.save();
response.status(200).send(savePost);
} catch (error) {
response.status(400).send(error);
}
});
NOTE: You don't necessarily have to create a named database and collection in Mongo Atlas before starting the project. The URI given to you covers that if there are no problems with the connection to the DB or the Code.
based on your code
URI:
"mongodb+srv://username:password#cluster0.iom1t.mongodb.net/QuizDatabase?retryWrites=true&w=majority";
would create a database called: QuizDatabase and collection called: quizs (MongoDb always creates the plural word from the model given and makes it start with lowercase (i.e from your Model.js, the mongoose.model("Quiz"))).
If no database is named in your URI, then a database called test is automatically created for you as a default database, with the collection name being the mongoose.model("") given.
CONCLUSION
This should solve at least 90% of your issues, any other creation/POST problems is currently beyond my current expertise. Happy Coding 🚀🚀🚀

Closing a MongoDB connection after running Mocha tests for an Express application

I have an Express application that looks like this.
const app = express();
...
...
...
router.post(...);
router.get(...);
router.delete(...);
app.use('/api/v1', router);
MongoClient.connect(mongoUri, { useNewUrlParser: true })
.then(client => {
const db = client.db('db_name');
const collection = db.collection('collection_name');
app.locals.collection = collection;
})
.catch(error => console.error(error));
const server = app.listen(settings.APIServerPort, () => console.log(`Server is listening on port ${settings.APIServerPort}.`));
module.exports = {
server,
knex // using this to connect to the RDBMS
}
The application uses both an RDBMS and Mongo.
I wrote tests for the application using Mocha and added the following block to the Mocha test.
const app = require('../app');
...test 1...
...test 2...
...test 3...
...
...
...
...test n...
after(async () => {
await app.knex.destroy();
});
The after hook closes out my connection to the RDBMS.
However, I don't know how to close the MongoDB connection once the test finishes.
Owing to keeping this connection open, the test never exits and hangs once all the tests have been run.
The closest answer that I have been able to find is this one - Keep MongoDB connection open while running tests using mocha framework.
However, I was unable to get to work for me.
Can someone please help with this?
Update
A combination of the answers below is what solved the problem.
const mongoClient = new MongoClient(mongoUri, { useNewUrlParser: true });
mongoClient.connect()
.then(client => {
const db = client.db('...');
const collection = db.collection('...');
app.locals.collection = collection;
})
.catch(error => console.error(error));
const server = app.listen(settings.APIServerPort, () => console.log(`Server is listening on port ${settings.APIServerPort}.`));
module.exports = {
server,
knex,
mongoClient
}
We can rewrite the mongo function to make it work
const client = new MongoClient(uri);
client.connect()
.then(client => {
const db = client.db('db_name');
const collection = db.collection('collection_name');
app.locals.collection = collection;
})
.catch(error => console.error(error));
And in the after block -
after(async () => {
await app.knex.destroy();
await client.close();
});

How to use mongoClient.connect with express?

I am getting started with mongoDB and I have to say that the official documentation is not that great to see how to implement it with nodejs.
I don't really know how to structure my server file to add mongoClient.connect, should my whole server be written inbeetwen the mongoClient.connect function in order to have access to the db, like in this boilerplate? I am using nodeJS/express.
If you know any good boilerplate, or anything, that could show me the structure of a backend with an implementation of mongoDB, I would really appreciate it. Every time I find something about mongoDB, it is actually about mongooooose!!
After further reasearch, here is what I was looking for, for those who wonder like me how to implement MongoDB (and not mongoose) with Express:
var express = require('express');
var mongodb = require('mongodb');
var app = express();
var MongoClient = require('mongodb').MongoClient;
var db;
// Initialize connection once
MongoClient.connect("mongodb://localhost:27017/integration_test", function(err, database) {
if(err) throw err;
db = database;
// Start the application after the database connection is ready
app.listen(3000);
console.log("Listening on port 3000");
});
// Reuse database object in request handlers
app.get("/", function(req, res) {
db.collection("replicaset_mongo_client_collection").find({}, function(err, docs) {
docs.each(function(err, doc) {
if(doc) {
console.log(doc);
}
else {
res.end();
}
});
});
});
I've found several ways of doing it, even in mongoDB's official pages.
By far, I prefer this one (not mine, source below) where you instantiate the connection in one file and export it and the database/client to the server file where express is instantiated:
(I copied only what's important, without error handling)
// database.js
const MongoClient = require('mongodb').MongoClient;
let _db; //'_' private
const mongoConnect = function(callback) {
MongoClient.connect(
'mongodb://localhost:27017',
{ useUnifiedTopology: true }
)
.then(client => {
_db = client.db('onlineshopping');
callback();
})
.catch(error => {
console.log(err);
throw new Error('DB connection failed...');
});
}
const getDB = () => {
if (_db) {
return _db;
} else {
throw new Error('DB connect failed');
}
}
exports.mongoConnect = mongoConnect;
exports.getDB = getDB;
// index.js
const express = require('express');
const app = express();
const mongoConnect = require('./util/database').mongoConnect;
// ...
mongoConnect(() => {
app.listen(3000);
})
Source:
https://github.com/TinaXing2012/nodejs_examples/blob/master/day9/util/database.js
Corresponding to this YouTube course that I recommend in this topic: https://www.youtube.com/watch?v=hh-gK0_HLEY&list=PLGTrAf5-F1YLBTY1mToc_qyOiZizcG_LJ&index=98
Other alternatives from mongoDB official repos, are:
https://github.com/mongodb-developer/mern-stack-example
https://github.com/mongodb-developer/nodejs-quickstart

Resources