Express.js, Mongoose: Populate returns null - node.js

I'm learning express.js and mongoDB and want to create a functionality, where user should be able add products.
My code contains 2 models, User and Product. User has a reference to Product. In User query when i try to populate Product', null is returned.
I'm testing it with postman, and the result is null in cart.
I am a beginner and don't understand how to solve the issue.
user schema
import mongoose, { Document } from 'mongoose'
import { ProductDocument } from './Products'
export type UserDocument = Document & {
firstName: string;
lastName: string;
password: string;
email: string;
isAdmin: boolean;
cart: ProductDocument[];
resetLink: string;
}
const userSchema = new mongoose.Schema({
firstName: {
type: String,
required: true,
max: 64,
},
lastName: {
type: String,
required: true,
max: 64,
},
password: {
type: String,
required: true,
min: 6,
},
email: {
type: String,
required: true,
lowercase: true,
trim: true,
},
isAdmin: {
type: Boolean,
default: false,
},
cart: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Products',
},
],
resetLink: {
data: String,
default: '',
},
})
export default mongoose.model<UserDocument>('Users', userSchema)
product schema
import mongoose, { Document } from 'mongoose'
export type ProductDocument = Document & {
name: string;
description: string;
categories: string[];
variants: string[];
sizes: number[];
}
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true,
index: true,
},
description: {
type: String,
},
categories: [String],
variants: [String],
sizes: [Number],
})
export default mongoose.model<ProductDocument>('Products', productSchema)
and the controller for responsible for populating
export const getProduct = async (req: Request, res: Response) => {
try {
const { productId } = await req.body
const productBought = await Products.findOne({ _id: productId }).then(
(product) => {
return product
}
)
console.log(productBought)
const updated = await Users.findOneAndUpdate(
{ _id: req.params.userId },
{ $push: { cart: productBought } },
{ new: true }
)
.populate('cart')
.exec()
return res.json(updated)
} catch (error) {
return res.status(404).json({ message: 'does not work' })
}
}
output from postman
{
"isAdmin": false,
"cart": [
null,
null
],
"_id": "5f894b3c06b4a108f8d9a7ab",
"firstName": "John",
"lastName": "Doe",
"password": "$2b$08$rA0/r8iVBWeSyyerPDpPWO.ztgouQoseX0QFBZ.mlPgb6tELlrhpy",
"email": "john#gmail.com",
"__v": 0
}

your "cart" like[ObjectId,ObjectId], but your "productBought" like[{},{}].
try let cart = [];productBought.forEach((v,k) => {cart[k] = v._id});
and use { $push: { cart: { $each: cart } },

Related

How to return documents in mongoose and express js which are associated with only logged in user?

Here i have two mongoose models orders and users which have one to many relationship.
user.model.js
import mongoose from "mongoose";
const userSchema = mongoose.Schema(
{
firstname: {
type: String,
required: [true, "Name is required"],
},
lastname: {
type: String,
required: [true, "LastName is required"],
},
email: {
type: String,
required: [true, "Email is required"],
unique: true,
},
password: {
type: String,
required: [true, "Password is required"],
},
isAdmin: { type: Boolean, default: false },
},
{
timestamps: true,
}
);
const User = mongoose.model("User", userSchema);
export default User;
order.model.js
import mongoose from "mongoose";
const orderSchema = mongoose.Schema(
{
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
customerId: { type: String },
paymentIntentId: { type: String },
products: [
{
id: { type: String },
name: { type: String },
category: { type: String },
price: { type: String },
size: { type: String },
color: { type: String },
thumbnail: { type: String },
qty: { type: Number },
},
],
// subTotal: { type: Number, required: true },
total: { type: Number, required: true },
shipping: { type: Object, required: true },
deliveryStatus: { type: String, default: "pending" },
paymentStatus: {
type: String,
required: true,
},
},
{
timestamps: true,
}
);
const Order = mongoose.model("Order", orderSchema);
export default Order;
I have some orders created by different users in my database. Now i am trying to get those specific orders associated with currently logged in user.
order.controller.js
export const getAllOrders = async (req, res) => {
const { _id } = req.user;
// console.log(typeof id);
try {
const orders = await Order.find({userId: _id});
console.log(orders);
res.status(200).json({ orders });
} catch (error) {
res.status(500).json({ msg: error.message });
}
};
I have tried this one but it always return an empty array.

How can I push the items to an array field schema in mongodb?

What im trying to do is to push the location and the status of the user in an array in a Schema.
The reason I'm doing that is to store all the user locations in a schema(like a history location)
When I create a location I use the method findOneAndUpdate().
controller/auth.js
exports.addLocation = asyncHandler(async (req, res, next) => {
const {phone, locations, status} = req.body;
const theLocation = await User.findOneAndUpdate(
{ phone },
{ locations, status },
{ new: false }
)
res.status(200).json({
success: true,
msg: "A location as been created",
data: theLocation
})
})
models/User.js
const UserSchema = new Schema({
phone: {
type: String,
unique: true,
},
createdAt: {
type: Date,
default: Date.now
},
code: {
type: Number,
max: 4
},
fullName: {
type:String
},
gender: {
type:String
},
birthdate: {
type:String
},
locations: [
{
location: {
latitude: {type:Number},
longitude: {type:Number},
},
status: {
type: String
},
createdAt: {
type: Date,
default: Date.now,
}
}
],
});
So when a new item is stored, the previous one is deleted.
How can I save the previous item to another MongoDB field?
You can use $push at your query like:
User.findOneAndUpdate(
{ phone: "0912341234" },
{
$push: {
locations: {
location: {
latitude: 10,
longitude: 10,
},
status: "online",
},
},
},
{ new: true },
);

Cast to ObjectId failed for value "{ id: 61141a8345c9ba4338f2af20 }" (type Object) at path "_id" for model "LeaveTypes"

I was trying to create HRM project using Node and Mongodb (Mongoose) with leave management so for the leave I have created two documents 1. for leavetypes i.e anualLeave, maternityLeave and so on and the other one of taking care of the leave requests taken by the employees.
So here is my schemas and api requests.
// leave schema embedded in leaveTypeSchema
const mongoose = require("mongoose");
const Joi = require("joi-browser");
Joi.objectId = require("joi-objectid")(Joi);
const { branchSchema } = require("./branch");
const { employeeSchema } = require("./employee");
const { leaveTypesSchema } = require("./leaveType");
const leaveSchema = mongoose.Schema({
branch: {
type: branchSchema,
required: true,
},
employee: {
type: employeeSchema,
required: true,
},
leaveType: {
type: [leaveTypesSchema],
required: true,
},
daysRequested: {
type: Number,
required: true,
},
fromDate: {
type: Date,
required: true,
},
endDate: {
type: Date,
required: true,
},
availableDays: {
type: Number,
},
});
const Leave = mongoose.model("leave", leaveSchema);
//validation
function validateLeave(leave) {
const schema = {
branchId: Joi.objectId().required(),
employeeId: Joi.objectId().required(),
leaveType: Joi.object()
.keys({
anualLeave: Joi.object()
.keys({
id: Joi.objectId().required(),
})
.required(),
})
.required(),
daysRequested: Joi.number().required(),
fromDate: Joi.date().required(),
endDate: Joi.date().required(),
};
return Joi.validate(leave, schema);
}
module.exports.Leave = Leave;
module.exports.Validate = validateLeave;
//route to post leave requests from employees
router.post("/", async (req, res) => {
// validate
const { error } = Validate(req.body);
if (error) return res.status(400).send(error.details[0].message);
// check if branch is valid
let branch = await Branch.findById(req.body.branchId);
if (!branch) return res.status(400).send("Invalid Branch");
// check if employee is valid
let employee = await Employee.findById(req.body.employeeId);
if (!employee) return res.status(400).send("Invalid employee");
// check if leaveType is valid
let leaveType = await LeaveType.findById({
id: ObjectID(req.body.leaveType.anualLeave.id),
});
if (!leaveType) return res.status(400).send("invalid leave Type");
// post the leave request
const leave = new Leave({
branch: {
_id: branch._id,
name: branch.name,
},
employee: {
_id: employee._id,
fullName: employee.fullName,
phoneNumber: employee.phoneNumber,
branch: {
_id: branch._id,
name: branch.name,
},
jobTitle: employee.jobTitle,
salary: employee.salary,
},
leaveType: [
{
anualLeave: {
id: leaveType.anualLeave.id,
},
},
],
daysRequested: req.body.daysRequested,
fromDate: req.body.fromDate,
endDate: req.body.endDate,
});
await leave.save();
res.send(leave);
Your document doesn't abide by the way you have created your schema.
When you are passing data to model, you have made leavetype nested inside employee
const leave = new Leave({
/**/
employee: {
_id: employee._id,
fullName: employee.fullName,
phoneNumber: employee.phoneNumber,
branch: {
_id: branch._id,
name: branch.name,
}, <- here
leaveType: [
{
anualLeave: {
id: leaveType.anualLeave.id,
},
},
],
});
whereas in the schema your leaveType is a diff. object property.
employee: {
type: employeeSchema,
required: true,
},
leaveType: {
type: [leaveTypesSchema],
required: true,
},

How can I update some fields of an embedded object using mongoose's findOneAndUpdate method and not lose the other fields?

router.put('/experience/update/:exp_id',
auth,
async (req, res) => {
const {
title,
company,
location,
from,
to,
current,
description
} = req.body;
const newExp = {};
newExp._id = req.params.exp_id;
if (title) newExp.title = title;
if (company) newExp.company = company;
if (location) newExp.location = location;
if (from) newExp.from = from;
if (to) newExp.to = to;
if (current) newExp.current = current;
if (description) newExp.description = description;
try {
let profile = await Profile.findOne({ user: req.user.id });
if (profile) {
//UPDATE Experience
profile = await Profile.findOneAndUpdate(
{ user: req.user.id });
const updateIndex = profile.experience.map(exp => exp._id).indexOf(req.params.exp_id);
profile.experience[updateIndex] = newExp;
console.log('Experience updated!')
}
await profile.save();
res.json(profile);
} catch (error) {
console.log(error.message);
res.status(500).send('Internal Server Error');
}
}
)
I am using the findOneAndUpdate method to update the experience field inside a profile mongoose model.
After accesssing the endpoint, I put the updated details, for eg. company and location. But I lose all the other fields. So how can I update only select fields while others remain unchanged ?
Below is the profile schema:
const mongoose = require('mongoose');
const ProfileSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
},
company: {
type: String
},
website: {
type: String
},
location: {
type: String
},
status: {
type: String,
required: true
},
skills: {
type: [String],
required: true
},
bio: {
type: String
},
githubusername: {
type: String
},
experience: [
{
title: {
type: String,
required: true
},
company: {
type: String,
required: true
},
location: {
type: String
},
from: {
type: Date,
required: true
},
to: {
type: Date,
},
current: {
type: Boolean,
default: false
},
description: {
type: String,
}
}
],
education: [
{
school: {
type: String,
required: true
},
degree: {
type: String,
required: true
},
fieldofstudy: {
type: String,
required: true
},
from: {
type: Date,
required: true
},
to: {
type: Date,
required: true
},
current: {
type: Boolean,
default: false
},
description: {
type: String,
}
}
],
social: {
youtube: {
type: String,
},
twitter: {
type: String,
},
facebook: {
type: String,
},
linkedIn: {
type: String,
},
instagram: {
type: String,
}
},
date: {
type: Date,
default: Date.now
}
});
module.exports = Profile = mongoose.model('profile', ProfileSchema);
There are some problems in your code.
You are passing only one argument to findOneAndUpdate. Ideally the syntax is findOneAndUpdate(filter, update). So basically you need to pass update query as 2nd argument.
profile = await Profile.findOneAndUpdate(
{ user: req.user.id });
In below code you are modifying the profile object and saving it. Which is not required. And this is also the reason why you are losing fields.
const updateIndex = profile.experience.map(exp => exp._id).indexOf(req.params.exp_id);
profile.experience[updateIndex] = newExp;
console.log('Experience updated!')
}
await profile.save();
Solution-
We need to figure out the update part of findOneAndUpdate(filter, update).
Here is the update query -
db.collection.update({
"user": "5f96dc85ac5ae03160a024a8",
"experience._id": "5f9826c3a3fa002ce0f11853"
},
{
"$set": {
"experience.$": {
"current": false,
"_id": "5f9826c3a3fa002ce0f11853",
"title": "Senior developer",
"company": "Morgan Stanley",
"location": "Pune",
"from": "2017-04-30T18:30:00.000Z",
"to": "2020-07-08T18:30:00.000Z",
"description": "testing"
}
}
})
Try it here
Trying Mongoose way :
const filter = { user: req.user.id, "experience._id": req.params.exp_id }
const update = { $set: { "experience.$": newExp } }
profile = await Profile.findOneAndUpdate(filter,update);

Mongoose doesn't save all the fields of my POST request

I have a very simple "social network" app: users can register, write posts, like/unlike them and comment a post.
I have a probleme with my Post Schema :
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
// Create Schema
const PostSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: "user",
},
text: {
type: String,
required: true,
},
name: {
type: String,
},
avatar: {
type: String,
},
likes: [
{
user: {
type: Schema.Types.ObjectId,
ref: "user",
},
},
],
comments: [
{
user: {
type: Schema.Types.ObjectId,
ref: "user",
},
},
{
text: {
type: String,
required: true,
},
},
{
name: {
type: String,
},
},
{
avatar: {
type: String,
},
},
{
date: {
type: Date,
default: Date.now,
},
},
],
date: {
type: Date,
default: Date.now,
},
});
module.exports = Profile = mongoose.model("post", PostSchema);
When I receive my POST request for comments...
// #route POST api/posts/comment/:id
// #desc Add comment to post
// #access Private
router.post(
"/comment/:id",
passport.authenticate("jwt", { session: false }),
(req, res) => {
const { errors, isValid } = validatePostInput(req.body);
// Check Validation
if (!isValid) {
// If any errors, send 400 with errors object
return res.status(400).json(errors);
}
Post.findById(req.params.id)
.then(post => {
const newComment = {
text: req.body.text,
name: req.body.name,
avatar: req.body.avatar,
user: req.user.id,
};
console.log("newComment: ", newComment);
// Add to comments array
post.comments.unshift(newComment);
console.log("post: ", post.comments);
// Save
post.save().then(post => res.json(post));
})
.catch(err => res.status(404).json({ postnotfound: "No post found" }));
},
);
The only fields that are saved in the post.comments array is the User. Not the other fields (text, name, avatar, date).
My console.log("newComment: ", newComment); properly returns the complete object with all its properties but then, 2 lines below, console.log("post: ", post.comments); only returns the comment _id and the user and those are the only fields saved in the DB...
What did I miss here?
There is some Problem in the creation of the schema structure, this is the correct way:
comments: [
{
user: {
type: Schema.Types.ObjectId,
ref: "user",
},
text: {
type: String,
required: true,
},
name: {
type: String,
},
avatar: {
type: String,
},
date: {
type: Date,
default: Date.now,
},
}
]
the valid structure is nothing but like this(showing the changes made above):
comments: [{
user: {},
text: {},
// others...
}]

Resources