How to receive object information from within an array in mongoose? - javascript

I have two models, a posts model and a category model where I have an array that stores posts by objectId
Category Model
const mongoose = require('mongoose');
const CategorySchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
},
color: {
type: String,
required: true,
},
posts: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post',
required: false,
}],
createdAt: {
type: Date,
default: Date.now,
}
},
{ timestamps: true }
);
module.exports = mongoose.model("Category", CategorySchema);
Post model
const mongoose = require('mongoose');
const PostSchema = new mongoose.Schema(
{
title: {
type: String,
required: true,
},
img: {
type: String,
required: true,
},
category: {
type: mongoose.Schema.Types.ObjectId,
ref: "Category",
},
desc: {
type: String,
required: false,
},
createdAt: {
type: Date,
default: Date.now,
}
},
{ timestamps: true }
);
module.exports = mongoose.model("Post", PostSchema);
So I created a get by id category controller
const Category = require('../../models/Category');
class FindCategory {
async find(req, res) {
const { id } = req.params;
try {
const category = await Category.findById(id);
return res.status(200).json(category);
} catch (err) {
return res.status(500).json(err);
}
}
}
module.exports = new FindCategory();
The problem is that when I make this request in my postman, it returns me for example
{
"_id": "63ac925d872445065a588f61",
"name": "Games",
"color": "#ff914d",
"posts": [
"63ac9cbccec4d9f35c4f4d1f"
],
"createdAt": "2022-12-28T19:00:45.847Z",
"updatedAt": "2022-12-28T19:49:47.119Z",
"__v": 0
}
But I would like to render the information of each post inside the "posts" array, something like that for example
{
"_id": "63ac925d872445065a588f61",
"name": "Games",
"color": "#ff914d",
"posts": [
"name": "red dead",
"img": "example.png",
"category": "games",
"desc": "test,
],
"createdAt": "2022-12-28T19:00:45.847Z",
"updatedAt": "2022-12-28T19:49:47.119Z",
"__v": 0
}

You should use the populate function provided by mongoose: https://mongoosejs.com/docs/populate.html#setting-populated-fields
Category.
findById(...).
populate('posts')

Related

Fetching Date Data from DB returns undefined or null

I am willing to pass the dates data from my MongoDB to the antD Date Picker as disabled dates but after fetching the data of date, I get either undefined or null.
What is the approach here?
Below can be seen that my DB has date as a string converted to ISOString, but on fetching it via MongoDB, I get either undefined or null.
My main logic was to pull this date info as response.data.data in frontend after page-loading and passing this data to a useState of setUnavailable date and then assigning this setState to the disableddate function to get a boolean.
Here is my code:
The data in my DB is:
`{
"_id": {
"$oid": "63690afb0623f513b3f22704"
},
"userId": "63614c627330d358d7ceff2d",
"name": "SampleName",
"title": "Doctor",
"type": "Lab Consultation",
"description": "Help Me Please",
"timings": "2022-11-07T06:00:00.000Z",
"date": "2022-11-15T00:00:00.000Z",
"status": "pending",
"createdAt": {
"$date": {
"$numberLong": "1667828475559"
}
},
"updatedAt": {
"$date": {
"$numberLong": "1667828475559"
}
},
"__v": 0
}
`
Schema:
`const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const consultationSchema = new mongoose.Schema(
{
createdBy: {
type: mongoose.ObjectId,
ref: "userModel",
},
userId: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
type: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
timings: {
type: String,
required: true,
},
date: {
type: String,
required: true,
},
status: {
type: String,
required: false,
default: "pending",
},
},
{
timestamps: true,
}
);
const consultationModel = mongoose.model("consultations", consultationSchema);
module.exports = consultationModel;
`
Router Get Data:
`router.get("/get-all-dates", authMiddleware, async (req, res) => {
try {
const consultation = await Consultation.find({
date: { $gte: "2022-11-11T00:00:00.000Z" },
});
const dates = consultation.date;
res.status(200).send({
message: "All Dates Fetched Successfully",
success: true,
data: dates,
});
console.log(dates);
} catch (error) {
console.log(error);
res.status(500).send({
message: "Fetching Dates Failed",
success: false,
error,
});
}
});
`
API Call in React:
`const getDateData = async () => {
try {
dispatch(showLoading());
const response = await axios.get("/api/user/get-all-dates", {
headers: {
Authorization: "Bearer " + localStorage.getItem("token"),
},
});
dispatch(hideLoading());
if (response.data.success) {
setUnavailableDates(response.data.data);
console.log(response.data.data);
console.log(setUnavailableDates);
}
} catch (error) {
dispatch(hideLoading());
}
};
useEffect(() => {
getDateData();
}, []);
const disabledDate = (current) => {
// How to pass the dates fecthed from DB here? Direct with setUnavailableDates?
return current && current < moment().endOf("day");
};
`
The output in network is this:
`{
"message": "All Dates Fetched Successfully",
"success": true
}`
The output in console is: undefined

Express - Find an object in MongoDB by id and save its data to array

I have two models: Meal and Ingredient. Here are the schemas:
const mealSchema = new Schema({
title: { type: String, required: true },
image: { type: String, required: false },
ingredients: [{
ingredient: { type: mongoose.Types.ObjectId, required: true, ref: 'Ingredient' },
amount: { type: Number, required: true }
}],
})
const ingredientSchema = new Schema({
name: { type: String, required: true },
unit: { type: String, required: true },
category: { type: String, required: false },
is_vege: { type: Boolean, required: true }
});
When I create the meal, I provide the ingredients as an array in POST request body.
const createMeal = async (req, res, next) => {
const { title, ingredients } = req.body;
// ingredientArray consists of objects with two keys:
// <String> ingredient - id of the associated ingredient in database
// <Number> amount - amount of the ingredient
ingredientArray = JSON.parse(ingredients)
const createdMeal = new Meal({
title,
image: req.file.path,
ingredients: ingredientArray
});
try {
await createdMeal.save();
} catch (err) {
const error = new HttpError('Error occurred, try again later', 500);
return next(error);
}
res.status(201).json(createdMeal);
}
This is the object that is created:
{
"_id": "62ea4531bd7e04fa740e2fee",
"title": "Spaghetti",
"image": "uploads\\images\\8da09ec6-3684-4af5-a513-f90155ddafd8.jpeg",
"ingredients": [
{
"ingredient": "62ea37251212c738a0ce9cee",
"amount": 100,
"_id": "62ea4531bd7e04fa740e2fef",
"id": "62ea4531bd7e04fa740e2fef"
},
{
"ingredient": "62ea371ab9392f3e0107c541",
"amount": 10,
"_id": "62ea4531bd7e04fa740e2ff0",
"id": "62ea4531bd7e04fa740e2ff0"
}
],
"__v": 0,
"id": "62ea4531bd7e04fa740e2fee"
}
I need my "ingredients" list to find the actual ingredient in the database and save its complete data so that the result would look something like this:
"ingredients": [
{
"ingredient": "62ea37251212c738a0ce9cee",
"amount": 100,
"name": "Pasta",
"unit": "g",
"category": "pastas",
"is_vege": true,
"_id": "62ea4531bd7e04fa740e2fef",
"id": "62ea4531bd7e04fa740e2fef"
},
{
"ingredient": "62ea371ab9392f3e0107c541",
"amount": 200,
"name": "Tomato",
"unit": "g",
"category": "vegetables",
"is_vege": true,
"_id": "62ea4531bd7e04fa740e2ff0",
"id": "62ea4531bd7e04fa740e2ff0"
}
]
EDIT: Ideally, the ingredient should be found BEFORE saving, because I want to run some calculations before the meal ends up in the database.
I figured it out. It took a single line of code:
let createdMeal = new Meal({
title,
image: req.file.path,
prep_time,
ingredients: ingredientArray
});
createdMeal = await createdMeal.populate({ path: 'ingredients.ingredient' });
Documentation:
https://mongoosejs.com/docs/populate.html

Ideal way to store multiple texts in mongoose with Node JS?

I've been building a mongoose schema for texts that will be displayed across different pages, and it has end point to POST data for updating the texts.
For example, I would like to store text messages that will be displayed/updated in About Page and Contact Page
What would be the preferred way of designing the text model?
1) Model that has all messages stored in one data object
In front-end, the parent component fetches all text messages with Texts.findOne() and trickles down to pages that need it
const textsSchema = new Schema(
{
aboutMessage1: {
type: String,
required: true
},
aboutMessage2: {
type: String,
required: true
},
contactMessage1: {
type: String
},
contactMessage2: {
type: String
}
},
{ timestamps: true }
);
2) Model that contains each message--so it will have multiple objects
In fron-end, each page uses Text.findById(textId) to retrieve each message
const textSchema = new Schema(
{
// Example: name = contactMessage
name: {
type: String
},
message: {
type: String
}
},
{ timestamps: true }
);
3) Multiple models that contains texts for each page
Similar to 1) approach, texts get fetched with Texts.findOne(), but performed in each page
const aboutTextsSchema = new Schema(
{
message1: {
type: String,
required: true
},
message2: {
type: String,
required: true
},
},
{ timestamps: true }
);
const contactTextsSchema = new Schema(
{
message1: {
type: String,
},
message2: {
type: String,
},
},
{ timestamps: true }
);
The most promising option is the second one. Because first and third options are static, and if in the future, you need to add a new page or or a new message to an existing page, it will require changes in the mongoose model, and deployment for API.
But I think, instead of creating a text schema, it would better to create a page schema for your scenario.
Here I embed messages inside the page schema.
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const pageSchema = new Schema(
{
page: {
type: String
},
messages: [
new Schema({
name: {
type: String
},
message: {
type: String
}
})
]
},
{ timestamps: true }
);
module.exports = mongoose.model("Page", pageSchema);
Now we can use this post route to create a page:
router.post("/pages", async (req, res) => {
const result = await Text.create(req.body);
res.send(result);
});
We can create a page and its messages using the previous post route.
Request Body:
{
"_id": "5e4937e9e2454a2c0c162890",
"page": "About",
"messages": [
{
"_id": "5e4937e9e2454a2c0c162892",
"name": "Abou1",
"message": "About1 message..."
},
{
"_id": "5e4937e9e2454a2c0c162891",
"name": "Abou2",
"message": "About2 message..."
}
],
"createdAt": "2020-02-16T12:39:05.154Z",
"updatedAt": "2020-02-16T12:39:05.154Z",
"__v": 0
}
Response:
{
"_id": "5e4937e9e2454a2c0c162890",
"page": "About",
"messages": [
{
"_id": "5e4937e9e2454a2c0c162892",
"name": "Abou1",
"message": "About1 message..."
},
{
"_id": "5e4937e9e2454a2c0c162891",
"name": "Abou2",
"message": "About2 message..."
}
],
"createdAt": "2020-02-16T12:39:05.154Z",
"updatedAt": "2020-02-16T12:39:05.154Z",
"__v": 0
}
If later we want to add a message to a page we can use the following put route.
router.put("/pages/:id", async (req, res) => {
const result = await Page.findByIdAndUpdate(
req.params.id,
{
$push: { messages: req.body }
},
{ new: true }
);
res.send(result);
});
Request Body:
{
"name": "Abou3",
"message": "About3 message..."
}
Response:
{
"_id": "5e4937e9e2454a2c0c162890",
"page": "About",
"messages": [
{
"_id": "5e4937e9e2454a2c0c162892",
"name": "Abou1",
"message": "About1 message..."
},
{
"_id": "5e4937e9e2454a2c0c162891",
"name": "Abou2",
"message": "About2 message..."
},
{
"_id": "5e493926f905ab3300106f94",
"name": "Abou3",
"message": "About3 message..."
}
],
"createdAt": "2020-02-16T12:39:05.154Z",
"updatedAt": "2020-02-16T12:44:22.763Z",
"__v": 0
}
When client needs a page's messages, all we need to do is retrieving the page by it's id or page name:
router.get("/pages/id/:id", async (req, res) => {
const result = await Page.findById(req.params.id);
res.send(result);
});
//or
router.get("/pages/name/:name", async (req, res) => {
const result = await Page.findOne({ page: req.params.name });
res.send(result);
});

Mongoose composite unique key index doesn't seem to work

I have added unique key index as suggested in the mongoose and mongoDB documentation but when I try to insert multiple question using insertMany it doesn't throw an error on duplicate items. Am I doing something wrong here?
Question.insertMany(questions);
const NotRequiredStringSchema = { type: String, required: false, default: '' };
const Question = new Schema({
"type": {
type: String,
trim: true,
enum: Object.values(questionTypes),
required: 'Question type is required'
},
"text": {
type: String,
required: true
},
"desc": NotRequiredStringSchema,
"options": [{
"id": ObjectId,
"name": NotRequiredStringSchema,
"helperText": NotRequiredStringSchema,
"icon_key": NotRequiredStringSchema,
"icon_url": NotRequiredStringSchema,
"icon_svg": NotRequiredStringSchema
}]
});
Question.index({
type: 1,
text: 1
}, {
unique: true
});
const model = mongoose.model('Question', Question);
module.exports = model;

Mongoose populate does not populate array

I have struggled with the mongoose.model.populate function for hours now. I have even tried directly copying and pasting several solutions without luck.
I have a User model which is supposed to contain an array of 'Dilemmas' which he/she has created, but I have been unable to populate it.
Here are the models as well as the implementation of populate().
User.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
// Create 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
},
dilemmas: [
{
type: Schema.Types.ObjectId,
ref: "Dilemma"
}
]
});
module.exports = User = mongoose.model("User", UserSchema, "users");
Dilemma.js
const mongoose = require("mongoose");
const slug = require("mongoose-slug-generator");
const Schema = mongoose.Schema;
mongoose.plugin(slug);
// Create Schema
const DilemmaSchema = new Schema({
creator: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
title: {
type: String
},
slug: {
type: String,
slug: "title"
},
red: {
type: String,
required: true
},
blue: {
type: String,
required: true
},
red_votes: {
type: Number,
default: 0,
required: true
},
blue_votes: {
type: Number,
default: 0,
required: true
},
likes: [
{
user: {
type: Schema.Types.ObjectId,
ref: "User"
}
}
],
comments: [
{
user: {
type: Schema.Types.ObjectId,
ref: "User"
},
text: {
type: String,
required: true
},
author: {
type: String
},
avatar: {
type: String
},
date: {
type: Date,
default: Date.now
}
}
],
date: {
type: Date,
default: Date.now
}
});
module.exports = Dilemma = mongoose.model("Dilemma", DilemmaSchema, "dilemmas");
Routes.js
// #route GET api/users/profile
// #desc Gets logged in user's profile
// #access Private
router.get(
"/profile",
passport.authenticate("jwt", { session: false }),
(req, res) => {
User.find({ username: req.user.username })
.populate("dilemmas")
.then(user => {
if (!user) {
errors.nouser = "There is no user";
return res.status(404).json(errors);
}
res.json(user);
})
.catch(err => res.status(400).json(err));
}
);
JSON Response
[
{
"_id": "5b807beef770e7c7e6bf7ce0",
"dilemmas": [],
"username": "Jonas",
"email": "Mohrdevelopment#gmail.com",
"password": "$2a$10$QaqljS9x08YQ9N9EuCBTpO114ZJUFuVxAV80xMzImNi8eW2frPg0C",
"date": "2018-08-24T21:43:10.411Z",
"__v": 0
}
]
JSON Dilemmas response
[
{
"red_votes": 0,
"blue_votes": 0,
"_id": "5b80975f6e47fecba621f295",
"user": "5b807beef770e7c7e6bf7ce0",
"title": "Am i the real author asdsdasd?",
"red": "This is the red dilemma",
"blue": "This is the blue dilemma",
"likes": [],
"comments": [],
"date": "2018-08-24T23:40:15.381Z",
"slug": "am-i-the-real-author-asdsdasd",
"__v": 0
},
{
"red_votes": 0,
"blue_votes": 0,
"_id": "5b808e789bc36bcae8c6c3ad",
"creator": "5b807beef770e7c7e6bf7ce0",
"title": "Am i the real author?",
"red": "This is the red dilemma",
"blue": "This is the blue dilemma",
"likes": [],
"comments": [],
"date": "2018-08-24T23:02:16.565Z",
"slug": "am-i-the-real-author",
"__v": 0
}
]
JSON Users response
{
"_id": {
"$oid": "5b807beef770e7c7e6bf7ce0"
},
"dilemmas": [],
"username": "Jonas",
"email": "Mohrdevelopment#gmail.com",
"password": "$2a$10$QaqljS9x08YQ9N9EuCBTpO114ZJUFuVxAV80xMzImNi8eW2frPg0C",
"date": {
"$date": "2018-08-24T21:43:10.411Z"
},
"__v": 0
}
I just encountered a similar issue myself. Populating a ref worked, but populating an array of refs did not. I was able to get the array populate to work by explicitly specifying the model name in the populate call, e.g.:
User.find({ ... }).populate({
path: 'dilemmas',
model: 'Dilemma',
});
I don't know why this makes a difference, when the name of the referenced model is already specified in the schema.
Have you tried this?
User.find({ username: req.user.username })
.populate("dilemmas")
.exec() // <-- add exec() to perform the search
.then(user => {
...
})
Did you check the documentation here?
https://mongoosejs.com/docs/populate.html#refs-to-children
It shows a similar setup (with Authors and Stories.) It mentions 'pushing' stories to be able to use a find / populate combo.

Categories

Resources