MERN Tutorial Issue: Cannot read properties of undefined (reading 'collection') - node.js

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

Related

How to reference the connected mongo db routes in api routes in node js

i just started learning mongo db , i connected my database atlas successfully but i need to call the client variable in api routes when i do so it is saying client not connected
const express = require("express");
const res = require("express/lib/response");
const app = express()
const {MongoClient} = require("mongodb")
async function main(params) {
let uri = "mongodb+srv://usn:pwd#cluster0.tasg9.mongodb.net/movies?retryWrites=true&w=majority"
const client = new MongoClient(uri)
try{
await client.connect()
}catch(e){
console.log(e)
}
finally{
await client.close()
}
}
function ListDatabases(client) {
let databases = client.db().admin().listDatabases()
console.log("databases ->")
databases.databases.forEach(element => {
console.log(`--${element.name}`)
});
}
main()
app.get("/db", function (req, res) {
ListDatabases(client)
return res.json("db")
});
app.listen(8000,() => {
console.log("server running on port 8000")
})
how to reference the connected instance in api routes in the above code
You are closing the connection in finally block, so as soon as you are connecting, you are also disconnecting it right away.
Remove the finally block
finally {
await client.close()
}

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 🚀🚀🚀

How to connect to a MongoDB database from an application deployed on ZEIT Now?

I have deployed an application on Zeit Now using ExpressJS. The application makes a connection to MongoDB using Mongoose. However, the state of the connection, which I obtain using mongoose.connection.readyState is displayed as 2, which denotes 'connecting'.
I tried running the application locally and it works fine, where I am able to write to the database.
const connectionURL = "mongodb+srv://MONGODB_USERNAME:MONGOD_BPASSWORD#cluster1-23abc.mongodb.net/DATABASE_NAME?retryWrites=true"
expressApp.listen(3000, function() {
console.log("Listening to port 3000");
});
mongoose
.connect(
connectionURL, { useNewUrlParser: true }
)
.then(function() {
console.log("db connected!");
});
expressApp.get("/", function(req, res) {
res.write(`Connection State: ${mongoose.connection.readyState}\n`);
res.end();
});
I would expect mongoose.connection.readyState to be 1, which denotes 'connected'.
However, mongoose.connection.readyState is stuck at 2, which denotes 'connecting'.
Also, now logs does not show any errors.
You'll want to cache your MongoDB connection so you won't have to make a new connection on each lamda call.
You can make an lib folder and empty mongoose.js file (lib/mongoose.js) as I did and place this code inside:
`import mongoose from 'mongoose';
let cachedDb = null;
console.log('outside-cachedDB:', cachedDb);
async function connectToDatabase(uri) {
if (cachedDb) {
console.log('=> using cached database instance');
return cachedDb;
}
// If no connection is cached, create a new one
const db = await mongoose.connect(uri, { useNewUrlParser: true });
console.log('New MongoDB Connected');
// Cache the database connection and return the connection
cachedDb = db;
return db;
}
export default async () => {
await connectToDatabase(process.env.MONGODB_URI);
};`
Then call this custom mongoose function in any lamda that needs a mongoDB connection:
`import express from "express";
import mongoose from "../lib/mongoose";
const app = express();
// #route Get api/connect
// #desc Tests post route
// #access Public
app.get("*", async (req, res) => {
await mongoose();
// Code to query your DB or whatever here
});
export default app;`
You don't have to use express of course, but I personally haven't moved on to newer solutions. One of these days I'll learn micro.js.

how to use the connection.db.collection function?

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

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