Multiple MongoDB queries in a Javascript function? - javascript

How can I execute the following queries together in a single javascript function for mongoDB?
//find the reviews from the reviews collection
var proRev = db.reviews.find({productID: "123"}).toArray();
//update the products collection
db.products.update({productID : "123"},{$push:{Reviews:proRev}},{multi:true});
//remove the reviews from the reviews collection
db.reviews.remove({productID: "123"});
The function would be based on finding the reviews for productID "123" from a reviews collection, and inserting them as an array, in a new field for productID "123" found in the products collection.
Rather than executing the queries seperate, I would like them to execute in a single function - I'm a javascript noob so sorry if this is a stupid question.
Thanks

Use promises.
Example:
const MongoClient = require('mongodb').MongoClient;
const url = 'mongodb://localhost:27017';
const dbName = 'myproject';
(async function() {
let client;
try {
client = await MongoClient.connect(url);
console.log("Connected correctly to server");
const db = client.db(dbName);
const [res1, res2] = await Promise.all([
db.collection('inserts').findOne({
foo: "bar"
}),
db.collection('inserts').findOne({
bar: "foo"
})
]);
} catch (err) {
console.log(err.stack);
}
// Close connection
client.close();
})();
If you want to do it in one operation within MongoDB, try using findOneAndUpdate

Related

How can use the .count() method in mongoose to count multiple collections

I have a operation that counts all of the registered users which there is a collection named registered-users in my MongoDB.
Here is how I accomplished that:
const User = require("../models/User")
const Topic = require("../models/Topic")
router.get('/', async (req, res) => {
await User.count((err, registeredUsers) => {
if (err){
console.log(err)
} else {
res.render("register", {registeredUsers})// using EJS for my template engine
}
})
})
so this let's me count the total users registered but I have another collection in the db named topics that I would like to get a total count of as well. how can I do that without having to res.render again (I'm not even sure if that works or not) and pass in another object? Is this possible to count the users and the topics (which is a different collection and model)?
The goal is that in the ejs it has something that says :
Total Users signed up: registeredUsers
Total Topics created: totalTopics
Model.count()
Counts number of documents that match filter in a database collection.
This method is deprecated. If you want to count the number of documents in a collection, e.g. count({}), use the estimatedDocumentCount() function instead. Otherwise, use the countDocuments() function instead.
source: https://mongoosejs.com/docs/api.html#model_Model-count
Also, since you are using async function, we can use Promise.all() to run queries in parallel.
const User = require("../models/User")
const Topic = require("../models/Topic")
router.get('/', async (req, res) => {
const [userCount, topicCount] = await Promise.all([
User.countDocuments({}),
Topic.countDocuments({})
])
// using EJS for my template engine
res.render("register", {
registeredUsers: userCount,
totalTopics: topicCount
})
})

Javascript Access an an Object in an Array

I'm fetching data from MongoDB, and the response is coming through fine, however it appears to be wrapped in array when it comes out of the User.find() function.
For example, one response is:
[{"_id":"62fe3c888e2776ef3c1a010f","username":"Drago D Trial","password":"U2FsdGVkX1867hs26KL0KitTGhWnP9tdVX6AcmI5pWE=","fullname":"Drago DaTrial","firstname":"","surname":"","email":"drago#hotmail.com","position":"QA Tester","userImage":"","locationCity":"","country":"","role":"","company":"","emailAuthorised":true,"professionalBio":"","positionRecentTitle":"","positionRecentCompany":"","companyAuthorised":"","isAdmin":false,"createdAt":"2022-08-18T13:20:08.045Z","updatedAt":"2022-08-18T13:21:02.619Z","__v":0}]
I'm accessing this through an api like this:
router.get('/inviteToJoinTeam/:token/:email', async (req, res) => {
try {
//verify the token against DB
const userToken = (req.params.token)
const indivEmailAdd = (req.params.email)
// creating user auth
try{
const userDetails = await User.find({email: indivEmailAdd})
const indivIDAdd = await userDetails (!want to access the data here and just get ID)
res.send(indivIDAdd)
}catch{
console.log('failure')
}
} catch (e) {
res.send('This isnt working');
}
});
How would you access this and just get the _id field out?
If there is only one item in the array then - simply get the id property of the first item intthe returned array
const indivIDAdd = await userDetails[0]['_id'];
or using dot notation
const indivIDAdd = await userDetails[0]._id;
if there are multiple results then map over the results and get the id from each
const ids = await userDetails.map(user => user._id);
just use response[0]._id
Ps: Response is the array coming from the database
Try projection for the same it should work
const userDetails = await User.find({ email: indivEmailAdd }, { _id : 1 })
it will return array of ObjectId. if you need to get only one object then use findOne instead of find.
According to me you have 2 solutions :
Option 1 use findOne instead of find :
const userDetails = await User.findOne({email: indivEmailAdd});
Option 2 access array / object with basic js:
const usersDetails = await User.find({email: indivEmailAdd});
const userDetails = usersDetails.at(0)._id; // or
const userDetails = usersDetails[0]['_id'];

Mongoose keep duplicate elements

I have a function that create guild entry for DiscordJS, but when the script start and also if the function is called multiple times, it create around 400 duplicate documents, it create by ID and the ID is unique, so it's not normal
My schema structure only have a ID type String and unique is true
client.createGuild = async guild => {
const exist = await Guild.findOne({ id: guild.id });
if(!exist) {
await Guild.create({ id: guild.id }); // new Guild().save() keep duplicate too
}
}
It look like the if statement doesn't exist
const Schema = mongoose.Schema;
const FooSchema = new Schema({
id: { type: String, index: true, unique: true }
});
const Foo = mongoose.model('Foo', FooSchema);
Foo.createIndexes();
If collection already exists. Create index manually to the collection via atlas or cmd.
You can combine getData and createData functions to one. Here is the example:
const mongoose = require('mongoose');
async function getData(Guild, guild) {
if (!mongoose.connection.readyState) await mongoose.connect('MONGO_URL'); // In case you haven't connect to database
const data = await Guild.findOne({ id: guild.id }); // get data from database
if (!data) {
return new Guild({
id: guild.id,
}); // If no data exists for the guild, return new model
}
return data; // If the data already exists, return that
}
Now if you want to get data from mongodb you just call the function. It automatically create and save a new one if there is not.
Comment if you still have any problem or you have got what you need.
Make sure to call the function with await or it won't return the data.

Mongoose - find() not returning anything when no parameters are passed, but returns data when parameters are passed

I have the following model in teamMembers.js:
const { Schema, model } = require('mongoose');
const teamMembersSchema = new Schema({
uid: String,
name: String,
hours: Number
})
const TeamMembers = model('teamMembers', TeamMembersSchema);
module.exports = TeamMembers;
I've created the following endpoints in teamMemberRoute.js:
const TeamMembers = require('./models/teamMembers');
module.exports = (app) => {
app.get('/api/pods/teamMembers/:uid', async (req, res) => {
let teamMember = await TeamMembers.find( {'uid': req.params.uid } );
return res.status(200).send(teamMember);
});
app.get('/api/pods/teamMembers', async (req, res) => {
let teamMembers = await TeamMembers.find();
return res.status(200).send(teamMembers);
});
}
The first endpoint (/api/pods/teamMembers/:uid) works just fine - when I pass a uid it returns documents specific to that uid in the TeamMember collection.
The second endpoint should return all documents from the TeamMember collection since no parameters are passed. However, when the request is executed, only [] is returned. We know for a fact that documents exist in the TeamMember collection, since the first endpoint returns data from that collection based on the uid parameter that is passed.
I'm stumped on this. Any ideas? I don't think there is anything wrong with my model since I am able to execute the first endpoint with no issues.
Express executes code from top to button, and that is the reason for this issue. It will match your first endpoint and assume that uid is null. Just change the order of defined endpoints, like this:
module.exports = (app) => {
app.get('/api/pods/teamMembers', async (req, res) => {
let teamMembers = await TeamMembers.find();
return res.status(200).send(teamMembers);
});
app.get('/api/pods/teamMembers/:uid', async (req, res) => {
let teamMember = await TeamMembers.find( {'uid': req.params.uid } );
return res.status(200).send(teamMember);
});
}

run synchronouse function in a promise

I am new to JS and async operations. In a router of nodeJS using express, I have aggregated some data from mongo using mongoose. The data is weather data collected from different sites every 15 minutes interval. I processed the data with mongoose aggregate pipeline to get hourly data and group by each site. But the data needs a further process to get periods where for example relative humidity over 90% and assign scores to each period so I wrote some synchronous functions that target each site (each geojson object).
Mongoose looks something like that:
module.exports.filteredData = function (collection, dateInput) {
return collection.aggregate([
{
$addFields :{
DateObj: {
$dateFromString: {
dateString: "$DateTime",
format: '%Y-%m-%d'
}
},
}
},
{
$addFields :{
NewDateTimes: {
$dateFromParts:{
'year': {$year: '$DateObj'},
'month':{$month: '$DateObj'},
'day':{$dayOfMonth: '$DateObj'},
'hour': {$toInt: "$Time"}
}
}
}
}
...
synchronouse functions:
const calcDSV = function(featuresJSON){
// featuresJSON
const SVscore = [];
const tuEval = featuresJSON.features.properties.TU90; // array
const obArr = featuresJSON.features.properties.OB; // array
const periodObj = getPeriods(tuEval);// get period position
const paramObj = getParams(periodObj, obArr); // get parameters
const periodDate = getPeriodDate(featuresJSON, periodObj);
const removeTime = periodDate.beginDate.map(x=>x.split('T')[0]);
let hourly = paramObj.hourCounts;
let avgTemps = paramObj.avgTemps;
for(let i = 0;i<hourly.length; i++){
let score = assignScore(avgTemps[i], hourly[i]);
SVscore.push(score);
}
// output sv score for date
const aggreScore = accumScore(removeTime, SVscore);
aggreScore.DSVdate = aggreScore.Date.map(x=>new Date(x));
featuresJSON.features.properties.periodSV = SVscore;
featuresJSON.features.properties.Periods = periodDate;
featuresJSON.features.properties.DSVscore = aggreScore;
return featuresJSON;
}
Now I am stuck on how to apply those function on each site return by the mongoose aggregate pipeline on a post request:
router.post('/form1', (req, res, next)=>{
const emdate = new Date(req.body.emdate);
const address = req.body.address;
const stationDataCursor = stationData.filteredData(instantData, emdate);
stationDataCursor.toArray((err, result)=>{
if(err){
res.status(400).send("An error occurred in Data aggregation")
};
res.json(result.map(x=>calcDSV.calcDSV(x)));
})
});
I tried in the callback:
stationDataCursor.toArray((err, result)=>{
if(err){
res.status(400).send("An error occurred in Data aggregation")
};
res.json(result.map(async (x)=>await calcDSV.calcDSV(x))));
})
and using then():
stationDataCursor.toArray().then((docArr)=>{
let newfeature = await docArr.map(async (x)=> await calcDSV.calcDSV(x))));
res.json(newfeature);
})
or make calcDSV() returns new promise
return new Promise((rej, res)=>{
resolve(featuresJSON);
})
I would expect to see all sites with a new feature added in the HTTP response output. But most of the time, I got ReferenceError: error is not defined.
I think I have figured it out:
after all, have to make all synchronous functions asynchronous by prepending async to those functions;
rewrite this part in the post router function, especially the array map part. I read from this. and in the map() gonna have try...catch... in it, otherwise it won't work.
await stationDataCursor.toArray().then(async (docArr)=>{
const newfeature = await Promise.all(docArr.map(async function(x){
try{
const feature = await calcDSV.calcDSV(x);
return feature
} catch(err){
console.log("Error happened!!! ", err);
}
}));
res.json(newfeature)
})
Hope it helps.

Categories