Node.js: mongoose .populate() is not showing data - javascript

I am trying to show data from database. I have 3 Schema, combined all of them in one. But, the combined data is not showing. I have attached my 3 Schema.
async-wait is working fine with try-catch, that seem clean to me. I have also tried to follow mongoose populate . Both returns the same result.
Need to mention: i am a newbie. So, don't have good idea about best practices to follow.
Book Schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const BookSchema = new Schema({
title: {
type : String,
required : [true, 'Book Title is Required'],
max : 100,
min : 5,
trim : true,
lowercase: true
},
author: {
type : Schema.Types.ObjectId,
ref : 'Author',
required: [true, 'Author is Required']
}
genre: [{
type: Schema.Types.ObjectId,
ref : 'Genre'
}]
}, { collection : 'book', timestamps: true });
BookSchema
.virtual('url')
.get(() => {
return 'book/' + this._id;
});
module.exports = mongoose.model('Book', BookSchema);
Author Schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const AuthorSchema = new Schema({
firstName: {
type : String,
required : [true, 'First Name is Required'],
max : 100,
min : 5,
trim : true,
lowercase: true
},
lastName: {
type : String,
required : [true, 'Last Name is Required'],
max : 100,
min : 5,
trim : true,
lowercase: true
}
}, { collection : 'author', timestamps: true });
AuthorSchema
.virtual('name')
.get(() => {
return this.firstName + this.lastName;
});
module.exports = mongoose.model('Author', AuthorSchema);
Genre Schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const GenreSchema = new Schema({
name: {
type : String,
required : [true, 'Genre Name is Required'],
max : 100,
min : 3,
trim : true,
lowercase: true
}
}, { collection : 'genre', timestamps: true });
module.exports = mongoose.model('Genre', GenreSchema);
Book Controller:
exports.bookList = async(req, res, next) => {
try {
const bookList = await Book.find({}).populate('author').exec();
res.render('./book/index', { title: 'Book List', bookList: bookList});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
index.pug:
ul
each book in bookList
li
a(href=book.url) #{book.title}
| (#{book.author.name})
else
li No book Has Been Listed Yet...!
URL is not appending the id
Author data is not showing
If i use .populate(), then. it is showing (Nan)
If i do not use populate, it is returning nothing
Expected output:
apes and angels(John)
The current output:
apes and angels(NaN)

Please try the following code. I think, it will work
exports.bookList = async(req, res, next) => {
try {
const bookList = await Book.find({}).populate('author').exec((error, list) => list);
res.render('./book/index', { title: 'Book List', bookList: bookList});
} catch (error) {
res.status(500).json({ message: error.message });
}
};

In my query, i had just needed to add callback like:
exports.bookList = async(req, res, next) => {
try {
const bookList = await Book.find({}).populate('author').exec((err, bookList) => {
if (err) return bookInstanceList;
// console.log(bookList);
res.render('./book/index', { title: 'Book List', bookList: bookList});
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
The main problem was with arrow function in Schema's. I have used arrow function to get the object. But, arrow function don't work with object. Here is the reference: medium

Related

I get this error: this.$__.validationError = new ValidationError(this)

I am new to learning web dev using node.js and express.js. When I test my code in Postman the server crashes and gives me this error this.$__.validationError = new ValidationError(this).
I am creating backend server connected to MongoDB and the mongoose package.
Routes
const express = require('express')
const router = express.Router()
const {
create
} = require('./../controllers/courseControllers')
const {verifyAdmin, verify} = require('./../auth')
router.post('/create', verifyAdmin, async (req, res) => {
// console.log(req.body)
try{
create(req.body).then(result => res.send(result))
}catch(err){
res.status(500).json(err)
}
})
Controllers
const Course = require('../models/Course');
//CREATE A COURSE
module.exports.create = async (reqBody) => {
const {courseName, description, price} = reqBody
let newCourse = new Course({
courseName: courseName,
description: description,
price: price
})
// console.log(newCourse)
return await newCourse.save().then((result, err) => result ? result : err)
}
Schema
const mongoose = require('mongoose');
const courseSchema = new mongoose.Schema({
courseName: {
type: String,
required: [true, `Course name is required`],
unique: true
},
description: {
type: String,
required: [true, `Course description is required`]
},
price: {
type: Number,
required: [true, `Price is required`]
},
isOffered: {
type: Boolean,
default: true
},
enrollees: [
{
userId: {
type: String,
required: [true, `userId is required`]
},
enrolledOn: {
type: Date,
default: new Date()
}
}
]
}, {timestamps: true})
module.exports = mongoose.model("Course", courseSchema);
Given the information you provided, i can say that you are missing the enrollees parameter on creation whitin the controller, as enrollees has been set to required, i hope its usefull for you, if not let me know.

Mongoose populate replacing ObjectIds with empty array

I've been trying to use the mongoose populate function to connect two models. I can save an object but when trying to retrieve using populate the ObjectIds are just replaced with an empty array.
Many questions seem to have been asked but none have a solution that worked for me
user.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var Route = require('./route')
var passportLocalMongoose = require('passport-local-mongoose');
const postSchema = new Schema ({
text: {
type: String,
default: '',
required: true
}
}, {
timestamps: true
});
const UserSchema = new Schema({
firstname: {
type: String
},
posts: [postSchema],
route: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Route'
}]
}, {
timestamps: true
});
UserSchema.plugin(passportLocalMongoose);
const User = mongoose.model('User', UserSchema);
module.exports = User;
route.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
const locationSchema = new Schema ({
id: {
type: Number,
default: 0,
required: true
},
address: {
type: String,
default: '',
required: true
},
lat: {
type: Number,
default: 0,
required: true
},
lng: {
type: Number,
default: 0,
required: true
}
},{
timestamps: true })
const routeSchema = new Schema ({
locations: [locationSchema],
description: {
journey1: {
type: String,
default: '',
required: false
},
journey2: {
type: String,
default: '',
required: false
},
journey3: {
type: String,
default: '',
required: false
},
journey4: {
type: String,
default: '',
required: false
}
}
}, {
timestamps: true
});
module.exports = mongoose.model('Route', routeSchema);
within REST POST end point
User.findOne({_id: req.user._id}, function(err,user) {
if(user) {
var routed = new Route();
routed.locations = req.body.locations;
routed.description = req.body.description;
user.route.push(routed);
user.save()
.then((user) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json')
res.json(user)
}, (err) => next(err))
} else {
console.log("errored")
err = new Error('User ' + req.body.username + ' not found');
err.status = 404;
return next(err);
}
})
within REST GET end point
User.findOne({_id: req.user._id})
.populate('route')
.then((user) => {
if(user){
console.log("user")
console.log(user)
console.log("routes")
console.log(user.route)
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json')
res.json({success: true, routes: user.route});
}
}, (err) => next(err))
.catch((err) => next(err));
If I remove populate I'll get something like
[
new ObjectId("61f053af7ba46267f4893f8f")
new ObjectId("61f053af7ba46267f4893f8f")
new ObjectId("61f053af7ba46267f4893f8f")
]
from the GET end point but adding it back in returns
[].
My understanding is that in 'new Route()' I'm creating a new Route Object with an Id that gets stored in the User model/document(?). Then when I call populate mongoose searches the Route document for those Ids and converts them to the objects I want. The only issue I could think of is that I'm not creating the Route objects correctly and so no object is being stored with that Id which is why an empty array is returned when I come to try swap Ids with Route objects.
Any ideas or are we all just stumbling in the dark ?
Not entirely sure this is the correct method but instead of instantiating a Route object as displayed I used the Route.create(...) method and then pushed that to the route array and now populate works as expected

Mongoose returning empty array

I have a db in Mongo with 2 collections, users and campaigns. For the former, all of my requests (get,post, patch, etc...) work correctly. However, I am having an issue with campaigns.
I can create a new campaign in postman but not 'get' the campaigns. THe request appears successful but returns an empty array.
I have the campaigns split into:
campaignController,
***Model,
***Routes,
and a handlerFactory to cover users and campaigns.
handlerFactory:
exports.getAll = Model =>
catchAsync(async (req, res, next) => {
// To allow for nested GET reviews on tour (hack)
let filter = {};
if (req.params.campaignId) filter = { campaign: req.params.campaignId };
const features = new APIFeatures(Model.find(filter), req.query)
.filter()
.sort()
.limitFields()
.paginate();
// const doc = await features.query.explain();
const doc = await features.query;
// SEND RESPONSE
console.log('-------', doc);
res.status(200).json({
status: 'success',
results: doc.length,
data: {
data: doc
}
});
});
Campaign Model:
const campaignSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Campaign name can not be empty!']
},
clientID: {
type: String,
},
creator_id: {
type: String,
},
budget: {
type: Number,
min: 100,
required: [true, 'Campaign name can not be empty!']
},
startStatus: {
type: String,
enum: ['preStart', 'isStarted', 'preEnd'],
default: 'preStart'
},
startDate: {
type: Date,
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {type: Date,
default: Date.now
},
isDeleted: {
type: Boolean,
// required: [true, 'Must be true or false!']
default: false
},
Priority: {
type: Boolean,
default: false,
},
location: {
type: String,
enum: ['Helsinki', 'Tallinn'],
default: 'Helsinki'
}
});
campaignSchema.pre('save', function(next) {
if (!this.isModified('createdAt') || this.isNew) return next();
this.updatedAt = Date.now() - 1000;
next();
});
campaignSchema.pre(/^find/, function(next) {
// this points to the current query
this.find({ isDeleted: { $ne: false } });
next();
});
const Campaign = mongoose.model('Campaign', campaignSchema);
module.exports = Campaign;
campaignController:
exports.getAllCampaigns = factory.getAll(Campaign);
exports.getCampaign = factory.getOne(Campaign);
exports.createCampaign = factory.createOne(Campaign);
exports.updateCampaign = factory.updateOne(Campaign);
exports.deleteCampaign = factory.deleteOne(Campaign);
exports.getMe = (req, res, next) => {
req.params.id = req.campaign.id;
next();
};
exports.deleteCurrentCampaign = catchAsync(async (req, res, next) => {
await User.findByIdAndUpdate(req.campaign.id, { active: false });
res.status(204).json({
status: 'success',
data: null
});
});
campaignRoutes:
const router = express.Router();
router
.route('/')
.get(campaignController.getAllCampaigns)
.post(
authController.protect,
authController.restrictTo('admin', 'super-admin'),
campaignController.createCampaign
);
router
.route('/:id')
.get(campaignController.getCampaign)
.patch(
authController.protect,
authController.restrictTo('admin', 'super-admin'),
campaignController.updateCampaign
)
.delete(
authController.protect,
authController.restrictTo('admin', 'super-admin'),
campaignController.deleteCampaign
);
module.exports = router;
Any idea where I am going wrong?
All code looks good but may be problem is,your collection not contain any records whose isDeleted=true.
because "find query middleware" in campaignModel is called before any find* query and it find all document whose isDeleted != false.

Node.js, MongoDB error - "message": "Schema hasn't been registered for model \"Category\".\nUse mongoose.model(name, schema)"

I'm in process of an app based on Node.js, MongoDB and Express. My goal is to get a fetch API system to work.
When using Postman to check out my status, the GET for "article.js" model file (in my localhost:3000/articles) shows the following error:
{
"error": {
"message": "Schema hasn't been registered for model \"Category\".\nUse mongoose.model(name, schema)",
"name": "MissingSchemaError"
}
}
This error disables the display of my articles or categories in Postman, as they are saved in my MongoDB project area at mongodb cloud.
The model file code "article.js" is the following:
const mongoose = require('mongoose');
const articleSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
title: { type: String, required: true },
description: { type: String, required: true },
content: { type: String, required: true },
categoryId: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'Category' }
});
module.exports = mongoose.model('Article', articleSchema);
This file connects with the controller named "articles.js", with the following relevant code:
const mongoose = require('mongoose');
const Article = require('../models/article');
const Category = require('../models/category');
module.exports = {
getAllArticles: (req, res) => {
Article.find().populate('categoryId', 'title').then((articles) => {
res.status(200).json({
articles
})
}).catch(error => {
res.status(500).json({
error
})
});
},
createArticle: (req, res) => {
const { title, description, content, categoryId } = req.body;
Category.findById(categoryId).then((category) => {
if (!category) {
return res.status(404).json({
message: 'Category not found'
})
}
const article = new Article({
_id: new mongoose.Types.ObjectId(),
title,
description,
content,
categoryId
});
return article.save();
}).then(() => {
res.status(200).json({
message: 'Created article'
})
}).catch(error => {
res.status(500).json({
error
})
});
},
}
The model file "category.js" code in the app looks like this:
const mongoose = require('mongoose');
const categorySchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
title: { type: String, required: true },
description: { type: String, required: true }
});
module.exports = mongoose.model('Category', categorySchema);
I looked up topics from the past here, such as this one - but it didn't solve my problem.
What should I do in order to fix my code?
Is it a syntax mistake or something else?
the code seems to be OK
I don't see here any particular error's

Mongoose & Express: How to properly Remove, Create & Store data that are reference

The first problem I am having is that whenever I try to delete the Comment, I also try to find the index of that specific comment inside post.comments as well as inside user.comments, it consistently returns -1, the reason why I am trying to find it, is so that I can splice it from the comments array that user and post do have.
The second problem I am having is that whenever I create a comment, I try to store it in the comments array that user and post have, but it stores it only as a string, although I think it is supposed to be stored as an object right?, So I can access it later by populating?
I have been struggling for days now being very frustrated why it does not work. Please help me!
Below will be my two routes, for deleting and creating comments, and my Schemas, Thank You for all the help!
Creating Comments
// #route POST api/posts/comment/:id
// #desc Comment on a post
// #access Private
router.post(
'/comment/:id',
[
auth,
[
check('text', 'Text is required')
.not()
.isEmpty()
]
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
const post = await Post.findById(req.params.id);
const user = await User.findById(req.user.id)
const newComment = {
text: req.body.text,
post: post._id,
user: req.user.id
};
const comment = new Comment(newComment);
post.comments.unshift(comment._id);
user.comments.unshift(comment._id)
console.log(user.comments);
console.log(post.comments);
console.log(comment)
await post.save();
await comment.save();
await user.save();
res.json(comment);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
}
);
Deleting comments
// #route DELETE api/posts/comment/:id/:comment_id
// #desc Delete comment
// #access Private
router.delete('/comment/:id/:comment_id', auth, async (req, res) => {
try {
const post = await Post.findById(req.params.id);
const user = await User.findById(req.user.id);
// Pull out comment by finding it through its id
const comment = await Comment.findById(req.params.comment_id);
// Make sure comment exists
if (!comment) {
return res.status(404).json({ msg: 'Post do not have this comment' });
}
// Check user
if (comment.user.toString() !== req.user.id) {
return res.status(401).json({ msg: 'User not authorized' });
}
// Get The value to be removed
const postCommentIndex = post.comments.findIndex(postComment => postComment === comment._id);
const userCommentIndex = user.comments.findIndex(userComment => userComment === comment._id);
console.log(`This is the post comment index ${postCommentIndex}`);
console.log(`This is the user comment index ${userCommentIndex}`);
post.comments.splice(postCommentIndex, 1);
user.comments.splice(userCommentIndex, 1);
// save user and post
await post.save();
await user.save();
await comment.remove();
// resend the comments that belongs to that post
res.json(post.comments);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
Schemas:
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
avatar: {
type: String
},
posts: [{type: mongoose.Schema.Types.ObjectId, ref: "Post"}],
comments: [{type: mongoose.Schema.Types.ObjectId, ref: "Comment"}],
date: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('User', UserSchema);
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const PostSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
text: {
type: String,
required: true
},
likes: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'User'
}
}
],
dislikes: [
{
user: {
type: Schema.Types.ObjectId,
ref: "User"
}
}
],
comments: [{type: Schema.Types.ObjectId, ref: "Comment"}],
date: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Post', PostSchema);
const mongoose = require("mongoose")
const Schema = mongoose.Schema;
const CommentSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
post: {
type: Schema.Types.ObjectId,
ref: "Post"
},
text: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
})
module.exports = mongoose.model("Comment", CommentSchema);
I think you need to redesign your schemas in a simpler way, there are too many references between the models, and this causes issues, for example you have 5 db access when you want to create a comment, and 6 db access when you want to delete a comment.
I would create the user schema like this removing the posts and comment references, but later when we want to access the posts from users, I set up virtual populate.
const UserSchema = new Schema(
{
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
avatar: {
type: String
},
date: {
type: Date,
default: Date.now
}
},
{
toJSON: { virtuals: true }
}
);
UserSchema.virtual("posts", {
ref: "Post",
localField: "_id",
foreignField: "user"
});
And in the posts schema, I removed the comments references.
(For simplicity I removed likes and dislikes fields.)
const PostSchema = new Schema(
{
user: {
type: Schema.Types.ObjectId,
ref: "User"
},
text: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
},
{
toJSON: { virtuals: true }
}
);
PostSchema.virtual("comments", {
ref: "Comment",
localField: "_id",
foreignField: "post"
});
Comment schema can stay as it is.
Now to add a comment to a post, we only need 2 db access, one for checking if post exists, and one for creating the post.
router.post(
"/comment/:id",
[
auth,
[
check("text", "Text is required")
.not()
.isEmpty()
]
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
const post = await Post.findById(req.params.id);
if (!post) {
return res.status(404).json({ msg: "Post not found" });
}
let comment = new Comment({
text: req.body.text,
post: req.params.id,
user: req.user.id
});
comment = await comment.save();
res.json(comment);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
}
);
Let's say we have these 2 users:
{
"_id" : ObjectId("5e216d74e7138b638cac040d"),
"name" : "user1"
}
{
"_id" : ObjectId("5e217192d204a26834d013e8"),
"name" : "user2"
}
User1 with _id:"5e216d74e7138b638cac040d" has this post.
{
"_id": "5e2170e7d204a26834d013e6",
"user": "5e216d74e7138b638cac040d",
"text": "Post 1",
"date": "2020-01-17T08:31:35.699Z",
"__v": 0,
"id": "5e2170e7d204a26834d013e6"
}
Let's say user2 with _id:"5e217192d204a26834d013e8" commented on this post two times like this:
{
"_id" : ObjectId("5e2172a4957c02689c9840d6"),
"text" : "User2 commented on user1 post1",
"post" : ObjectId("5e2170e7d204a26834d013e6"),
"user" : ObjectId("5e217192d204a26834d013e8"),
"date" : ISODate("2020-01-17T11:39:00.396+03:00"),
"__v" : 0
},
{
"_id": "5e21730d468bbb7ce8060ace",
"text": "User2 commented again on user1 post1",
"post": "5e2170e7d204a26834d013e6",
"user": "5e217192d204a26834d013e8",
"date": "2020-01-17T08:40:45.997Z",
"__v": 0
}
To remove a comment we can use the following route, as you see we decreased the db access from 6 to 3, and code is shorter and cleaner.
router.delete("/comment/:id/:comment_id", auth, async (req, res) => {
try {
const comment = await Comment.findById(req.params.comment_id);
if (!comment) {
return res.status(404).json({ msg: "Post do not have this comment" });
}
if (comment.user.toString() !== req.user.id) {
return res.status(401).json({ msg: "User not authorized" });
}
await comment.remove();
// resend the comments that belongs to that post
const postComments = await Comment.find({ post: req.params.id });
res.json(postComments);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
Now you may ask, how will access the posts from an user? Since we setup virtual populate in our user schema, we can populate the posts like this:
router.get("/users/:id/posts", async (req, res) => {
const result = await User.findById(req.params.id).populate("posts");
res.send(result);
});
You can try this code snipet :
Comment.deleteOne({
_id: comment.id
}, function (err) {
if (err) {
console.log(err);
return res.send(err.message);
}
res.send('success');
});

Categories

Resources