Mongoose: is it possible to combine many database calls into one? - mongodb

I've got 4 database operations in 1 API call, is it excessive? Can/should they be combined somehow?
// 1) Get the id for the user I want to follow
const account = await userModel
.findOne({ 'shared.username': username })
.exec();
if (!account) throw new Error('User not found');
// 2) Save the user id I want to follow and my Id into the following collection
const followed = new followingModel({
followingId: account.id,
userId: id,
} as FollowingModelInterface);
const saved = await followed.save();
// 3) Increment the number of people I'm following
const addFollowing = await userModel
.findOneAndUpdate(
{ _id: id },
{
$inc: { 'shared.following': 1 },
},
{
new: true,
}
)
.exec();
// 4) Increment the number of people who are following the user i just followed
const addFoller = await userModel
.findOneAndUpdate(
{ _id: account.id },
{
$inc: { 'shared.followers': 1 },
},
{
new: true,
}
)
.exec();
It feels like allot of trips to the database but maybe it's ok I'm not sure?
Maybe 3 and 4 dont need await and I can send a response to the front end after 2??
Maybe I'm overthinking it??

First and fourth operations look like that can be combined like this:
const account = await userModel.findOneAndUpdate(
{ "shared.username": username },
{
$inc: { "shared.followers": 1 }
},
{ new: true }
);
Or you can combine 3 userModel related update by using mongoose Model.bulkWrite

Related

How find in mongoose by an array property

I have defined my Conversation scheme like this:
const { Schema, model } = require("mongoose");
const ConversationSchema = Schema(
{
members: {
type: Array,
},
},
{ timestamps: true }
);
module.exports = model("Conversation", ConversationSchema);
My problem is that when I want to create a conversation model I search first if there is already a conversation.
const newConversation = async (req, res = response) => {
try {
const { senderId, receiverId } = req.body;
const conversation = await Conversation.find({
members: { $in: [senderId, receiverId] },
});
if (conversation.length === 0) {
const dbConversation = new Conversation({
members: [senderId, receiverId],
});
await dbConversation.save();
return res.status(201).json({
ok: true,
conversation: dbConversation
});
} else {
return res.status(403).json({
ok: false,
msg: "Conversation already exist",
});
}
} catch (err) {
return res.status(500).json({
ok: false,
msg: "Please contact with administrator",
});
}
};
senderId and receivedId are the ids of the users that are in that conversation, but it doesn't work.
How can I make it check if there is already a conversation with both ids?
Per the comments, we came to understand that the thing that wasn't working about the current code was always taking the code path that returned the message that the "Conversation already exist". This meant that the following query was always returning data:
const conversation = await Conversation.find({
members: { $in: [senderId, receiverId] },
});
The logic here does not match the logic implied in the question. This syntax uses the $in operator to find documents whose members array has at least one of the values passed to it (here the senderId and the receiverId).
To instead find documents where both of those people are present in the members array, you want to use the $all operator instead:
const conversation = await Conversation.find({
members: { $all: [senderId, receiverId] },
});
Working Mongo Playground example here.

Reading an array into another array and storing all objects into a list using mongoose

I have a model (user) which contains an array (discontinued items). IDs are stored within this array, all of which belong to a specific item model. Now I would like to list all users where this array is not empty and then, in the same step, read out all articles from this array using their ID. Unfortunately, I can't do this because I get "undefinded" in the console when I print. What is that? Thank you very much
export const AlleUserMitArtikel = async (req, res) => {
try {
const alleUser = await User.find({
eingestellteArtikel: { $exists: true, $not: { $size: 0 } },
});
const liste = await Promise.all(
alleUser.map(async (user) => {
console.log(user); //Displays the correct object in the console, see below
user.eingestellteArtikel.map(async (id) => {
console.log(id); //Displays the correct ID, see Error section first two entries
return await Artikel.find({ _id: id });
});
})
);
console.log(liste); //Displays undefined
res.status(200).json(alleUser);
} catch (error) {
console.log(error);
}
};
USER MODEL:
{
_id: new ObjectId("630f36f0295ec768e2072c10"),
eingestellteArtikel: [ '630fe7caabfdf4387030a723', '63105cbedae68f22984ba434' ],
createdAt: 2022-08-31T10:24:48.845Z,
updatedAt: 2022-09-01T07:18:22.044Z,
__v: 0,
}
Undefined message:
630fe7caabfdf4387030a723
63105cbedae68f22984ba434
[ undefined, undefined ]

How to create dynamic query in mongoose for update. i want to update multiple data(Not all) with the help of Id

If I'm doing this, the field which I don't want to update is showing undefined. Any solution? (Like generating dynamic query or something)
exports.updateStudentById = async (req, res) => {
try {
const updateAllField = {
first_name: req.body.first_name,
last_name: req.body.last_name,
field_of_study: req.body.field_of_study,
age: req.body.age,
};
const data = await student_master.updateOne(
{ _id: req.body._id },
{ $set: updateAllField }
);
res.json({ message: "Student Data Updated", data: data });
} catch (error) {
throw new Error(error);
}
};
You can go for a dynamic query creation .Example
const requestBody = {
first_name: "John",
last_name: "Cena",
field_of_study: ""
}
const query={};
if(requestBody.first_name){
query["first_name"]=requestBody.first_name
}
if(requestBody.last_name){
query["last_name"]=requestBody.last_name
}
Check for the fields that are present in req.body and create a dynamic query
and when updating using mongoose use this
const data = await student_master.updateOne(
{ _id: req.body._id },
{ $set: query }
);
In this way only those fields would be updated which are present in your req.body

Mongodb: how to update a field only if the value I want to upload is not null?

I have a field named "profilePicture" that can have a string or a null value. When editing his profile, a user may or may not upload a new profile image. This mean that I can either receive a string or a null value.
How to update profilePicture only if the new value is not null (so I can keep the existing string )?
Here is the code:
const newProfilePicture = null // can be null or "string"
const user = await User.findByIdAndUpdate(
userId,
[{$set: { profilePicture: newProfilePicture }}]
{ new: true }
);
Thanks!
Try this
const updateObj = {};
// other properties added to object
// e.g.
// updateObj.name = "DoneDeal0";
if (newProfilePicture !== null) {
updateObj.profilePicture = "https://profilepicture.etc/picture.jpg";
}
const user = await User.findByIdAndUpdate(
{ userId:"someID" },
{ $set: updateObj },
{ new: true }
);
Use conditional logic to build an object prior to your mongo query.
I would do this using application logic. You can check beforehand if newProfilePicture is null, and if not perform the update. Something like this:
const newProfilePicture = null;
if (newProfilePicture){
const user = await User.findByIdAndUpdate(
userId,
[{$set: { profilePicture: newProfilePicture }}]
{ new: true }
);
}
If you need to return user regardless of whether an update actually occurs, you can just add an else branch and do a normal findById:
const newProfilePicture = null;
if (newProfilePicture){
const user = await User.findByIdAndUpdate(
userId,
[{$set: { profilePicture: newProfilePicture }}]
{ new: true }
);
} else {
const user = await User.findById(userId);
}

Mongoose: consistently save document order

Suppose we have a schema like this:
const PageSchema = new mongoose.Schema({
content: String
order: Number
})
We want order to be always a unique number between 0 and n-1, where n is the total number of documents.
How can we ensure this when documents are inserted or deleted?
For inserts I currently use this hook:
PageSchema.pre('save', async function () {
if (!this.order) {
const lastPage = await this.constructor.findOne().sort({ order: -1 })
this.order = lastPage ? lastPage.order + 1 : 0
}
})
This seems to work when new documents are inserted.
When documents are removed, I would have to decrease the order of documents of higher order. However, I am not sure which hooks are called when documents are removed.
Efficiency is not an issue for me: there are not many inserts and deletes.
It would be totally ok if I could somehow just provide one function, say fix_order, that iterates over the whole collection. How can I install this function such that it gets called whenever documents are inserted or deleted?
You can use findOneAndDelete pre and post hooks to accomplish this.
As you see in the pre findOneAndDelete hook, we save a reference to the deleted document, and pass it to the postfindOneAndDelete, so that we can access the model using constructor, and use the updateMany method to be able to adjust orders.
PageSchema.pre("findOneAndDelete", async function(next) {
this.page = await this.findOne();
next();
});
PageSchema.post("findOneAndDelete", async function(doc, next) {
console.log(doc);
const result = await this.page.constructor.updateMany(
{ order: { $gt: doc.order } },
{
$inc: {
order: -1
}
}
);
console.log(result);
next();
});
Let's say you have these 3 documents:
[
{
"_id": ObjectId("5e830a6d0dec1443e82ad281"),
"content": "content1",
"order": 0,
"__v": 0
},
{
"_id": ObjectId("5e830a6d0dec1443e82ad282"),
"content": "content2",
"order": 1,
"__v": 0
},
{
"_id": ObjectId("5e830a6d0dec1443e82ad283"),
"content": "content3",
"order": 2,
"__v": 0
}
]
When you delete the content2 with "_id": ObjectId("5e830a6d0dec1443e82ad282") with findOneAndDelete method like this:
router.delete("/pages/:id", async (req, res) => {
const result = await Page.findOneAndDelete({ _id: req.params.id });
res.send(result);
});
The middlewares will run, and adjust the orders, the remaining 2 documents will look like this:
[
{
"_id": ObjectId("5e830a6d0dec1443e82ad281"),
"content": "content1",
"order": 0,
"__v": 0
},
{
"_id": ObjectId("5e830a6d0dec1443e82ad283"),
"content": "content3",
"order": 1, => DECREASED FROM 2 to 1
"__v": 0
}
]
Also you had better to include next in your pre save middleware so that other middlewares also work if you add later.
PageSchema.pre("save", async function(next) {
if (!this.order) {
const lastPage = await this.constructor.findOne().sort({ order: -1 });
this.order = lastPage ? lastPage.order + 1 : 0;
}
next();
});
Based on the answer of SuleymanSah, I wrote a mongoose plugin that does the job. This way, it can be applied to multiple schemas without unnessecary code duplication.
It has two optional arguments:
path: pathname where the ordinal number is to be stored (defaults to order)
scope: pathname or array of pathnames relative to which numbers should be given (defaults to [])
Example. Chapters should not be numbered globally, but relative to the book to which they belong:
ChapterSchema.plugin(orderPlugin, { path: 'chapterNumber', scope: 'book' })
File orderPlugin.js:
function getConditions(doc, scope) {
return Object.fromEntries([].concat(scope).map((path) => [path, doc[path]]))
}
export default (schema, options) => {
const path = (options && options.path) || 'order'
const scope = (options && options.scope) || {}
schema.add({
[path]: Number,
})
schema.pre('save', async function () {
if (!this[path]) {
const last = await this.constructor
.findOne(getConditions(this, scope))
.sort({ [path]: -1 })
this[path] = last ? last[path] + 1 : 0
}
})
schema.post('findOneAndDelete', async function (doc) {
await this.model.updateMany(
{ [path]: { $gt: doc[path] }, ...getConditions(doc, scope) },
{ $inc: { [path]: -1 } }
)
})
}