Query MongoDB w/Mongoose on an Array of objects - node.js

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

Related

How to create a new objectID in mongoose schema?

I want to generate two different objectIds in mongoose schema. One for departureLocation and one for arrivalLocation and want to reference it later.
I imported the object id from mongoose like this
let id = new mongoose.Types.ObjectId()
now I want to generate two different object ids like I said above
const routeSchema = new mongoose.Schema(
{
location: {
departureLocation: {
name: {
ref: "Location",
type: String,
required: true,
},
//want to create new object id here,
subLocation: [String],
_id: {
type: String,
},
},
arrivalLocation: {
name: {
ref: "Location",
type: String,
required: true,
},
//want to create new object id here,
subLocation: [String],
_id: {
type: String,
},
},
},
duration: {
type: Number,
required: true,
},
busId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Bus",
required: true,
},
date: {
type: String,
required: true,
},
},
{
timestamps: true,
}
);
MongoDB will automatically create _id for an object in a schema. You do not need to create one. In this schema, there will be 4 automatically generated _ids one for location, one for departureLocation, one for arrivalLocation, and one for the overall schema.

Mongoose: Populate an array of related values

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.

mongoose index don't create

I tried to create index from two fields in mongoose Schema but it didn't work
this two fields are the id of two other schema and i want to be unique
i get the ids from "mongoose.Schema.ObjectId"
this is my code:
const reviewSchema = new mongoose.Schema(
{
review: {
type: String,
required: [ true, 'Review can not be empty!' ],
},
rating: {
type: Number,
min: 1,
max: 5,
},
createdAt: {
type: Date,
default: Date.now,
},
tour: {
type: mongoose.Schema.ObjectId,
ref: 'Tour',
required: [ true, 'Review must belong to a tour.' ],
},
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: [ true, 'Review must belong to a user' ],
},
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
},
);
reviewSchema.index({ tour: 1, user: 1 }, { unique: true });
I found it was a bug from atlas ,I don't know why but it couldnt create index with options
I create a local data base and now it works

How to make reference beetween two schema mognose node.js

I try to make refrence in my MERN app beetwen two schema Movie and Seanse.
This is my Movie Schema:
const Movie = mongoose.Schema({
title: {
type: String,
require: true,
},
movieDescription: {
type: String,
require: true,
},
movieImgUrl: {
type: String,
require: true,
},
seanses: [
{
type: Schema.Types.ObjectId,
ref: "Seanse",
},
],
});
export default mongoose.model("Movie", Movie);
And this is my Seanse schema:
const Seanse = mongoose.Schema({
date: {
type: Date,
require: true,
},
hour: {
type: String,
require: true,
},
movie: {
type: Schema.Types.ObjectId,
ref: "Movie",
},
bookings: {
type: Array,
require: true,
},
});
export default mongoose.model("Seanse", Seanse);
But when i requests for movies in postman I see empty array, this is my code for geting all movies :
async findAll(req, res) {
const movies = await Movie.find().populate({
path: "seanses",
model: "Seanse",
});
return res.status(200).send({ data: movies });
},
This is what I recive at Postman :
{
"seanses": [],
"_id": "5f5bea1993df462974d63e66",
"title": "Hacker 5",
"movieDescription": "Lorem Ipusm",
"movieImgUrl": "https://m.media-amazon.com/images/M/MV5BZGJiZGMwMmUtMjdiZS00M2QzLWE3YjAtNTU2MjQ2ZDE3NDE1XkEyXkFqcGdeQXVyMTc5OTQwMzk#._V1_.jpg",
"__v": 0
}
Of course, I have previously created seanse in which I entered the id of an existing movie in the "movie" field
Mongoose always adds 's' to the end of model name, so you should rename Movie to Movies and Seanse to Seanses.

How to select all the experiences from a specific user profile in Node and Mongoose from embedded schema

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);
});

Resources