Mongoose virtual return null - node.js

I have a Person schema :
const person = new mongoose.Schema({
name: String,
birthday: Date,
sex: String
},
{
toObject: { virtuals: true },
toJSON: { virtuals: true }
});
person.virtual('tasks', {
ref: 'task',
localField: '_id',
foreignField: 'person'
});
export default mongoose.model('person', person);
And a task one which has the person field :
const schema = new mongoose.Schema({
person: {
type: mongoose.Schema.Types.ObjectId,
ref: 'person',
required: true
},
name: String
});
export default mongoose.model('task', schema);
And my API to retrieve one person :
api.get('/person/:id', async (req, res) => {
let personID = req.params.id;
if (personID) {
let person = await Person.findById(personID);
if (person)
res.json(person);
else
res.status(404).end();
}
else {
res.status(400).end();
}
});
When I query the API for one person, tasks is always null, any ideas why?
Thanks
Update
I tried with :
let person = await Person.findById(personID).populate('tasks').exec();
and it's still returning null.

Use populate in your query.
Can you please check one of the below query
let person = await Person.findOne({ _id: personID })
.populate('tasks')
.exec((error, personData)
=> personData);
OR
let person = await Person.findById(personID)
.populate('tasks')
.exec((error, personData) => personData);
reference Link : http://thecodebarbarian.com/mongoose-virtual-populate

Ok I found the issue, I was registering the model before setting up the virtual (in some wrapper I wrote around mongoose.Schema).

Related

Ref in mongoose model not giving output

I am using mongoose for defining schema. I have two schemas user and Userdetail. i want data from user in userdetail
I have below schema but i am not getting the output. i think the code is correct but not getting why there is no output...instead i am getting empty array.
const mongoose = require("mongoose")
const UserDetailSchema = mongoose.Schema({
Phone : {
type : Number
},
FirstName : {
type : String
},
LastName : {
type : String
},
productimage : {
data : Buffer,
contentType : String
},
IsDeleted:{
type:Boolean,
default:false
},
UserID : {
type : String,
},
data : [{
type: mongoose.Schema.Types.ObjectId,
ref: "user"
}],
},
{timestamps: true})
const UserDetail = new mongoose.model("userdetail",UserDetailSchema);
module.exports = UserDetail;
my user schema is,
const mongoose = require("mongoose");
const UserSchema = mongoose.Schema({
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
IsDeleted:{
type:Boolean
},
},
{timestamps: true});
module.exports = mongoose.model("user", UserSchema);
query is,
<pre>
router.get("/UserDetail",async (req,res)=>{
try{
const UsersData= await UserDetail.find();
res.json(UsersData)
}catch(e){
res.status(500).json({ message: e.message })
}
})
</pre>
Even though i am using only find, i must get the data with only id right?
Output is -
Any help would be appreciated
router.patch("/UserDetail/:id",Auth,upload.single("productimage"),async(req,res)=>{
try{
const id = req.params.id;
const updatedData = req.body;
updatedData.productimage = {data: fs.readFileSync('upload/' + req.file.filename),
contentType: 'image/png'};
const options = { new: true };
const result = await UserDetail.findOneAndUpdate(
id, updatedData, options
)
res.send(result)
}catch(e){
res.status(500).json({ message: e.message })
}
})
You can populate a field with the populate function:
const userDetails = await UserDetail.find({}).populate('data').exec();
firstly you need a little change in userID in schema of userDetail.Please make it to UserID:{type : mongoose.Schema.Types.ObjectId}, as it will help you in future during aggregation and you can also remove data from your userDetail model as it will not store any data until you save it.And lastly try to run this aggregation query.
const UsersData= await UserDetails.aggregate([
{$lookup:
{
from: "users",
localField: "userID",
foreignField: "_id",
as: "data"
}
}])
In this way your respective details of users will be displayed in array of data.
Make changes in your model and then populate the data.
const mongoose = require("mongoose")
const UserDetailSchema = mongoose.Schema({
Phone : {
type : Number
},
FirstName : {
type : String
},
LastName : {
type : String
},
productimage : {
data : Buffer,
contentType : String
},
IsDeleted:{
type:Boolean,
default:false
},
UserID : {
type : String,
},
data : {
type: mongoose.Schema.Types.ObjectId,
ref: "user"
},
},
{timestamps: true})
}
populate query
let Model=//import your model here
let userdata=await Model.find().populate("data")
console.log(userdata)

Accessing a schema inside a schema using Express Router and MongoDG

I'm trying to create a route where it takes in a parameter for a username and then displays that users information. Only thing is, the username is in the user schema from when the user signs up. The profile schema references the user schema. How do I use the username parameter in the findOne call to display the users profile data?
User schema:
const UserSchema = new Schema({
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
module.exports = User = mongoose.model("users", UserSchema);
Profile schema:
const ProfileSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: "users"
},
name: {
type: String
},
image: {
type: String
},
bio: {
type: String
},
location: {
type: String
},
website: {
type: String
},
social: {
youtube: {
type: String
},
facebook: {
type: String
},
instagram: {
type: String
},
twitter: {
type: String
}
}
});
module.exports = User = mongoose.model("profile", ProfileSchema);
Route:
router.get("/user/:username", (req, res) => {
const errors = {};
Profile.findOne({ user: req.params.user.username })
.populate("user", "username")
.then(profile => {
if (!profile) {
errors.noprofile = "There is no profile for this user";
return res.status(404).json(errors);
}
res.json(profile);
})
.catch(err => res.status(404).json(err));
});
Please try this :
router.get("/user/:username", async (req, res) => {
const errors = {};
try {
const profile = await User.aggregate([
{ $match: { username: req.params.username } },
{ $lookup: { from: "profile", localField: "_id", foreignField: "user", as: "userProfile" } },
{ $project: { userProfile: { $arrayElemAt: ["$userProfile", 0] }, username: 1, _id:0 } }
]).exec();
if (!profile.length) {
errors.noprofile = "There is no profile for this user";
return res.status(404).json(errors);
}
res.json(profile[0]);
} catch (error) {
console.log('Error in retrieving user from DB ::', error);
return res.status(404);
}
})
Try using aggregate, firstly you check-in user table for getting details of a specific username then fetch the profile details as below using lookup, if no profile found after unwind the document will not be fetched and you can check on aggregate result's length as aggregate always return an array in result :
User.aggregate([
{$match:{ username: req.params.user.username }},
{$lookup:{from:"profile",localField:"_id",foreignField:"userId",as:"profileData"}},
{$unwind:"$profileData"},
{$project:{profileData:1,username:1}}
{$limit:1}
])
.then(profile => {
if (!profile.length) {
errors.noprofile = "There is no profile for this user";
return res.status(404).json(errors);
}
res.json(profile[0]);
})
You can do it in 2 steps.
Look for users containing username in userSchema, get it's id.
Then in promise, use that id to, look for profileSchema contains.
router.get("/user/:username", (req, res) => {
users.findOne({ username: req.params.username }).then(_user=>{
profile.findOne({ user: _user._id }).populate('user').then(_profile => {
res.json(_profile);
})
})
});
This code will look for username in userSchema and look for userSchema's id in profileSchema then returns profileSchema populated with user.

Mongoose create child and associated parent at same time

So basically, in my application I have a employee, and a company model. This is just the basic information about these models, there is actually more information, so using nested objects rather than 2 schema's doesn't seem like a good option (I think)
var EmployeeSchema = new Schema(
{
name: { type: String, required: true, max: 100 },
company: { type: Schema.Types.ObjectId, ref: 'Company', required: true },
}
);
var CompanySchema = new Schema(
{
name: { type: String, required: true },
},
{
toJSON: { virtuals: true },
},
);
CompanySchema.virtual('employees', {
ref: 'Employee',
localField: '_id',
foreignField: 'company',
justOne: false,
});
And on a form to create a new employee, I want the option to either select a company, or create a new one.
So my API will send information like the following:
employee: {
name: 'John Bastien',
company: 5d44635fa5993c0424da8e07
}
or:
employee: {
name: 'Dan Smith',
company: {
name: 'ACME'
}
}
This of course can be changed, it was just what I had in mind.
So in my express app when I do var employee = await new Employee(req.body.employee).save(); How can I make it so that the company is created along with the employee. It works fine when sending an object ID, but how can I do it with just a JSON object for the associated document?
I ended up writing some middleware on my models that will handle this. This logic could be extracted out to make it more generic, but for my use case it hasn't needed to yet.
EmployeeSchema.virtual('company', {
ref: 'Company',
localField: 'companyId',
foreignField: '_id',
justOne: true,
}).set(function(company) {
this.companyId= company._id;
this.$company = company;
return this.$company;
});
EmployeeSchema.pre('validate', function(next) {
if (this.company && this.company instanceof Company) {
var err = this.company.validateSync();
if (err) {
// mergeErrors is a helper function that will merge the two exceptions into a nice format
err = mergeErrors(this.validateSync(), { company: err });
}
next(err);
}
next();
});
EmployeeSchema.pre('save', async function(next, saveOpts) {
if (this.company && this.company instanceof Company && this.company.isModified()) {
await this.company.save(saveOpts);
}
next();
});

Mongoose - How to populate a sub document on condition

I'm new to Mongoose I don't know how to populate on condition.
So this is my model :
const OrderSchema = new Schema({
products: [{ type: Schema.Types.ObjectId, ref: 'Product' }],
remarks: {type: String, lowercase: true}
});
mongoose.model("Order", OrderSchema);
const ProductSchema = new Schema({
reference: {type: String}
status: {type: Schema.Types.ObjectId, ref: 'ProductStatus'}
});
mongoose.model("Product", ProductSchema);
const ProductStatus = new Schema({
name: {type: String}
});
const CountrySchema = new Schema({
name: {type: String}
});
mongoose.model("Country", CountrySchema);
I have a getOrderById methods
export const getOrderById = async (req, res) => {
let id = req.params.id;
try {
await orderModel
.findById(id)
.populate({
path: 'products',
populate: {
path: 'country',
model: 'Country'
}
})
.populate({
path: 'products',
populate: {
path: 'status',
model: 'ProductStatus'
}
})
.exec(function (err, orders) {
if (err) {
res.send(err);
}
res.status(200).json(orders);
});
} catch (error) {
console.log(error);
}
}
And now I would like to show in the order lists all products that have the status Received in France.
First, I guess you also missed reference to the country in the product schema, so assuming these are your corrected schemas:
const OrderSchema = new Schema({
products: [{
type: Schema.Types.ObjectId,
ref: 'Product'
}],
remarks: {
type: String,
lowercase: true
}
});
const Order = mongoose.model("Order", OrderSchema);
const ProductSchema = new Schema({
reference: {
type: String
},
country: {
type: Schema.Types.ObjectId,
ref: 'Country'
},
status: {
type: Schema.Types.ObjectId,
ref: 'ProductStatus'
}
});
const Product = mongoose.model("Product", ProductSchema);
const ProductStatusSchema = new Schema({
name: {
type: String
}
});
const ProductStatus = mongoose.model("ProductStatus", ProductStatusSchema);
const CountrySchema = new Schema({
name: {
type: String
}
});
const Country = mongoose.model("Country", CountrySchema);
As far as I understand you want to only show the products, whose country's name is 'France' and ProductStatus' name is 'Received', these kind of operations are done through Aggregation
Your query may look like this assuming you want to do it one query:
const getOrderById = async (req, res) => {
let id = req.params.id.toString();
const ObjectId = mongoose.Types.ObjectId
try {
const aggregationStages = [{
$match: {
_id: ObjectId(id) //It is important to cast to ObjectId in aggregations
}
}, {
$lookup: {
from: 'products',
let: {
productIds: '$products'
},
pipeline: [{
$match: {
$expr: {
$in: ['$_id', '$$productIds']
}
}
}, {
$lookup: {
from: 'countries',
localField: 'country',
foreignField: '_id',
as: 'country'
}
}, {
$lookup: {
from: 'productstatuses',
localField: 'status',
foreignField: '_id',
as: 'status'
}
}, {
$match: {
'country.name': 'France',
'status.name': 'Received'
}
}],
as: 'products'
}
}];
await orderModel.aggregate(aggregationStages)
.exec(function (err, orders) { // The return is an array btw.
if (err) {
res.send(err);
}
res.status(200).json(orders);
});
} catch (error) {
console.log(error);
}
}
If you feel the aggregation is complicated you may resort to breaking it to smaller simpler queries. Feel free to ask if you need more explanation/modification.

Reference not populated

In a User schema, I have a simple reference to a Customer schema.
const UserSchema = new Schema({
customer: { type: Schema.Types.ObjectId, ref: Customer }, // Customer is the compiled CustomerSchema
...
});
const CustomerSchema = new Schema({
name: String,
...
});
In an Express controller, I'm fetching an user and I'm trying to embed the customer in the returned JSON:
export function me(req, res, next) {
User
.findOne({ _id: req.user._id }, '-salt -hashedPassword')
.populate('customer')
.exec((err, user) => {
if(err) return next(err);
if(!user) return res.json(401);
res.json(user);
});
}
But in the response, customer is null.
The test data I use:
A user document:
{
"_id" : ObjectId("570d1f0938f7da5151b815d2"),
"customer" : ObjectId("570d1f0838f7da5151b815d0"),
...
}
The related customer document:
{
"_id" : ObjectId("570d1f0838f7da5151b815d0"),
...
}
Probably a noob question, but I don't see what I don't see what I could forget =)
I think ref must be a string:
customer: { type: Schema.Types.ObjectId, ref: 'Customer' },

Resources