Saving data to array in mongoose - node.js

Users are able to post items which other users can request. So, a user creates one item and many users can request it. So, I thought the best way would be to put an array of users into the product schema for who has requested it. And for now I just want to store that users ID and first name. Here is the schema:
const Schema = mongoose.Schema;
const productSchema = new Schema({
title: {
type: String,
required: true
},
category: {
type: String,
required: true
},
description: {
type: String,
required: true
},
userId: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
requests: [
{
userId: {type: Object},
firstName: {type: String}
}
],
});
module.exports = mongoose.model('Product', productSchema);
In my controller I am first finding the item and then calling save().
exports.postRequest = (req, res, next) => {
const productId = req.body.productId;
const userId = req.body.userId;
const firstName = req.body.firstName;
const data = {userId: userId, firstName: firstName};
Product.findById(productId).then(product => {
product.requests.push(data);
return product
.save()
.then(() => {
res.status(200).json({ message: "success" });
})
.catch(err => {
res.status(500).json({message: 'Something went wrong'});
});
});
};
Firstly, is it okay to do it like this? I found a few posts about this but they don't find and call save, they use findByIdAndUpdate() and $push. Is it 'wrong' to do it how I have done it? This is the second way I tried it and I get the same result in the database:
exports.postRequest = (req, res, next) => {
const productId = req.body.productId;
const userId = req.body.userId;
const firstName = req.body.firstName;
const data = {userId: userId, firstName: firstName};
Product.findByIdAndUpdate(productId, {
$push: {requests: data}
})
.then(() => {
console.log('succes');
})
.catch(err => {
console.log(err);
})
};
And secondly, if you look at the screen shot is the data in the correct format and structure? I don't know why there is _id in there as well instead of just the user ID and first name.

Normally, Developers will save only the reference of other collection(users) in the collection(product). In addition, you had saved username also. Thats fine.
Both of your methods work. But, second method has been added in MongoDB exactly for your specific need. So, no harm in using second method.

There is nothing wrong doing it the way you have done it. using save after querying gives you the chance to validate some things in the data as well for one.
and you can add additional fields as well (if included in the Schema). for an example if your current json return doesn't have a field called last_name then you can add that and save the doc as well so that's a benefit..
When using findById() you don't actually have the power to make a change other than what you program it to do
One thing I noticed.. In your Schema, after you compile it using mongoose.modal()
export the compiled model so that you can use it everywhere it's required using import. like this..
const Product = module.exports = mongoose.model('Product', productSchema);

Related

Removing an element from the array in MongoDB through mongoose

I've defined a MongoDB model using mongoose in my NodeJS application as:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const userSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
rToken: [String]
})
module.exports = mongoose.model("User", userSchema);
Every rToken is unique. I want to remove an element from the rToken array specified by the user in the request body, therefore, I've created a route below:
module.exports.logout = async (req, res) => {
const token = req.body.token;
User.updateOne({}, {$pull: {$in: [token]}});
res.sendStatus(204);
}
But it isn't removing the element and I'm still getting 204 status. Please help.
If rToken is an object you can remove element like that, assuming you remove a password key:
const { password, ...rTokenWithoutPassword } = rToken;
Then use rTokenWithoutPassword to do anything.
If you want to remove more than one key, salt and password for example:
const { password, salt, ...rTokenWithoutPassword } = rToken;
If you need to remove from within mongoDB, assuming rToken is an array of strings, you need to modify your Mongoose model because actually it is a String, not an array.
Then, according to mongodb documentation: https://www.mongodb.com/docs/manual/reference/operator/update/pull/
Modify your update request, assuming "abcd" is the value to pull off:
User.updateOne({}, { $pull: { rToken: { $eq: 'abcd' } } });
Here is a playground: https://mongoplayground.net/p/Fq-bH7vBUF7

Updating a Subdocument array INSIDE another Subdocument array Mongoose

I am at my wits end with something that is seemingly straightforward:
I need to be able to push new gifts into the Events Array under the specific user. Because each event will have numerous gifts added, I want to keep them all under the user, as they are the one creating the event, and the gifts will live inside of their event where they belong.
The PROBLEM is: when I use the mongoose method 'findByIdAndUpdate', I can only find the main user, and from there, push an event to the events array. What I NEED to be able to do: push gifts to a specific event under that user. I am using mongoose Subdocuments. See my schema below and how I have a subdocument schema (EventSchema) inside of the main user schema, and a subdocument (gift) schema inside the event schema.
SCHEMA:
const Schema = mongoose.Schema;
let giftArr = new Schema({
giftname: String,
giftlink: String,
claimed: Boolean,
claimee: String
})
let eventSchema = new Schema({
eventname: String,
eventowner: String,
date: {
type: Date,
default: Date.now
},
attendees: [
{
attendeename: String
}
],
gift: [giftArr]
})
let userSchema = new Schema({
username: String,
email: { type: String, required: false },
events: [eventSchema]
});
Here are my controllers for my POST & GET routes:
export const insertEventsById = ((req, res) => {
const update = { $push: { events: req.body } }
const id = req.params.userID
Gift.findByIdAndUpdate(id, update, (err, data) => {
if (err) {
console.log(err);
} else {
res.json(data)
console.log(data);
}
})
})
export const getUserById = (req, res) => {
Gift.findById(req.params.userID, (err, user) => {
if(err){
res.send(err)
}
res.json(user)
})
}
To further illustrate, here is my postman GET request for a USER. I can push to the 'events' array (red arrow) as my findByIdAndUpdate method shows above, but when I attempt to go one nested level deeper, into the gift array (green arrow), I cannot find any documentation on that.
I been up and down the mongoose subdocuments and queries pages, and I cannot find a method that will pull specifically the '_id' of the particular event I need. I have even tried the methods on the embedded schemas to specifically look for _id's that way.
Can someone point out where I am going wrong here? Thanks in advance...as always fellow Stacks.

MongoDB element on nested schemas and return only that element

i have this Schema for a simple twitter app
const userSchema = new Schema ({
loginInfo: {
username: String,
email: String,
password: String
},
tweets: [{
content: String,
likes: Number,
comments: [{
owner: String,
content: String,
likes: Number
}]
}],
followers: [String],
following: [String]
})
and i want to make endpoint that return only the tweet that has the same _id that has been given as a params on the URL ..
I made that solution below and its working correctly but i believe there is a much better solution than this ..
const handleTweet = (User) => (req,res) => {
const { id } = req.params;
let theTweet = [];
User.findOne({ "tweets._id": id})
.then(user => {
user.tweets.forEach(tweet => {
if(tweet._id.toString() === id)
return theTweet.push(tweet)
})
res.json(theTweet)
})
.catch(err => res.json(err))
}
module.exports = handleTweet;
One more question : Is it better to make nested schemas like this or making a different models for each schema (in this case schema for User and another one for Tweets) ?
You should make the tweets into a different collection since you are querying based on that, and then you can use autopopulate when you need it.
Also instead of the foreach you could use Array.prototype.find
Hope this helps!
You can use the $push & findOneAndUpdate methods from mongoose. You can modify your example to be like this:
User.findOneAndUpdate(id, { $push: { tweets: req.body.tweet } }, {new: true})
.then((record) => {
res.status(200).send(record);
})
.catch(() => {
throw new Error("An error occurred");
});
Notice the {new: true} option, it makes the findOneAndUpdate method to return the record with the edit.
For your second question, it's recommended to split the modals to make your code more readable, maintainable and easy to understand.

Problem with a Cast using Router.put with mongoDB in MERN app

I want to attach the router.put which will update the Boolean(isOn) in toggle button but firstly I wanted to try how it works and now I am facing the problem.
const express = require("express");
const router = express.Router();
const Buttons = require('../../models/Buttons');
// GET buttons
// This request works perfect
router.get('/', (req,res) => {
Buttons.find()
.sort({name: 1})
.then(buttons => res.json(buttons))
});
// PUT buttons
// This one doesnt work at all
router.put('/:name', function(req,res,next) {
Buttons.findByIdAndUpdate({name: req.params.name},
req.body).then(function(){
Buttons.findOne({name: req.params.name}).then(function(buttons){
res.send(buttons);
});
});
});
module.exports = router;
Model of buttons has only name: String, required: true and isOn: Boolean, required: true and data in db looks like that:
Can you tell me what did I do wrong here?
Code of Buttons modal :
const mongoose = require('mongoose');
const Schema = mongoose.Schema
const buttonSchema = new Schema ({
name: {
type: String,
required: true
},
isOn: {
type: Boolean,
required: true
}
});
module.exports = Buttons = mongoose.model("buttons", buttonSchema);
You ca only use findByIdAndUpdate when you want to update the document by matching the _id of the document
If you want to match the document by any other property (such as name in your case), you can use findOneAndUpdate
Write your query like this
router.put('/:name', function(req,res,next) {
Buttons.findOneAndUpdate({name: req.params.name},
req.body).then(function(){
Buttons.findOne({name: req.params.name}).then(function(buttons){
res.send(buttons);
});
});
});
Hope this helps
Please add your id as well which you have to update in your database
Model.findByIdAndUpdate(id, updateObj, {new: true}, function(err, model) {...
This error occur because findByIdAndUpdate need id of an Object which we want to update so it shows ObjectId error. so pass your id from front end and use it in your back-end to update particulate data.
step 1 : you can create new endpoint for update-name
router.put('/update-name', function(req,res,next) {
//here you can access req.body data comes from front-end
// id = req.body.id and name = req.body.name then use it in your
Buttons.findByIdAndUpdate(id, { name : name }, {new: true}, function(err, model) {...
}
step 2 : try this endpoint /update-name and pass your data in Body from postman

How to get another colletion data with mongoose populate

I have the following models in node js and i want to get data from file schema and from client schema in just one call, i was reading about populate but have no ideia how to use that.
This is my model
const mongoose = require('mongoose');
const fileSchema = mongoose.Schema({
_id: mongoose.SchemaTypes.ObjectId,
client_id: mongoose.SchemaTypes.ObjectId,
user_id: mongoose.SchemaTypes.ObjectId,
status: String,
name: String,
path: String,
clients: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Client' }]
});
const clientSchema = mongoose.Schema({
_id: mongoose.SchemaTypes.ObjectId,
name: String,
img: String
});
module.exports =
mongoose.model('File', fileSchema, 'files'),
Client = mongoose.model('Client', clientSchema, 'clientes');
This is how i am getting the file data now
exports.getFiles = (req, res, next) => {
File.find({ field: res.locals.field })
.select('_id client_id user_id status name path')
.exec()
.then(file => {
res.status(200).json({
response: file
});
})
.catch(err => {
console.log(err);
res.status('500').json({
error: err
});
});
};
this returns an json response, when i tried to use populate i got an empty array.
You're almost there but you have an issue with your find search. At least with the File model you posted, you don't have a field called 'field' so you won't get any results.
Let's pretend that you're trying to find a file based off of its name and the request is being sent to the url 'blah/files/:name' and it looks like you're using Express.js so this should work.
To use populate, you usually do something like:
File.find({ name: req.params.name })
.populate('clients')
.exec()
.then(files => {
res.status(200).json({
response: files
});
})
.catch(err => {
console.log(err);
res.status('500').json({
error: err
});
});
What you have in your 'select' bit it not necessary since you're starting the search based on the File model and you're just asking it to return all of the fields you have anyway on that model. You get those returned in the result 'for free'.
The populate is flagged out on the 'clients' field since you specified in the File model that it's an object id that references the Client model. Mongoose should handle it basically automagically. However, be careful, ALL of the fields on the Client model will be populated in the clients array of the File. If you want to return only one or a couple fields for your clients, it's there that you should use the select.
Also a note: the find method will return an array even if it's just a result of one document. If you are expecting or wanting just one result, use the findOne method instead.
Update
It looks like there's also a bugaboo in your module exports in the model file, which could be why you are having problems. My coding style is different from yours but here's how I would do it just to be sure that there are no mess ups :
const File = mongoose.model('File', fileSchema);
const Client = mongoose.model('Client', clientSchema);
module.exports = { File, Client };
Then in your router code, you import them as so:
const { File, Client } = require('<path-to-model-file>');

Resources