finding multiple documents with mongoose - node.js

this is what happens when I console.log()
this is what happens when I return all documents with that id, I just get the first document
const followingUsers = await User.find({ _id: { $in: foundUser.followings } })
const getFeedData = async() => {
for (let user of followingUsers) {
for (let postId of user.posts) {
console.log(postId)
}
}
}
I'm running this code when I console.log(postId) it returns all the posts with that id, but when I try to retrieve all documents with that id it returns just one document

findById will only return one record or null, an ID is the _id field on each document in a collection, which is a unique value
find is the equivalent of a where command in SQL, it returns as many documents that match the query, or an empty array
passing $in as a query to find looks for an array of matching document for the user id's
so if you already know the document _id's, then find will return the ID's so long you have passed an array of valid ObjectId
// (pretending these are real id's)
const arrayOfUserIds = [
ObjectId("5af619de653438ba9c91b291"),
ObjectId("5af619de653438ba9c91b293"),
ObjectId("5af619de653438ba9c91b297")
]
const users = await User.find({ _id: { $in: arrayOfUserIds } })
console.log(users.length)
users.forEach((user, index) => {
console.log(`${index} - `, user._id)
})
// => 3
// => 0 - ObjectId("5af619de653438ba9c91b291")
// => 1 - ObjectId("5af619de653438ba9c91b293")
// => 2 - ObjectId("5af619de653438ba9c91b297")

Related

Mongoose: After finding document, iterate over a value in the document and run a new query on each

I have one schema which contains an array of references to another schema (among other fields):
const RecipeIngredient = new Schema({
ingredientId: { // store id ref so I can populate later
type: Schema.Types.ObjectId,
ref: 'ingredients',
required: true
},
// there are a couple other fields but not relevant here
});
const Recipe = new Schema({
ingredients: [RecipeIngredient]
});
I'm trying to write a route which will first find a recipe by _id, populate the ingredients array (already have this working), and finally iterate over each ingredient in that array.
router.get('/:recipeId/testing', async (req, res) => {
const { recipeId } = req.params
let recipe = await Recipe
.findById(recipeId)
.populate({
path: 'ingredients.ingredientId',
model: 'Ingredient',
select: '_id ......' //I'm selecting other fields too
})
.lean()
.exec();
if (recipe) {
const { ingredients } = recipe;
const newIngredients = [];
await ingredients.forEach(async (ingr) => {
// here I'd like to be able to run a new query
// and append the result to an array outside of the forEach
// I do need information about the ingr in order to run the new query
newIngredients.push(resultOfNewQuery);
});
return res.json(newIngredients)
};
return res.status(404).json({ noRecipeFound: 'No recipe found.'});
})
I've tried approaching this in a few different ways, and the closest I've gotten was executing the new query within each iteration, but because the query is async, I return the response before I've actually collected the documents from the inner query.
I also attempted to use .cursor() in the initial query, but that won't work for me because I do need to access the ingredients field on the recipe once it is resolved before I can iterate and run the new queries.
Any ideas would be appreciated! I'm definitely opening to restructuring this whole route if my approach is not ideal.
I was able to make this work by using a for loop:
const newIngredients = [];
for (let idx = 0; idx < ingredients.length; idx++) {
const { fieldsImInterestedIn } = ingredients[idx];
const matchingIngredients = await Ingredient
.find(fieldsImInterestedIn)
.lean()
.exec()
.catch(err => res.status(404).json({ noIngredientsFound: 'No ingredients found' }));
newIngredients.push(ingredientsToChooseFrom[randomIndex]);
};
return res.json(newIngredients);
still a little perplexed as to why this was able to work while forEach wasn't, but I'll happily move on...

MongoDB updateOne returning null for upsertedId

I'm trying to get the upsertedId for the updated doucment, but it's returning null.
Here's the function for updating a doucment and trying to return result.upsertedId:
async function run() {
try {
await client.connect()
const collection = client.db("database").collection("collection")
const filter = { username: `${username}`}
const options = { upsert: true };
const updateDoc = {
$set: { date: new Date(), topartists: data.body.items, toptracks: toptracks }
};
await collection.updateOne(filter, updateDoc, options)
.then(result => {
console.log(`${result.matchedCount} document(s) matched the filter, updated ${result.modifiedCount} document(s)`)
console.log(result.upsertedId)
})
console.log('done1')
} finally {
await client.close();
}
}
and here's the console output of the function:
1 document(s) matched the filter, updated 1 document(s)
null
done1
I'm just starting with MongoDB, and the end goal here is to get
_id. It works there is nothing in my database (when it has to make a new document instead of updating one).
That will not be the case.
Your log clearly says - your query matched 1 document and it updated one document.
If your update results in insert, then response of your update operation will have upsert ids. - Means, matched count is 0.

MongoDB How to check if all ObjectIds inside the array exist in the database?

I have an array containing multiple mongodb ObjectId, how can I know if all that Ids exist in the specific collection,
Although I can achieve my expected result by for loop like this, it will take too long to load because it will make multiple query for how many index my array has.
const queryData = async objectIds => {
// objectIds contains an array of ObjectId
for (let i = 0; i < objectIds.length; i++) {
const objId = objectIds[i]
const data = await DataCollection.findOne({ _id: objId })
if (!data) throw new Error('Data not found!')
}
}
how can I achieve the same result as my sample function without querying multiple times that makes the process so much slower?
You can use .count() and $in for filtering condition:
let count = await DataCollection.count( { _id: { $in: objectIds } } );
let all = count === objectIds.length;

How to implement pagination for mongodb in node.js using official mongodb client?

I want to implement pagination for mongodb in node.js enviroment using offical mongodb package. I tried to find out on internet but all are mongoose based links. I dont want to use mongoose.
How can I implement pagination using official client api given at
http://mongodb.github.io/node-mongodb-native/3.1/api/
Offset-based approach has a big flaw: if the results list has changed between calls to the API, the indices would shift and cause an item to be either returned twice or skipped and never returned
This problem is demonstrated at
https://www.sitepoint.com/paginating-real-time-data-cursor-based-pagination/
Time-based pagination approach would be little better because results are no longer skipped. If you query the first page, and then a new item is deleted, it won’t shift the results in your second page and all is fine. However, this approach has a major flaw: what if there is more than one item that was created at the same time?
Best would be to use Cursor based pagination
Which can be implemented using any field in collection which is Unique, Orderable and Immutable.
_id satisfy all Unique, Orderable and Immutable conditions. Based on this field we can sort and return page result with _id of last document as the cusror for subsequent request.
curl https://api.mixmax.com/items?limit=2
const items = db.items.find({}).sort({
_id: -1
}).limit(2);
const next = items[items.length - 1]._id
res.json({ items, next })
when the user wants to get the second page, they pass the cursor (as next) on the URL:
curl https://api.mixmax.com/items?limit=2&next=590e9abd4abbf1165862d342
const items = db.items.find({
_id: { $lt: req.query.next }
}).sort({
_id: -1
}).limit(2);
const next = items[items.length - 1]._id
res.json({ items, next })
If we want to return results in a different order, such as the date the item then we will add sort=launchDate to the querystring.
curl https://api.mixmax.com/items?limit=2&sort=launchDate
const items = db.items.find({}).sort({
launchDate: -1
}).limit(2);
const next = items[items.length - 1].launchDate;
res.json({ items, next })
For subsequent page request
curl https://api.mixmax.com/items?limit=2&sort=launchDate&next=2017-09-11T00%3A44%3A54.036Z
const items = db.items.find({
launchDate: { $lt: req.query.next }
}).sort({
_id: -1
}).limit(2);
const next = items[items.length - 1].launchDate;
res.json({ items, next });
If we launched a bunch of items on the same day and time? Now our launchDate field is no longer unique and doesn’t satisfy Unique, Orderable and Immutable. condition. We can’t use it as a cursor field. But we could use two fields to generate the cursor.Since we know that the _id field in MongoDB always satisfies the above three condition, we know that if we use it alongside our launchDate field, the combination of the two fields would satisfy the requirements and could be together used as a cursor field.
curl https://api.mixmax.com/items?limit=2&sort=launchDate
const items = db.items.find({}).sort({
launchDate: -1,
_id: -1 // secondary sort in case there are duplicate launchDate values
}).limit(2);
const lastItem = items[items.length - 1];
// The cursor is a concatenation of the two cursor fields, since both are needed to satisfy the requirements of being a cursor field
const next = `${lastItem.launchDate}_${lastItem._id}`;
res.json({ items, next });
For subsequent page request
curl https://api.mixmax.com/items?limit=2&sort=launchDate&next=2017-09-11T00%3A44%3A54.036Z_590e9abd4abbf1165862d342
const [nextLaunchDate, nextId] = req.query.next.split(‘_’);
const items = db.items.find({
$or: [{
launchDate: { $lt: nextLaunchDate }
}, {
// If the launchDate is an exact match, we need a tiebreaker, so we use the _id field from the cursor.
launchDate: nextLaunchDate,
_id: { $lt: nextId }
}]
}).sort({
_id: -1
}).limit(2);
const lastItem = items[items.length - 1];
// The cursor is a concatenation of the two cursor fields, since both are needed to satisfy the requirements of being a cursor field
const next = `${lastItem.launchDate}_${lastItem._id}`;
res.json({ items, next });
Refefence: https://engineering.mixmax.com/blog/api-paging-built-the-right-way/
Using the recommended pagination approach with limit() and skip() (see here):
const MongoClient = require('mongodb').MongoClient;
MongoClient.connect('http:localhost:27017').then((client) => {
const db = client.db(mongo.db);
db.collection('my-collection').find({}, {limit:10, skip:0}).then((documents) => {
//First 10 documents
console.log(documents);
});
db.collection('my-collection').find({}, {limit:10, skip:10}).then((documents) => {
//Documents 11 to 20
console.log(documents);
});
});
Here's a pagination function:
function studentsPerPage (pageNumber, nPerPage) {
return db.collection('students').find({},
{
limit: nPerPage,
skip: pageNumber > 0 ? ( ( pageNumber - 1 ) * nPerPage ) : 0
});
}
I am sending an API that is on MongoDb and Nodejs.
module.exports.fetchLoans = function(req, res, next) {
var perPage = 5;
var page = req.body.page || 1;
loans
.find({ userId: req.user._id})
.select("-emi")
.skip(perPage * page - perPage)
.limit(perPage)
.sort({ timestamp: -1 })
.exec(function(err, loan) {
if (loan != null) {
loans
.find({ userId: req.user._id})
.count()
.exec(function(err, count) {
if (count != null) {
res.json({
success: true,
loans: loan,
currentpage: page,
totalpages: Math.ceil(count / perPage)
});
} else {
console.log("Milestone Error: ", err);
res.json({ success: false, error: "Internal Server Error. Please try again." });
}
});
} else {
console.log("Milestone Error: ", err);
res.json({ success: false, error: "Internal Server Error. Please try again." });
}
});
};
In this code, you will have to provide page number on every hit.
You can use skip and limit options to implement pagination
module.exports = (data)=>{
let page = parseInt(data.page);
let limit = parseInt(data.limit);
let skip = 0
if(page>1){
skip = (page * limit);
}
let mongoClient = require('mongodb').MongoClient;
mongoClient.connect('mongodb://localhost:27017').then((client) => {
let db = client.db('your-db');
db.collection('your-collection').find({}, {limit:limit, skip:skip}).then((documents) => {
console.log(documents);
});
});
};

How to use json object with where clause?

What I'm trying to achieve
Find all players which is in the authenticated users team.
What is the Problem?
Unable to use the returned json within const findUsers = await User.findAll where clause and I am unsure if this is the correct way.
Database Tables
Users Table : id (PK) , etc
Teams: id (PK) , etc
TeamUsers: id , TeamID (Foreign Key) , UserID (Foreign Key) , etc
Returning Json from FindTeamUsers (Var ob) which is correct
[{"id":2,"TeamID":1,"UserID":1,"createdAt":"2019-08-09","updatedAt":"2019-08-09"},{"id":3,"TeamID":1,"UserID":3,"createdAt":"2019-08-09","updatedAt":"2019-08-09"},{"id":76,"TeamID":1,"UserID":5,"createdAt":"2019-08-22","updatedAt":"2019-08-22"}]
Below is the Route that I am currently using using Nodejs, ExpressJS
router.get('/Team', auth, async function(req, res) {
// -- Get the Users team that is currently Authenticated (req.user.id (auth) )
const findTeam = await TeamUsers.findOne({
where: {
UserID: req.user.id
}
});
//If the User has a team
if (findTeam) {
// -- Get the players Team Mates who have the matching TeamID
const findTeamUsers = await TeamUsers.findAll({
where: {
TeamID: findTeam.TeamID
}
});
//Store the object and Display in JSON FORMAT
var ob = JSON.stringify(findTeamUsers);
console.log(ob);
if (!findTeamUsers) {
console.log('error');
} else {
//find the Users Details From the Users Table Model
//findTeamUsers - Is an array of each record found from const findTeamUsers = await TeamUsers.findAll
const findUsers = await User.findAll({
where: {
id: ob.UserID
}
});
res.status(200).json(findUsers);
}
}
});
Your ob is a string so ob.UserID is undefined. findTeamUsers (FindTeamUsers result) is an array of object so findTeamUsers.UserID would be undefined too. (array findTeamUsers does not have property UserID).
You can pass an array of UserIDs to search multiple elements (if you want to find for all UserIDs in the array):
User.findAll({
where: {
id: findTeamUsers.map(o => o.UserID)
}
})

Resources