I am creating an app where I want to toggle the default address of a person.
My User Schema contains a field which is an array of delivery addresses which are objects.
One of the fields is isDefault, which is the defaultAddress field I want to toggle from true to false and vice versa.
Specifically, if the user changes their default address, I want to set the rest of the addresses to false and update the one he/she chose to be true
User Schema
const UserSchema = Schema({
email: {
type: String,
required: true,
},
deliveryAddresses: [deliverySchema]
Delivery Address Schema
{
id: {
type: Schema.Types.ObjectId,
required: true
},
isDefault: {
type: Boolean,
required: true,
default: false,
},
name: {
type: String,
},
email: {
type: String,
},
phone: {
type: String,
},
}
To do that, what I have done so far is:
Getting the email of the user.
Getting the id of the delivery address from the user that will be toggled to true.
exports.setDefault = (req, res, next) => {
const email = req.body.email;
const id = req.body.id;
User.findOne({ email: email })
.then((user) => {
let addresses = [...user.deliveryAddresses];
addresses.forEach((address) => {
address.isDefault = false;
});
const index = addresses.findIndex((address)=> {
return address.id.toString() === id.toString();
});
addresses[index].isDefault = true;
user.deliveryAddresses = addresses;
return user.save();
})
.then((doc) => {
res.status(200).json({
user: doc,
statusCode: "200",
msg: "Address updated successfully",
});
})
.catch((err) => {
res.status(500).json({
statusCode: 500,
error: err,
msg: "Something went wrong",
});
});
};
However, after doing all this, on testing my api on postman, it seems to work, no errors.
But on checking the database, nothing has changed.
I am at loss as to what I'm doing wrong.
Mongoose is weird. You need to mark the deliveryAddresses sub-object as modified, otherwise its changes won't be saved.
user.markModified('deliveryAddresses');
user.save();
From Mongoose FAQs
"Mongoose doesn't create getters/setters for array indexes; without
them mongoose never gets notified of the change and so doesn't know to
persist the new value. There are two workarounds: MongooseArray#set or
Document#markModified()."
Can you try sth like
user.markModified('deliveryAddresses');
user.save();
More on https://mongoosejs.com/docs/faq.html
Related
I'm using NodeJS with Mongoose. I've two tables into db.js:
const mongoose = require('mongoose')
const UserSchema = new mongoose.Schema(
{
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true }
},
{ collection: 'users' }
)
const model = mongoose.model('UserSchema', UserSchema)
const AccountSchema = new mongoose.Schema(
{
username: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'hahaha' },
balance: { type: Number, required: true }
},
{ collection: 'accounts' }
)
module.exports.UserSchema = model
module.exports.AccountSchema = model
As you can see the first collection contains users (username, email, password). The second collection represents a bank account, related to a specific user. So, it has two fields: user (foreign key) and balance ($100, i.e.). First question: is my code correct to accomplish this task?
Second question: how can I insert data into the AccountSchema?
I obviously want to insert data only if the user exists into Userschema. I think that this doesn't work:
const Schema = require('./model/db')
app.post('/api/addaccount', async (req, res) => {
const { username, balance } = req.body
try {
const response = await Schema.AccountSchema.create({
username,
balance
})
console.log('User account successfully: ', response)
res.json({status : "ok"})
} catch (error) {
throw error
}
})
How can I do this?
This won't work. You've to query the User model first to check if any user exists with this username. If yes, you'll continue to store data in the Account model, if not you'll return a response something like user doesn't exist
I am trying to update a dictionary field in MongoDB. But I can't figure out a way to update nested dictionary with a variable as key.
Here is the schema
const UserSchema = new mongoose.Schema({
email: String,
password: {type: String, unique: true, required: true},
id: mongoose.Schema.Types.ObjectId,
course: mongoose.Schema.Types.Mixed
});
I am trying to update course so that every time a course name is added as a key in the following form, with course name stored in the variable addName
For example, if addName = Intro to Computer Science, the desired result would be
{
"email" 12345#gmail.com,
...
"course": {
"Intro to Computer Science" {
"syllabus": ""
}
}
}
However, I am not sure how to add a variable key to the dictionary. When I tried below, but I get the following syntax error
app.post("/api/add-course/", (req, res) => {
const email = req.body.email;
const addName = req.body.addName;
console.log(req.body)// assumes that User was registered in `./db.mjs`
User.updateOne({"email": email}, {'$set': {'course.'+addName: {"syllabus": ""}}},
).then(result => {
res.status(201).json({
message: "success",
course: user.course
})
}).catch(err => {
console.log(err),
res.status(500).json({
error: err
});
});
console.log(user);
})
I'm learning node.js and it's amazing, especially with mongo, but sometimes I struggle to solve a simple problem, like patching only 1 attribute in my user database.
It's easier to patch something that cannot be unique, but I want to patch an username attribute and I defined it as "unique" in my schema. I don't know why, but MongoDB doesn't care other db entry has the same user, it let me save.
My schema:
/** #format */
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema(
{
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
email: { type: String, required: true, unique: true },
userNumber: { type: Number, required: true },
description: { type: String },
verified: { type: Boolean, default: false },
isAdmin: { type: Boolean, default: false },
isSubscriber: { type: Boolean, default: false },
isDisabled: { type: Boolean, default: false },
acceptedTerms: { type: Number, required: true },
},
{ timestamps: true }
);
module.exports = mongoose.model('User', userSchema);
On my user controllers in node, I want to updateOne({ _id: userId}, { username: myNewUsername} but it always happens, it doesn't take into consideration another db entry can have the username, so I tried a different strategy but it doesn't work:
exports.changeUsername = (req, res, next) => {
// Requirements
const userId = req.params.userId;
const newUsername = req.body.username;
console.log('userId: ' + userId);
console.log('newUsername: ' + req.body.username);
User.findOne({ username: req.body.username })
.then(result => {
console.log(result);
if (result.username) {
const error = new Error('Could not find this sport');
error.code = 'DUPLICATED';
throw error;
}
return;
})
.catch(err => next(err));
// if no username was in use then updateOne
User.updateOne({ _id: userId }, { username: newUsername })
.then(result => {
res.status(200).json({
message: 'username has been updated',
username: result.username,
});
})
.catch(err => next(err));
};
I don't know if I can updateOne at the same time add some find validation. What I am doing wrong? Users cannot have the same username.
On the console, it seems it works, but it throws an extra error I don't understand:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at new NodeError (node:internal/errors:371:5)
at ServerResponse.setHeader (node:_http_outgoing:576:11)
at ServerResponse.header (/Users/username/Sites/pipi-api/node_modules/express/lib/response.js:776:10)
I tried this other approach and it works, but doesn't trigger an error if the record is not unique as I stated in the schema.
// GET ONLY ONE SPORT BY ID
exports.changeUsername = async (req, res, next) => {
// Requirements
const userId = req.params.userId;
const newUsername = req.body.username;
console.log('userId: ' + userId);
console.log('newUsername: ' + req.body.username);
try {
const oldUsername = await User.findOne({ username: newUsername });
if (oldUsername.username) {
throw new Error('Error: its duplicated');
}
const user = await User.findOneAndUpdate(
{ _id: userId },
{ username: newUsername },
{ new: true }
);
console.log('User successfully updated.');
return res.status(200).json({ success: true, user });
} catch (err) {
console.log('ERROR: ', err);
return res.status(400).json({ success: false });
}
};
If I uncomment the code above, it triggers an error if I find a record on the database that matches but it doesn't allow me to continue to my next line of codes I the username is not found on the db.
I get a new error:
userId: 6231bdef334afbde85ed9f43
newUsername: tetete
ERROR: TypeError: Cannot read properties of null (reading 'username')
at exports.changeUsername (/Users/user/Sites/pipi-api/v1/controllers/users/index.js:43:21)
That error is not related to Mongo. It means that you are trying to send a response and the response is already sent.
The issue is because you called both User.findOne and User.updateOne and both of them has .then handler. So the first one of these that finishes will send the actual response. In the moment the second one finished, the response is already send and the error is thrown because you are trying to send response again.
Mongo will throw the error if you try to change username property that some other user already have. You should check if the req.params.userId and req.body.username sent correctly to the backend. Try to console.log() them and check if they are maybe null.
Consider refactoring your handler to use async/await instead of then/catch. You can do it like this:
exports.changeUsername = async (req, res, next) => {
try {
const userId = req.params.userId;
const newUsername = req.body.username;
const user = await User.findOneAndUpdate({ _id: userId }, { username: newUsername }, { new: true });
console.log('User successfully updated.');
return res.status(200).json({ success: true, user });
} catch (error) {
console.log('ERROR: ', error);
return res.status(400).json({ success: false });
}
}
I have a chat application that I'm trying to add seen functionality on it.
I'm trying to execute a query that could update all chat messages (seen) columns.
here's the schema:
const messageSchema = new mongoose.Schema({
senderId: {
type: String,
required: true
},
message: {
type: String,
required: true
},
seen: { // I'm trying to update this in all the documents !!
type: Boolean,
default: false
}
}, {timestamps: true});
const chatSchema = new mongoose.Schema({
messages: [messageSchema]
});
As you can see here, I have a seen property in the message schema (which is nested inside the chat schema)
so I just want to get a single chat, update all messages inside it and update seen column to be true
I tried that:
const messagesSeen = async (req, res) => {
const chatId = req.params.id;
await Chat.findByIdAndUpdate(
chatId,
{
$set: { 'messages.seen': true },
},
{ new: true }
)
.then((chat) => {
return res
.status(200)
.json({ chat });
})
.catch((error) => {
return res.status(500).json({ message: error.message });
});
};
but unfortunately, it didn't work
so, hope to find a solution.
thank you
You should use positional operator - $[].
await Chat.findByIdAndUpdate(
chatId,
{
$set: { "messages.$[].seen": true },
},
{
new: true
})
Working example
so I have two different collections for my social media app. One for the users and the other one for the user's posts. Whenever I'm updating the info from one of my user's collection it should also modify it on the post (since my post collection includes data from the user too), but it's only doing it on the posts that I create after that and not on the ones that I've been creating before. How can I fix it?
USER SCHEMA
const userSchema = new Schema({
name: { type: String, required: true },
lastname: { type: String, required: true },
username: { type: String, required: true },
email: { type: String, required: true },
password: { type: String, required: true, minlength: 8 },
avatar: { data: Buffer, contentType: String },
});
POST SCHEMA
const postSchema = new Schema({
user: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
name: { type: String, required: true },
lastname: { type: String },
username: { type: String },
avatar: { data: Buffer, contentType: String },
date: { type: Date, default: Date.now() },
textOfThePost: { type: String, required: true },
});
EDIT FUNCTION EXPRESS/MONGOOSE
router.put("/edit_profile", async (req, res) => {
try {
const { name, lastname, username } = req.body;
const user = await User.findById(req.user).select("-password");
if (!user) return res.status(404).json("User doesn't exists");
if (name) user.name = name;
if (lastname) user.lastname = lastname;
if (username) user.username = username;
await user.save();
res.json(user);
} catch (err) {
res.status(500).json({ error: err.message });
}
};
You can use updateMany() for that purpose.
const res = await Post.updateMany({ user: user.id }, { name: user.name, username: user.username /* ... */ });
However as already pointed out you are storing user data redundant on the post model as well as on the user model which is not necessary. Similar to joins in SQL you can simply use populate() and not store any user-related data on your post model. This way everytime you query your posts it will automatically pull the latest matching user model by its id.
myPost.populate('user')
Note that therefore the ref is required, which tells mongoose how to populate the user field.