I'm fairly new to Mongo and Mongoose, so if this question is rather silly then feel free to point me in the right direction.
I have a API with models for users and stories that they published. When I display the users, I want the stories to show as well.
The relation is drawn rather simply:
const userSchema = mongoose.Schema({
username: {
type: String,
unique: true,
},
password: String,
firstName: {
type: String,
required: false,
},
lastName: {
type: String,
required: false,
},
birthday: {
type: Date,
},
bio: {
type: String,
required: false,
},
country: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Country',
},
stories: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Story',
}]
})
With a stories scheme declared as such:
const storySchema = new mongoose.Schema({
name: {
type: String,
max: 255,
},
url: {
type: String,
unique: true,
},
description: {
type: String,
required: false,
},
length: {
type: Number,
default: 0,
},
createdAt: Date,
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
language: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Country'
},
type: {
type: mongoose.Schema.Types.ObjectId,
ref: 'StoryType'
},
genre: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Genre'
},
})
I print the values using the simple find method (the action for now is only meant to show all of them.
app.get('/list-users', async (req, res) => {
const users = await User.find().populate('country').populate('stories').exec();
res.json(users);
});
Unfortunately, although the results show all the values properly, the stories array does not get populated at all.
[
{
"_id": "6127ce5b0576979256fd3e08",
"username": "test",
"password": "123",
"firstName": "tst",
"lastName": "tst",
"birthday": "1995-11-02T00:00:00.000Z",
"country": {
"_id": "6127c020f95eb5abf72f713a",
"name": "Poland",
"languange": "Polish",
"__v": 0
},
"stories": [],
"__v": 0
}
]
The docs have a clear indicator that such schemas will return an empty array, and I am aware I could just run a find by User and populate with that but I'm wondering what is the cleanest solution to apply in a situation like this.
Any help would be of great value.
Related
I am having a rough time querying my collection of data, Im building a simple E-Comm platform and in that platform i want to be able to have categories for the products. the idea is simple, each category should be its own object in the database that has a collection of products, however each product can have multiple categories, this is for products that might fit into one or more category definitions.
here is the model for the categories
import mongoose from "mongoose";
const categorySchema = mongoose.Schema(
{
cat_name: {
type: String,
required: true,
unique: true,
},
cat_items: [
{
product: {
type: mongoose.Schema.Types.ObjectId,
ref: "Product",
},
},
],
// Adds a relationship between a User admin and a product
// this is useful to see which admin, if multiple created a category
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: "User",
},
},
{ timestamps: true }
);
const Category = mongoose.model("Category", categorySchema);
export default Category;
Here is the Product model
const productSchema = mongoose.Schema(
{
// Adds a relationship between a User admin and a product
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: "User",
},
name: {
type: String,
required: true,
},
image: {
type: String,
required: true,
},
brand: {
type: String,
required: true,
},
category: {
type: String,
required: true,
},
categories: [
{
category: {
type: mongoose.Schema.Types.ObjectId,
ref: "Category",
},
},
],
description: {
type: String,
required: true,
},
reviews: [reviewSchema],
rating: {
type: Number,
required: true,
default: 0,
},
numReviews: {
type: Number,
required: true,
default: 0,
},
price: {
type: Number,
required: true,
default: 0,
},
countInStock: {
type: Number,
required: true,
default: 0,
},
},
{
timestamps: true,
}
);
const Product = mongoose.model("Product", productSchema);
export default Product;
I believe the overall problem is the way i have the models setup, because any query i run on the categories object to return the products associated with it in cat_items, does not return any products it simply returns an empty array.
here is the simple function to return data
const products = await category.map((product)=> { return Product.findById(product._id)})
here im pulling out the array of category items, and mapping over them looking for the product in the database by the _id which is what product is example: 620e626203a59f0004e5a8c6 this in theory if you had 3 items, would return a new array of the product objects that i can send to the client, however every attempt only returns a [] and im pretty sure this is all do in part to the way i have the models setup.
for reference this is what returning a category looks like in postman:
{
"category": {
"_id": "62102f5c2b990d0e7c7d9bf8",
"cat_name": "Pendants",
"user": "620af3311fe4c247904b84d9",
"cat_items": [
{
"_id": "620e626203a59f0004e5a8c6"
},
{
"_id": "620e626203a59f0004e5a8c6"
}
],
"createdAt": "2022-02-18T23:44:28.697Z",
"updatedAt": "2022-02-18T23:54:23.103Z",
"__v": 2
},
"products": [
{},
{}
]
}
where im trying to fill the products array with the actual products stashed in the database
I have succesfully used population across multiple levels and field selection separately. But I couldn't find a way to do them both at the same time.
Population across multiple levels
Profile.findOne({ user: req.user.id })
.populate({
path: "user",
populate: { path: "following" },
})
Field selection:
Profile.findOne({ user: req.user.id })
.populate("user", ["name", "avatar"])
I want to do something like this:
NOTE: THIS DOESN'T WORK
Profile.findOne({ user: req.user.id })
.populate({
path: ["user", ["name", "avatar"]],
populate: { path: "following" },
This is the Profile model:
const ProfileSchema = new Schema({
//We want to associate the user with the profile
user: {
//This will associate the user by his id
type: Schema.Types.ObjectId,
ref: "users",
},
// We want a user friendly url
handle: {
type: String,
// required: true, // Even though we are doing pre-validatioon
max: 40,
},
bio: {
type: String,
},
school: {
type: String,
},
major: {
type: String,
},
website: {
type: String,
},
location: {
type: String,
},
// Optional (might add it later)
/**
* status: {
//where they are in their career
type: String,
required: true
},
*/
skills: {
// In the form they will put skill1,skill2,.. and we will turn that into an array
type: [String],
// required: true,
},
interests: {
// In the form they will put interest1,interest2,.. and we will turn that into an array
type: [String],
// required: true,
},
help_offers: {
// In the form they will put help_offer1,help_offer2,.. and we will turn that into an array
type: [String],
// required: true,
},
// Might be volunteering experience
experience: [
{
title: { type: String, required: true },
company: {
type: String,
required: true,
},
location: {
type: String,
},
from: {
type: Date,
required: true,
},
to: {
type: Date,
// Not required, going to be a checkbox
},
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,
// Not required, going to be a checkbox
},
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,
dafault: Date.now,
},
videoURL: {
type: String,
},
});
This is the User model:
const UserSchema = new Schema({
name: {
type: String,
required: true,
},
surname: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
avatar: {
type: String,
},
birthday: {
type: String,
required: true,
},
country: {
type: String,
required: true,
},
date: {
type: Date,
default: Date.now,
},
following: [
{
type: Schema.Types.ObjectId,
//The User has only ObjectId in uses array. ref helps us get full fields of User when we call populate() method.
//https://bezkoder.com/mongodb-many-to-many-mongoose/
ref: "users",
},
],
followers: [
{
type: Schema.Types.ObjectId,
ref: "users",
},
],
});
I am not sure if I understood your aim by this
Profile.findOne({ user: req.user.id })
.populate({
path: ["user", ["name", "avatar"]],
populate: { path: "following" },
But if what i understood is what you want, then we're lucky. here is what i got.
await RequestAproves.find()
.populate('user', 'firstName surname email roleId')
.populate({
path: 'request',
populate: [
{ path: 'budgetItem', select: 'code name' },
{ path: 'user', select: 'firstName surname' },
],
}
)
More details will come later, Sorry, I couldnt use your schema description, Incase it doesnt work out, let me know, ill try to use your schema.
I'm new to building rest api's with mongoose and express and have a question on how to use refPath correctly on my Models files and allowing for an array of items.
Below I have included the code for a model (built thus far) and would love any input on if I'm even close to building this correctly.
I will also include a screenshot that visually depicts the relationships I'm trying to create.
Those who answer questions here are GODS and I appreciate all the help this community has given me over the years!
const mongoose = require("mongoose");
const slugify = require("slugify");
const AlertSchema = new mongoose.Schema({
parentId: {
type: mongoose.Schema.ObjectId,
required: true,
refPath: "parentModel",
},
parentModel: {
type: String,
required: true,
enum: ["orgs", "clients"],
},
status: { type: String, default: "no-status" },
departments: [{ type: mongoose.Schema.Types.ObjectId, ref: "orgs" }],
createdAt: { type: Date, default: Date.now },
createdByType: [{ type: mongoose.Schema.Types.ObjectId, ref: "users" }],
createdById: [{ type: mongoose.Schema.Types.ObjectId, ref: "users" }],
groups: [{ type: String, default: "unGrouped" }],
stage: [{ type: mongoose.Schema.Types.ObjectId, ref: "stages" }],
children: { type: String },
resource: {
type: String,
match: [
/https?:\/\/(www\.)?[-a-zA-Z0-9#:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()#:%_\+.~#?&//=]*)/,
"Please use a valid URL with HTTP or HTTPS",
],
},
notes: [{ type: mongoose.Schema.Types.ObjectId, ref: "notes" }],
comments: [{ type: mongoose.Schema.Types.ObjectId, ref: "comments" }],
priority: { type: String },
assignedTo: [{ type: mongoose.Schema.Types.ObjectId, ref: "users" }],
title: {
type: String,
required: [true, "Please add a title"],
maxlength: [50, "Title cannot be more than 50 characters"],
},
message: {
type: String,
required: [true, "Please add a message"],
maxlength: [500, "Message cannot be more than 500 characters"],
},
slug: String,
});
//create alert slug from the title
AlertSchema.pre("save", function (next) {
console.log("Slugify Ran", this.name);
this.slug = slugify(this.title, { lower: true });
next();
});
module.exports = mongoose.model("Testalert", AlertSchema);
Desired relationships diagram:
I'm building an API in NodeJS Express and MongoDB using Mongoose.
I created a Profile model that embeds the Experience schema.
Like:
{
"_id": "5e26ff6d5be84a3aeeb2f7bb",
"username": "some"
<fields of the profile>
"experience": [
{
"image": "",
"createdAt": "2020-01-21T13:41:01.873Z",
"updatedAt": "2020-01-21T13:41:01.873Z",
"_id": "5e26ff6d5be84a3aeeb2f7bc",
"title": "Senior Dev",
"role": "Dev",
"company": "ArosaDev",
"startDate": "2018-12-03T23:00:00.000Z",
"endDate": null,
"description": "",
"area": ""
}
],
"createdAt": "2020-01-21T13:41:01.874Z",
"updatedAt": "2020-01-21T13:41:01.874Z",
"__v": 0
}
The problem is I have to create an endpoint GET which gets for one profile all the experiences.
experienceRouter.get("/:username", async(req, res) => {
console.log(req.params.username);
const experiences = await Profiles.findOne({"username":req.params.username} ??? );
res.send(experiences);
});
I know I should select the embedded field experience and get back all the experiences but I don't know how should I do with Mongoose in my route.
I don't know what comes next after I select the username and how I can select the all experience for the username I'm requested.
I'm new to this and cannot find any good references explaining to me that for good.
I will appreciate an example of how a route like this should be done.
My model:
// Profile Schema model
// Embedded we have the Experience as []
const mongoose = require("mongoose");
const { isEmail } = require("validator");
const experienceSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
role: {
type: String,
required: true
},
company: {
type: String,
required: true
},
startDate: {
type: Date,
required: true
},
endDate: {
type: Date
},
description: {
type: String
},
area: {
type: String
},
createdAt: {
type: Date,
default: Date.now,
required: false
},
updatedAt: {
type: Date,
default: Date.now,
required: false
},
image: {
type: String,
required: false,
default: "https://via.placeholder.com/150"
}
})
const profileSchema = new mongoose.Schema({
firstname: {
type: String,
required: true
},
surname: {
type: String,
required: true
},
email: {
type: String,
trim: true,
lowercase: true,
unique: true,
required: true,
validate: {
validator: string => isEmail(string),
message: "Provided email is invalid"
}
},
bio: {
type: String,
required: true
},
title: {
type: String,
required: true
},
area: {
type: String,
required: true
},
imageUrl: {
type: String,
required: false,
default: "https://via.placeholder.com/150"
},
username: {
type: String,
required: true,
unique: true
},
experience: [
experienceSchema
],
createdAt: {
type: Date,
default: Date.now,
required: false
},
updatedAt: {
type: Date,
default: Date.now,
required: false
}
});
const collectionName = "profiles";
const Profile = mongoose.model(collectionName, profileSchema);
module.exports = Profile;
replace your code with below
You can mention your fields which you need in the second argument of function this is called projection as mentioned
So for including of fields use 1 and for excluding fields use 0
experienceRouter.get("/:username", async(req, res) => {
console.log(req.params.username);
const profile = await Profiles.findOne({"username":req.params.username},{experience:1 ,username:1, _id:0}).lean();
res.send(profile);
});
A list could have many items. I can easily retrieve all the items from a list but I'm having issues doing the opposite i.e retrieving all lists which contain an item
ItemSchema:
const ItemSchema = mongoose.Schema({
name: { type: String, required: true, min: 1 },
created_at: { type: Date, default: Date.now }
},{ toJSON: { virtuals: true }});
ListSchema:
const ListSchema = mongoose.Schema({
title: { type: String, required: true, max: 100 },
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
description: { type: String, required: true },
items: [{
type: mongoose.Schema.Types.Mixed, ref: 'Item', quantity: 'String'
}],
completed: { type: Boolean, default: false },
date: { type: Date, default: Date.now },
});
Document:
"items": [
{
"_id": "5c6d74a98a3f532b4c1d2a23",
"quantity": "7"
}
],
How I populate: Item.findById(id).populate('lists'); but it returns empty array.
Any suggestions?
Haven't used MongoDB in a long time, but it doesn't seem you have "lists" key in your Item Schema to populate in the first place. Maybe you should add one or query the lists that have that item.