Mongoose populate array is empty - javascript

I have defined a UserSchema and a PostSchema in my model.js. UserSchema has a ref to its own Posts, while PostSchema has a ref to its author.
In my controllers.js methods I'm creating a user and a post instance (bound to the newly created user). I'm now trying to populate the user's posts, but mongoose returns an empty array. However, the other way around works (I'm able to retrieve the user by a Post instance).
models.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UserSchema = new Schema({
username: String,
posts: [{
type: Schema.Types.ObjectId,
ref: 'Post'
}]
})
const PostSchema = new Schema({
content: String,
author: {
type: Schema.Types.ObjectId,
ref: 'User'
}
})
const Post = mongoose.model('Post', PostSchema, 'posts');
const User = mongoose.model('User', UserSchema, 'users');
module.exports = { Post, User }
controllers.js
require('../model/db'); // DB config
const mongoose = require('mongoose');
const models = require('../model/models');
const getUser = (req, res) => {
const user = new models.User({
_id: new mongoose.Types.ObjectId(),
username: 'JohnDoe'
});
user.save(function (err) {
if (err) return res.json(err);
const post = new models.Post({
content: 'New Post',
author: user.id
});
post.save(function (err) {
if (err) return res.json(err);
models.User.findOne({
username: 'JohnDoe'
})
.populate('posts').exec((err, user) => {
res.json(user);
})
});
});
}
const getPost = (req, res) => {
const user = new models.User({
_id: new mongoose.Types.ObjectId(),
username: 'JohnDoe'
});
user.save(function (err, user) {
if (err) return res.json(err);
const post = new models.Post({
content: 'NewPost',
author: user.id
});
post.save(function (err, post) {
if (err) return res.json(err);
models.Post.findOne({
content: 'NewPost'
})
.populate({
path: 'author',
model: 'User'
}).exec((err, user) => {
res.json(user);
})
});
});
}
getUser's result (posts is empty):
{
"posts": [],
"_id": "5b9426a6fd187d3949470f54",
"username": "JohnDoe",
"__v": 0
}
getPost result (author is correctly returned)
{
"_id": "5b94287e99072c3a173419f2",
"content": "NewPost",
"author": {
"posts": [],
"_id": "5b94287e99072c3a173419f0",
"username": "JohnDoe",
"__v": 0
},
"__v": 0
}

You need to push the post id to the posts array in the user model as follows
const getUser = (req, res) => {
const newUser = new models.User({ username: 'JohnDoe' });
newUser.save(user => {
if (err) return res.json(err);
const newPost = new models.Post({
content: 'New Post',
author: user._id
});
newPost.save(post => {
if (err) return res.json(err);
models.User.findByIdAndUpdate(user._id,
{ '$push': { 'posts': post._id } },
{ 'new': true }
)
.populate('posts')
.exec((err, u) => {
res.json(u);
})
});
});
}
Using async/await
const getUser = async (req, res) => {
try {
const newUser = new models.User({ username: 'JohnDoe' });
const user = await newUser.save();
const newPost = new models.Post({
content: 'New Post',
author: user._id
});
const post = await newPost.save();
const result = await models.User.findByIdAndUpdate(user._id,
{ '$push': { 'posts': post._id } },
{ 'new': true }
)
.populate('posts')
.exec();
res.json(result)
}
catch (err) {
return res.json(err);
}
}

Related

In a MERN and Axios app, using the mongoose populate function, the field populates in the server/terminal but not on the front end

I am developing a MERN app with axios and trying to populate a field(songList) in a model (User) that is referencing the Schema.Types.ObjectId of another schema (Song).
The _id populates when I create a new Song appropriately.
I can see the entire referenced field is populated in the terminal server side with a console.log but the I cannot get the field to populate on the client side.
My Model; I am trying to populate songList.
const { Schema, model } = require('mongoose')
const bcrypt = require('bcrypt');
const userSchema = new Schema({
username: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
songList: [
{
type: Schema.Types.ObjectId,
ref: 'Song'
}
],
});
userSchema.pre('save', async function (next) {
if (this.isNew || this.isModified('password')) {
const saltRounds = 10;
this.password = await bcrypt.hash(this.password, saltRounds);
}
next();
});
userSchema.methods.isCorrectPassword = async function (password) {
return bcrypt.compare(password, this.password);
};
const User = model("User", userSchema);
module.exports = User;
My server side query, console.log(userSongs) and console.log(user.songList) shows the array of songs appropriately in the terminal:
//login
router.post('/login', async (req, res) => {
const username = req.body.username;
const password = req.body.password;
User.findOne({ username: username })
.populate({path: "songList"})
.exec((err, user) => {
if (err) {
res.status(500).send({ message: err });
return;
}
if (!user) {
res.status(404).json({ message: 'User Not Found' });
}
const passwordIsValid = bcrypt.compareSync(
password,
user.password
);
if (!passwordIsValid) {
return res.status(401).send({ message: "Invalid Password" });
}
let userSongs = [];
for (let i = 0; i < user.songList.length; i++) {
userSongs.push(user.songList[i])
}
const accessToken = sign(
{ username: user.username, _id: user._id },
"importantsecret");
// res.json({
// token: accessToken,
// username: username,
// _id: user._id,
// songList: user.songList
// });
res.status(200).send({
token: accessToken,
username: username,
_id: user._id,
userSongs: userSongs
});
console.log(userSongs)
});
});
The client side request for the user information where I am hoping to see a populated songList with console.log(singleUser):
const [singleUser, setSingleUser] = useState({})
const [userSongs, setUserSongs] = useState([])
useEffect(() => {
Axios.get(`http://localhost:3001/api/user/${id}`).then((response) => {
setSingleUser(response.data)
})
}, [authState])
Client side login request.
const login = () => {
const data = { username: username, password: password };
Axios
.post("http://localhost:3001/api/user/login", data)
.then((response) => {
if (response.data.error) {
console.log(response.data.error)
} else {
localStorage.setItem('accessToken', response.data.token)
setAuthState({
username: response.data.username,
_id: response.data._id,
status: true
});
window.location.replace('/')
}
})
}
Here is where I create a new Song and add it to the user that is logged in.
router.post('/insert', (req, res) => {
const id = req.body.id;
const songTitle = req.body.songTitle;
const artist = req.body.artist;
const album = req.body.album;
Song.create({ songTitle: songTitle, artist: artist, album: album })
.then((song) => {
return User.findOneAndUpdate(
{ _id: id },
{ $addToSet: { songList: song._id } },
{ new: true }
)
})
.then((user) =>
!user
? res.status(404).json({
message: 'Song created, but found no user with that ID',
})
: res.json('Created the song')
)
.catch((err) => {
console.log(err);
res.status(500).json(err)
})
});
Any suggests on how I can get songList to populate using the populate() mongoose function is much appreciated.
Thank you,
Brandon
I've read articles on stack overflow, 'Mongoose 'populate' not populating",
"Mongoose .populate() not working correctly". Medium articles, and the mongoose documentation.
I've tried sending the user songs in the response back as res.json() and res.send(). The field shows up but is not populated.
I've tried being more specific with songList.songTitle and {path: "songList")
All of these show the field populated in the terminal but not on the front side.

Node Js: Remove string array element from mongoDB

I have a user schema as follows:
const UserSchema = new mongoose.Schema({
skills: [String]
});
module.exports = mongoose.model("User", UserSchema);
And a Fetch request to delete a skill as follows:
const deleteItem = async (id) => {
try {
await fetch(`http://localhost:5000/api/user/deleteskill`, {
method: "DELETE",
headers: { "Content-Type": "application/JSON", token: accessToken },
body: JSON.stringify({ userid: userid , skill:id}),
})
.then((res) => res.json())
.then((data) => {
console.log("USER SKILLS:", data.userskills);
});
} catch (err) {
console.log(err);
}
};
Server
const deleteSkill = async (req, res) => {
try {
const user = await User.findById(req.body.userid)
//user.skills.pull(req.body.skill);
// removeskill = user.skills.filter(function(item) {
// return item !== req.body.skill
// })
if (user.skills.includes(req.body.skill)) {
res.status(400).json("Item Still Exists");
} else {
res.status(200).json("Item Deleted");
}
} catch (error) {
res.status(500).send({ error: error.message });
}
};
the array is in the following structure
[
'skill1', 'java', 'skill5'
]
I have tried to remove the user skill from the array in several ways but I still get res.status(400).json("Item Still Exists");. What I'm doing wrong?
Use the findOneAndUpdate method to find a document with the user id and update it in one atomic operation:
const deleteSkill = async (req, res) => {
try {
let message = "Item Deleted";
let status = 200;
const user = await User.findOneAndUpdate(
{ _id: req.body.userid },
{ $pull: { skills: req.body.skill } },
{ new: true }
)
if (user && user.skills.includes(req.body.skill)) {
message = "Item Still Exists";
status = 400;
} else if (!user) {
message = "User Not Found";
status = 404;
}
res.status(status).send({ message });
} catch (error) {
res.status(500).send({ error: error.message });
}
};
I believe you want to remove skills from the database then the following function could help you out.
var MongoClient = require('mongodb').MongoClient;
var url = "mongodb://localhost:27017/";
MongoClient.connect(url, function(err, db) {
if (err) throw err;
var dbo = db.db("mydb");
var myquery = { userid: userid, skillid: skillid};
dbo.collection("skills").deleteOne(myquery, function(err, obj) {
if (err) throw err;
console.log("1 document deleted");
db.close();
});
});
You have a method of removing elements from arrays, if you want to remove the first one you could use array.shift (more on it here), but if you want to delete it completely from your database you could always, find it and then update it.
User.update({ _id: userid }, { $pull: { "skills": "[skill]" }})

cant add products to shopping cart in mongodb

I am working on a shopping cart and am able to create one in mongodb but I can't add the products that are stored in the DB to the cart. It will just show an empty items array in the console.log as well as mongodb gui. Any help is appreciated.
const express = require('express');
const Carts = require('../repo/carts');
const router = express.Router();
router.post('/cart/products', async (req, res) => {
Carts.findById(req.session.cartId, (err, foundCart) => {
if(err) {
console.log(err)
}
if (foundCart) {
console.log(foundCart);
Carts.update( { _id:req.session.cartId }, {
$push: {
items: {
_id: req.body.productId,
quantity: 1,
},
},
});
} else {
if (!foundCart) {
const newCart = new Carts({
_id: req.session.cartId,
items: [],
});
newCart.save();
}
}
});
res.send('product added to cart!!');
});
module.exports = router;
carts schema for mongodb
const mongoose = require('mongoose');
const cartSchema = new mongoose.Schema({
_id: String,
items: [
{ quantity: Number, _id: String,}
]
});
const Carts = new mongoose.model('Carts', cartSchema);
module.exports = Carts;
image of cart in mongodb robo3t
That err is for the findById, you can add a similar function for the update function and see why it is not working.
Also check if the id body id is received ok.
Carts.findById(req.session.cartId, (err, foundCart) => {
if(err) {
console.log(err) // This err is for the find by Id, not to the update function
}
if (foundCart) {
console.log(foundCart)
console.log(req.body.productId)
Carts.update(
{ _id:foundCart._id }, {
$push: {
items: {
_id: req.body.productId,
quantity: 1,
},
},
},(err,updatedCart) => {
if(err){
console.log(err)
}
}
);
} else {
if (!foundCart) {
const newCart = new Carts({
_id: req.session.cartId,
items: [],
});
newCart.save();
}
}
});
res.send('product added to cart!!');
});
module.exports = router;

Check if ID exist in related mongodb collection with mongoose

I have a "Drinkers" model and a "Sodas" model which is "related" - a drinker can have drunk X amount of sodas.
The route to get the data is this
router.get('/all/:drinkerId', sodasController.getAllSodasFromDrinker)
In my sodasController, is there a way to check if :drinkerId exists in the "Drinkers" collection and if not return an error that the drinker doesn't exist, without having to require the drinkersController in the sodasController.
Right now getAllSodasFromDrinker looks like this
const Sodas = require("../models/sodas.model");
exports.getAllSodasFromDrinker = async (req, res, next) => {
try {
const id = req.params.drinkerId;
if (id.match(/^[0-9a-fA-F]{24}$/)) {
await Sodas.find({ drinker: id }).exec((err, drinkerItem) => {
if (err) {
return next(err);
}
res.json({ data: drinkerItem });
});
} else {
return next("ID is in the wrong format");
}
} catch (error) {
return next(error);
}
};
In that function, I want to check if a user exists with the applied ID.
I want to avoid having to
const Drinkers = require("../models/drinkers.model") in the sodasController
The Drinkers model:
const Schema = mongoose.Schema;
const drinkersSchema = new Schema(
{
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
sodas: {
type: Schema.Types.ObjectId,
ref: "Sodas",
},
},
{ timestamps: true }
);
The Sodas model
const Schema = mongoose.Schema;
const sodaSchema = new Schema(
{
name: {
type: String,
required: true,
},
drinker: {
type: Schema.Types.ObjectId,
ref: "Drinkers",
},
},
{ timestamps: true }
);
I would add a middleware function which validates if the drinkerId exists. If it exists, you can continue to the controller. If not, then you should throw a 404 error.
Your route:
router.get(
'/all/:drinkerId',
drinkerMiddleware.exists,
sodasController.getAllSodasFromDrinker
);
drinkerMiddleware:
exports.exists = async (req, res, next) => {
try {
const drinker await Drinker.find({ drinker: req.params.drinkerId }).exec();
if (!drinker) {
return next("Drinker not found.");
}
return next();
} catch (error) {
return next(error);
}
};

MongoDB unable to push to array when grabbing schema by _id

I have been trying to add a subscription feature to my website where the person subscribes and that class is added to their class array in the users model... Here is my code:
app.post("/subscribe", function (req, res) {
const newClass = req.body.subClass;
const id = req.user.id
const ObjectId = mongoose.Types.ObjectId;
User.findOneAndUpdate(
{ _id: new ObjectId(id) },
{ $push: { classes: newClass } },
{ upsert: false, new: true }
);
res.redirect("/dashboard")
});
Here is my schema:
const userSchema = new mongoose.Schema({
email: String,
password: String,
secret: String,
classes: [String]
});
and the model I am pushing to:
const User = new mongoose.model("User", userSchema)
All help is appreciated
app.post("/subscribe", async function (req, res) {
try {
const newClass = req.body.subClass;
const id = req.user.id
const ObjectId = mongoose.Types.ObjectId;
await User.findByIdAndUpdate(
id,
{ $push: { classes: newClass } },
{upsert: true, new: true }
);
}
catch(err) {
console.error(err);
}
res.redirect("/dashboard")
});
Here's mongoose references link

Categories

Resources