these are the two models that I have.
user.js
const mongoose = require("mongoose");
const userSchema = new mongoose.Schema({
email: {
type: String,
},
passwordHashed: {
type: String,
},
role: {
type: String,
},
student: [{ type: mongoose.Schema.Types.ObjectId, ref: "Student" }],
});
const Users = mongoose.model("User", userSchema);
module.exports = Users;
student.js
const mongoose = require("mongoose");
const studentSchema = new mongoose.Schema({
email: {
type: String,
},
role: {
type: String,
},
});
const Students = mongoose.model("Student", studentSchema);
module.exports = Students;
My problem is I want to populate the data to user.js but when I am running this code:
const user = await Users.find().populate('student')
console.log(user)
It just returns me an empty array of the student. How can I fix this problem? Thank you in advance.
For any type population/join I all time use mongodb $lookup.
It's much powerful, flexible and covenant than using population.
According your userSchema, most probably you have store the _id of student document on userSchema's student array.
If I'm correct then you can follow this way:
await Users.aggregate([
{
"$lookup": {
"from": "students",
"localField": "student",
"foreignField": "_id",
"as": "studentList"
}
}
])
Though your student field of userSchema is objectId, then you don't further need to do anything, else you have to convert your localField as mongoose.Types.ObjectId(student)
here is an example: https://mongoplayground.net/p/fuw9Drld9M-
Related
I have a simple Mongo DB with two models: Campaign and Donation, where a Campaign is related to many Donations.
Using mongoose I can query for a specific Campaign using an aggregation with a lookup() and it returns all of the related Donations. But if I try to use a populate() statement, no Donations are returned. I cannot figure out why.
Campaign
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
require('models/Donation');
const CampaignSchema = new Schema({
name: {
type: String,
required: true
},
goal: {
type: Number,
required: true
},
donations: [{
type: Schema.Types.ObjectId,
ref: "Donation"
}]
},
{
collection: 'campaigns'
});
mongoose.model('Campaign', CampaignSchema);
Donation
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
require('models/Campaign');
const DonationSchema = new Schema({
campaign: {
type: Schema.Types.ObjectId,
ref: "Campaign",
required: true
},
amount: {
type: Number,
required: true
},
date: {
type: Date,
required: true,
default: Date.now
}
},
{
collection: 'donations'
});
mongoose.model('Donation', DonationSchema);
This code will return a single Campaign based on _id and all of the associated donations:
const filter = {_id: mongoose.Types.ObjectId(req.params.id)};
Campaign.aggregate().match(filter).lookup({
from: "donations",
localField: "_id",
foreignField: "campaign",
as: "donations"}).then((results) => {
console.log(results[0].donations); // HAS ALL OF THE RELATED RECORDS
});
This code returns an empty array for donations:
const filter = {_id: mongoose.Types.ObjectId(req.params.id)};
const campaign = await Campaign.findOne(filter).populate('donations').then((c) =>{
console.log(c.donations); // EMPTY
});
I am still on this issue and I have implemented so far what I have seen online, still not working. I have two models named: PostSchema and UserSchema. PostSchema and Userschema both have a field called 'username'. So, I have created action that enables the user to update their profile. The challenge I have now is that the update made by the user which is stored in the UserSchema does not reflect on the PostSchema. For instance, I would like the username on the PostSchema to be updated to the current username on the UserSchema was the user updates or change their name from the UserSchema. I am trying to use the ref and populate features in mongoose. Probably, I am not doing it the right way.
With the current codes I have now, the posts are not fetching. If I remove the populate(' username', 'username').exec() line, it will fetch but the post.username will not change to user.username coming from the UserSchoma model.
Here are my codes:
I am still new and still learning, kindly help me pass this stage.
UserSchema model
const mongoose = require("mongoose"); //import mongoose
const UserSchema = new mongoose.Schema({
username:{ //I want the username here to update to post model too
type: String,
required: true,
unique: true
},
email:{
type: String,
required: true,
unique: true
},
password:{
type: String,
required: true
},
profilePicture:{
type: String,
default: "",
},
}, {timestamps: true}
);
//exporting this schema
module.exports = mongoose.model("User", UserSchema);
PostSchema model
const mongoose = require("mongoose"); //import mongoose
const Schema = mongoose.Schema;
const PostSchema = new mongoose.Schema(
{
title:{
type: String,
required: true,
unique: true
},
description:{
type: String,
required: true,
},
postPhoto:{
type: String,
required:false,
},
username:{ //this should reference user.username
type: Schema.Types.ObjectId, ref: 'User',
required: true,
},
categories:{
type: Array,
required: false
},
}, {timestamps: true}
);
//exporting this schema
module.exports = mongoose.model("Post", PostSchema);
This is where I am getting the posts
//Get Post
router.get("/:id", async(req, res)=>{
try{
const post = await Post.findById(req.params.id);
populate(' username', 'username').exec()
res.status(200).json(post)
}catch(err){
res.status(500).json(err)
}
})
This is the client side code with React.js where I called the posts from the api
import { useLocation } from 'react-router';
export default function SinglePost() {
const location = useLocation()
const path = location.pathname.split("/")[2];
const [post, setPost] = useState({});
const [title, setTitle] = useState("")
const [description, setDescription] = useState("");
const [updateMode, setUpdateMode] = useState(false)
useEffect(() => {
const getPost = async () => {
try{
const response = await axios.get("/posts/"+path )
setPost(response.data);
setTitle(response.data.title);
setDescription(response.data.description);
setPostUser(response.data.username)
}catch(err){
}
};
return getPost()
}, [path]);
use $lookup and aggregation
aggregate object is like this
Post.aggregate([
{ $match: { _id: req.params.id } },
{
$lookup: {
from: "User",
localField: "username",
foreignField: "_id",
as: "username",
},
},
]).exec()
I want to get all the posts with their author details from user model. I am using mongoDB lookup. But getting an empty array. I am matching author.uid from post to _id of user.
I want to get all the posts with their author details from user model. I am using mongoDB lookup. But getting an empty array. I am matching author.uid from post to _id of user.
//Post Model
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const postSchema = new Schema({
category : {
type: String
},
content: {
type: String
},
caption: {
type: String
},
tags: [{
type: String
}],
createdAt: {
type: Number,
required: true
},
author: {
uid:{
type: String,
required: true
},
name:{
type: String
}
},
likes:[{
type:String
}],
comments:[{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment"
}]
});
module.exports = mongoose.model('Post', postSchema);
//User Model
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
_id: {
type: String,
required: true
},
name:{
type: String,
required: true
},
avatar:{
type:String
},
bio:{
type: String
},
followers:[
{
type: String
}
],
followings:[
{
type: String
}
],
posts:[{
type: mongoose.Schema.Types.ObjectId,
ref: "Post"
}]
});
module.exports = mongoose.model('User', userSchema);
//Node js
const express = require('express');
const router = express.Router();
const Post = require('../../models/Post');
const User = require('../../models/user');
router.get('/', (req, res) => {
Post.aggregate([
{
$lookup:
{
from: 'User',
localField: "author.uid",
foreignField: "_id",
as: "creator"
}
}
]).exec((err, result) => {
if (err) {
console.log("error" ,err)
}
if (result) {
console.log(JSON.stringify(result));
}
});
});
//Output
{"_id":"5b9c7f30d",
"author": {"uid":"y08RxtsHe","name":"Sujoy Saha"},
"tags": ["#lo"],
"likes":[], "comments[],
"category":"image","content":"jsdnvs","caption":"standing
\n#lol","createdAt":1536982759517,"__v":0,"creator":[]}
You can see, i am getting empty creator array. Please help me out.
mongoose.js pluralizes (adds 's' after your model name) when it creates a collection in MongoDb.
Can you try with from: 'users' in your $lookup clause?
It's possible that i'm just burned out, but I have the following models:
user
const mongoose = require('mongoose');
const validate = require('mongoose-validator');
const Post = require('./post');
let UserSchema = mongoose.Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
email: {
type: String, required: true, lowercase: true, trim: true, unique: true, index: true,
validate: [validate({ validator: 'isEmail', message: 'Invalid Email!' })]
},
posts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post' }]
})
module.exports = mongoose.model('User', UserSchema);
posts
const _ = require('lodash');
const mongoose = require('mongoose');
const User = require('./user');
let PostSchema = mongoose.Schema({
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
title: { type: String, required: true },
body: { type: String, require: true }
})
PostSchema.post('save', async function (next) {
await User.update({ _id: this.user }, { $push: { posts: this._id } })
return next();
})
module.exports = mongoose.model('Post', PostSchema);
When trying to add a new post, the post save hook runs, but I get the error User.update is not a function (same goes for findOneAndUpdate, findOne, etc).
I can call user.update from the rest of the app without issues, so not sure whats happening here. Both models are in the same directory.
What you missed is that post middleware has the first argument as the "document" and not the next handler:
user.js
const { Schema } = mongoose = require('mongoose');
const userSchema = new Schema({
firstName: String,
lastName: String,
posts: [{ type: Schema.Types.ObjectId, ref: 'Post' }]
});
post.js
const { Schema } = mongoose = require('mongoose');
const User = require('./user');
const postSchema = new Schema({
user: { type: Schema.Types.ObjectId, ref: 'User' },
title: String,
body: String
});
// note that first argument is the "document" as in "post" once it was created
postSchema.post('save', async function(doc, next) {
await User.update({ _id: doc.user._id },{ $push: { posts: doc._id } });
next();
});
index.js
const { Schema } = mongoose = require('mongoose');
const User = require('./user');
const Post = require('./post');
const uri = 'mongodb://localhost/posttest';
mongoose.set('debug', true);
mongoose.Promise = global.Promise;
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri);
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
let user = await User.create({ firstName: 'Ted', lastName: 'Logan' });
let post = new Post({ user: user._id, title: 'Hi', body: 'Whoa!' });
post = await post.save();
mongoose.disconnect();
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
Returns:
Mongoose: users.remove({}, {})
Mongoose: posts.remove({}, {})
Mongoose: users.insertOne({ posts: [], _id: ObjectId("5b0217001b5a55208150cc9b"), firstName: 'Ted', lastName: 'Logan', __v: 0 })
Mongoose: posts.insertOne({ _id: ObjectId("5b0217001b5a55208150cc9c"), user: ObjectId("5b0217001b5a55208150cc9b"), title: 'Hi', body: 'Whoa!', __v: 0 })
Mongoose: users.update({ _id: ObjectId("5b0217001b5a55208150cc9b") }, { '$push': { posts: ObjectId("5b0217001b5a55208150cc9c") } }, {})
Showing that the update fires with the correct detail.
In good design you really should avoid this and simply drop the posts array from the User model. You can always either use a virtual instead:
userSchema.virtual('posts', {
ref: 'Post',
localField: '_id',
foreignField: 'user'
})
Or just get the data via $lookup:
User.aggregate([
{ "$match": { "_id": userId } }
{ "$lookup": {
"from": Post.collection.name,
"localField": "_id",
"foreignField": "user",
"as": "posts"
}}
])
Storing and maintaining arrays of related ObjectId values "on the parent" is kind of an "anti-pattern" and leads to unnecessary overhead such as writing in two places where you only need "one".
Also in general you should be opting for embedding "first", and only considering "referencing" if and when the usage pattern of the application actually demands it. Simply copying the same patterns of an RDBMS with a database engine that was not designed for that is not the best way to utilize it.
I want to get query of the mongoose in nodejs application as describe below out put.
user.js, comment.js and post.js are the model files I used.
user.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var userSchema = new Schema({
nick_name:{type:String},
email: {
type: String,
trim: true,
required: '{PATH} is required!',
index: true,
},
},{ collection: 'user'});
var User = mongoose.model('User', userSchema);
module.exports = User;
comment.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var commentSchema = new Schema({
comment: type:String,
user_id:{
type:Schema.Types.ObjectId, ref:'User'
},
is_active :1
},{ collection: 'comment'});
post.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var postSchema = new Schema({
post: type:String,
user_id:{
type:Schema.Types.ObjectId, ref:'User'
},
is_active :1
},{ collection: 'post'});
wants to get out put as follows:
{
"nick_name":"prakash",
"email":"prakash#mailinator.com",
"comments":[
{
"comment":"this is a comment text1",
"is_active":1,
},
{
"comment":"this is a comment text2",
"is_active":1,
}
],
"posts":[
{
"post":"this is a post text1",
"is_active":1,
},
{
"post":"this is a post text2",
"is_active":1,
},
{
"post":"this is a post text3",
"is_active":1,
},
]
}
dependencies
"express" => "version": "4.7.4",
"mongoose" => "version": "4.4.5",
"mongodb" => "version": "2.4.9",
"OS" => "ubuntu 14.04 lts 32bit",
if query is not possible ,please suggests me a proper mongoose plugn.
but I don't want to any changes in user.js file and its userSchema object.
There are no 'joins' in Mongo. But what you would do is change your User Schema to store the ObjectId's of the Comment and Post documents in an array of your User. Then use 'populate' when you need the data with the user.
const userSchema = new Schema({
nick_name:{type:String},
email: {
type: String,
trim: true,
required: '{PATH} is required!',
index: true,
},
comments: [{ type: Schema.Types.ObjectId, ref:'Comment' }],
posts: [{ type: Schema.Types.ObjectId, ref:'Post' }]
}, {timestamps: true});
mongoose.model('User', userSchema);
Your query would then look something like this:
User.find()
.populate('comments posts') // multiple path names in one requires mongoose >= 3.6
.exec(function(err, usersDocuments) {
// handle err
// usersDocuments formatted as desired
});
Mongoose populate docs
It is possible .you should use aggregation.
it should work.
Initiate the variable
var mongoose = require('mongoose');
var userCollection = require('./user');//import user model file
var resources = {
nick_name: "$nick_name",
email: "$email"};
userCollection.aggregate([{
$group: resources
}, {
$lookup: {
from: "Comments", // collection to join
localField: "_id",//field from the input documents
foreignField: "user_id",//field from the documents of the "from" collection
as: "comments"// output array field
}
}, {
$lookup: {
from: "Post", // from collection name
localField: "_id",
foreignField: "user_id",
as: "posts"
}
}],function (error, data) {
return res.json(data);
//handle error case also
});
Of course it is possible, you just have to use populate, let me tell you how:
Import your schemas
var mongoose = require('mongoose');
var userSch = require('userSchema');
var postSch = require('postSchema');
var commSch = require('commentSchema');
Init all the necessary vars
var userModel = mongoose.model('User', userSch);
var postModel = mongoose.model('Post', postSch);
var commModel = mongoose.model('Comment', commSch);
And now, do the query
postModel.find({}).populate('User')
.exec(function (error, result) {
return callback(null, null);
});
commModel.find({}).populate('User')
.exec(function (error, result) {
return callback(null, null);
});
This way you get the user inside of your comment and your post, to get the post and comments inside of your user, you have to do 3 queries, one for the user, one for the comments and one for the post, and mix all together
you can consider using populate virtual with both comments and posts like docs (https://mongoosejs.com/docs/populate.html#populate-virtuals) in model User. And using like this:
User.find({filter}).populates('virtualComments').populate('virtualPosts')
Adding on to the answers above: What if we only want a few specific fields returned for the populated documents? This can be accomplished by passing the usual field name syntax as the second argument to the populate method:
Story.
findOne({ title: /casino royale/i }).
populate('author', 'name'). // only return the Persons name
exec(function (err, story) {
if (err) return handleError(err);
console.log('The author is %s', story.author.name);
// prints "The author is Ian Fleming"
console.log('The authors age is %s', story.author.age);
// prints "The authors age is null"
});
Reference: https://mongoosejs.com/docs/populate.html#field-selection
Use aggregate to do your work in a single query which is almost like a join.
var mongoose = require('mongoose');
var userModel = require('./user');//import user model file
let result = await userModel.aggregate([
{
$match: {
user: mongoose.Types.ObjectId(req.body.user_id),//pass the user id
}
},
{
$lookup: {
from: "comments",//your schema name from mongoDB
localField: "_id", //user_id from user(main) model
foreignField: "user_id",//user_id from user(sub) model
pipeline: [
{
$project:{ //use to select the fileds you want to select
comment:1, //:1 will select the field
is_active :1,
_id:0,//:0 will not select the field
}
}
],
as: "comments",//result var name
}
},
{
$lookup: {
from: "post",//your schema name from mongoDB
localField: "_id", //user_id from user(main) model
foreignField: "user_id",//user_id from user(sub) model
pipeline: [
{
$project:{//use to select the fileds you want to select
post:1,//:1 will select the field
is_active :1,
_id:0,//:0 will not select the field
}
}
],
as: "posts",//result var name
}
},
{
$project:{//use to select the fileds you want to select
nick_name:1,//:1 will select the field
email:1,
_id:0,//:0 will not select the field
comments:1,
posts:1
}
}
])