I am trying to build a kanban as a project for my portfolio with the mern stack with a rest api. I am having issues querying the models with deeply nested objects in it. My idea is to have the lists be referenced in the project, the task be referenced in the list and the user be referenced in the task and the project as a contributor. My issue is getting the information of the task (title, etc.) through populating the project model with mongoose, or MongoDB for that matter. How should I approach this? Is it even possible to do with MongoDB? I see a lot of people doing this with a sql database. The four model schemas are as follows:
const projectSchema = new Schema({
title: { type: String, required: true, max: 32, trim: true },
lists: [{ type: Schema.Types.ObjectId, ref: 'List' }],
contributors: [{ type: Schema.Types.ObjectId, ref: 'User' }],
});
const listSchema = new Schema({
title: { type: String, required: true, max: 32, trim: true },
tasks: [{ type: Schema.Types.ObjectId, ref: 'Task' }],
});
const taskSchema = new Schema({
title: { type: String, required: true, max: 38, trim: true },
text: { type: String, max: 255, trim: true },
assignee: { type: Schema.Types.ObjectId, ref: 'User' },
});
const userSchema = new Schema(
Name: { type: String, required: true, trim: true },
projects: [{ type: Schema.Types.ObjectId, ref: 'Project' }],
tasks: [{ type: Schema.Types.ObjectId, ref: 'Task' }],
);
The issue I have is displaying the tasks in the list. When I populate the list with the project call, I get the ObjectId of the task, but would like to get the task title, which as it seems is not possible by applying the .populate call again. I have the following api controller/route:
router.get('/:projectId', async (req, res) => {
try {
const project = await Project.findOne({
_id: req.params.projectId,
}).populate('lists', 'title tasks');
res.json(project);
} catch (error) {
if (error) {
console.error(error.message);
res.status(500).send('Server Error');
}
}
});
How would I approach getting the task title and later on the user name for all the tasks in the list referencing a project?
So I found a solution to this. Not sure if this is the most efficient method to solve this. If anyone can provide a better solution, please post it on here.
I found the mongoose documents explains quite well how to populate over multiple levels.
https://mongoosejs.com/docs/populate.html#deep-populate
Now the solution for the answer looks as follows
router.get('/:projectId', async (req, res) => {
try {
const project = await Project.findOne({
_id: req.params.projectId,
}).populate({ path: 'lists', populate: { path: 'tasks' } });
res.json(project);
} catch (error) {
if (error) {
console.error(error.message);
res.status(500).send('Server Error');
}
}
});
Related
I have 4 level nested schema:
Framework has Domain referenced to it, Domain has control referenced to it, and Control has SubControl referenced to it.
Now I have been searching for a while and I keep getting confused.
First question: is it possible to post all the data from the framework it self?
Second question: I have used referencing with ID approach, should I switch to using subDocuments?
Framework Schema:
const FrameworkSchema = new Schema({
name: {
type: String,
trim: true
},
description: {
type: String,
trim: true
},
domain: [{
domain: {type: Mongoose.Schema.Types.ObjectId, ref: 'Domain'}
}],
updated: Date,
created: {
type: Date,
default: Date.now
}
});
module.exports = Mongoose.model('Framework', FrameworkSchema);
Domain Schema:
const DomainSchema = new Schema({
_id: {
type: Schema.ObjectId,
auto: true
},
domainNo: {
type: String,
trim: true
},
domainName: {
type: String,
trim: true
},
domainDescription: {
type: String,
trim: true
},
framework: {
type: Mongoose.Schema.Types.ObjectId,
ref: 'Framework'
},
control: [{
control: {type: Mongoose.Schema.Types.ObjectId, ref: 'Control'}
}],
updated: Date,
created: {
type: Date,
default: Date.now
}
});
module.exports = Mongoose.model('Domain', DomainSchema);
My control Schema:
const ControlSchema = new Schema({
_id: {
type: Schema.ObjectId,
auto: true
},
mainControl: {
type: String
},
subControl: [{
subControlNo: {type: Mongoose.Schema.Types.String, ref: 'SubControl'}
}],
controlDescription: {
type: String,
trim: true
},
updated: Date,
created: {
type: Date,
default: Date.now
}
});
module.exports = Mongoose.model('Control', ControlSchema);
My SubControl Schema
const SubControlSchema = new Schema({
_id: {
type: Schema.ObjectId,
auto: true
},
subControlNo: {
type: [String]
},
updated: Date,
created: {
type: Date,
default: Date.now
}
});
module.exports = Mongoose.model('SubControl', SubControlSchema);
Now I'm trying to post this nested documents from the framework api:
router.post(
'/add',
auth,
role.checkRole(role.ROLES.Admin), async (req, res) => {
try {
const subControl = new SubControl({...req.body});
const subControlDoc = await subControl.save();
const control = new Control({...req.body}); // take the control data
control.subControl.push(subControlDoc._id); // take the subControl and push the ID into the control
const controlDoc = await control.save();
//make the subcontrol pushed into control
// make control pushed in domain
const domain = new Domain({...req.body});
domain.control.push(controlDoc._id);
const domainDoc = await domain.save();
const framework = new Framework({...req.body});
framework.domain.push(domainDoc._id);
const frameworkDoc = await framework.save(); //save the framework + domain's ID
res.status(200).json({
success: true,
message: `Framework has been added successfully!`,
framework: frameworkDoc
});
} catch (error) {
res.status(400).json({
error
// error: 'Your request could not be processed. Please try again.'
});
}
}
);
Now I'm using push to push the data as an array, not sure if this the right approach, and Is it possible to post the all the data from the framework api?
Tried to post this from postman:
{
"name": "ISO780001",
"description": "frameworkDescription",
"domain":
[
{
"domainNo": "11",
"domainName": "domainName00",
"domainDescription": "domaindescu0",
"control": [{
"mainControl": "1-4",
"subControl": [{
"subControlNo": "1-4-1"
},
{
"subControlNo": "1-4-2"
}],
"controlDescription": "controlDescriptionTest"
},
{
"mainControl": "1-4",
"subControl": [{
"subControlNo": "1-4-1"
},
{
"subControlNo": "1-4-2"
}],
"controlDescription": "controlDescriptionTest"
}
]
},
{
"domainNo": "1-2",
"name": "domainName00",
"description": "domaindescu0",
"control": {
"mainControl": "1-4",
"subControl": [{
"subControlNo": "1-4-1"
},
{
"subControlNo": "1-4-2"
}],
"controlDescription": "controlDescriptionTest"
}
}
]
}
Only the id's of the domain, control, and subControl get saved in mongodb, is this the how it works can I post all the data from one model in this case the framework? or should I use embedded approach ?
What I will do in scenario where i have alot of references (mongoose name it ref by the way, which allows you to populate).
Example of a frameWork schema with domain reference.
const frameworkSchema = mongoose.Schema({
domains: [{type: mongoose.Schema.Types.ObjectId, ref: 'Domain'}],
})
const FrameworkModel = mongoose.model('Framework', frameworkSchema)
Domain above refers to a Domain model. We can create a domain model now.
const domainSchema = mongoose.Schema({
_id: { type: mongoose.Schema.Types.ObjectId } //this is the default
})
const DomainModel = mongoose.model('Domain', domainSchema);
Example Usage - We want to get all the domain information related to a specific schema.
const results = FrameworkModel.findOne({ _id: 'some framework id'}).populate('domains').lean({ virtuals: true });
The results will be something like
{
_id: 'some framework id',
name: 'name of framework',
domains: [
{
_id: 'id of domain 1',
name: 'example.com'
},
{
_id: 'id of domain 2',
name: 'example2.com'
}
]
}
You can also explore virtuals to see how you can maintain your framework, domains and other controls as separate collections, in order to easily reference to them. This is a better design than nesting multiple levels in a single document.
Unless where necessary, using separate collections has more benefits than using subdocuments. (easier to find documents and easier to update documents, and of course, more performant)
I have a model to create an article, and I want to save the id of the user that created that article also. I am doing this, but I have an error. I really appreciate any help!
let articleSchema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String,
},
markdown: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now
},
user: {type: Schema.ObjectId, ref: 'User'}
});
module.exports = mongoose.model('Article', articleSchema);
let userSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
unique: true,
required: true
},
password: {
type: String,
required: true
},
role: {
type: String,
default: 'USER_ROLE',
enum: validRoles
}
});
userSchema.plugin(uniqueValidator, {message: '{PATH} must be unique'});
module.exports = mongoose.model('User', userSchema);
And in the post method of the article I have this:
How can I access the user??
const article = new Article({
title: body.title,
description: body.description,
markdown: body.markdown,
user: req.user._id
});
I think that there's a problem in your article schema. Try to change
user: {type: Schema.ObjectId, ref: 'User'}
to
user: {type: Schema.Types.ObjectId, ref: 'User'}
Then, to display the author information try this in your routes file:
app.get('your-get-article-route', async (req, res) => {
try {
const articles = await Article.find().populate('user');
// other pieces of code if needed
res.status(200).json({ articles });
} catch (error) {
res.status(400).json({ message: 'an error occured!'});
)
If you're not familiar with async/await, you can do it with promises:
app.get('your-get-article-route', (req, res) => {
const articles = Article.find().populate('user').then((articles) => {
// other pieces of code if needed
res.status(200).json({ articles });
}).catch((error) => {
res.status(400).json({message: 'an error occured'}
);
)
I have the following Schema, and Base is the one-for-all collector of info:
const BaseSchema = mongoose.Schema({
creatorId: { type: mongoose.Schema.Types.ObjectId, required: true },
title: { type: String, required: true },
created: { type: Date, default: Date.now() },
users: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
messages: [{ type: mongoose.Schema.Types.ObjectId, ref: "Message" }]
});
And:
const BaseUserSchema = mongoose.Schema({
userId: { type: String },
baseId: { type: mongoose.Schema.Types.ObjectId },
created: { type: Date, default: Date.now() },
acceptedMembership: { type: Boolean, default: false },
isCreator: { type: Boolean, default: false }
});
(I have one for Message which looks about the same)
The latter one is referred to as User in const User = mongoose.model("User", UserSchema);
When I create a new Base I automatically add a user to a list within. In the DB i can see that the user does exist, but when I call the following the field does not populate:
Base.find({ creatorId: req.params.id })
.populate("users", "messages")
.exec()
.then(data => console.log(data));
I get the following from the console.log:
[ { created: 2018-09-05T03:41:45.416Z,
users: [],
messages: [],
_id: 5b8f508b2760c5329c13a9be,
creatorId: 5b86f7970cd98b2004969bf0,
title: 'testBase1',
__v: 1 } ]
When I first create the base via React front-end, and the base gets added to a list, I see that the users.length is 1, or the length of the automatically created user. When I refresh the page, however, the userlist is empty in the console.
Adding:
Forget to show how I populate the userlist upon creation:
router.post("/add", jwtAuth, (req, res) => {
Base.create({
creatorId: req.body.userId,
title: req.body.title
}).then(baseInfo => {
BaseUser.create({
userId: req.body.username,
baseId: baseInfo._id,
created: Date.now(),
acceptedMembership: true,
isCreator: true
})
.then(baseuser => {
baseInfo.users.push(baseuser);
return baseInfo.save();
})
.then(base => res.json(base.serialize()));
});
});
Answer:
.populate("users", "messages")
to begin with will return messages from users. It has to be two separate entries, like so:
.populate("users")
.populate("messages")
Then, in the model
users: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
refers to the wrong ref. It has to be
users: [{ type: mongoose.Schema.Types.ObjectId, ref: "BaseUser" }],
from
const BaseUser = mongoose.model("BaseUser", BaseUserSchema);
At last, I am not sure if this is needed, but I added a ref to each of the to-be-populated items:
baseId: { type: mongoose.Schema.Types.ObjectId, required: true, ref: "Base" },
baseId: { type: mongoose.Schema.Types.ObjectId, ref: "Base" },
Now it works like a charm, all without additional fetch
I'm trying to populate my Thread schemas GET response with all the comments related that that specific thread. I've specified a path within the Thread model to accept an array of Comments that I'll then populate when I request a thread, but it's continuing to be empty.
I'm not sure if I then need to push all the comments into the thread, but I think with the way I'm doing it, it's not required? I'm using Mongoose 4.4.19. I've followed along with the docs but still can't figure out where I've gone wrong.
Thread Schema:
const threadSchema = new Schema({
user: {
type: Schema.ObjectId,
ref: 'User',
required: true
},
title: {
type: String
},
content: {
type: String
},
category: {
type: String
},
comments: [{
type: Schema.ObjectId,
ref: 'Comment'
}]
}, {
timestamps: true
})
Comment Schema:
const commentSchema = new Schema({
user: {
type: Schema.ObjectId,
ref: 'User',
required: true
},
thread: {
type: Schema.ObjectId,
ref: 'Thread',
required: true
},
content: {
type: String
}
}, {
timestamps: true
})
Handles get requests:
export const index = ({ querymen: { query, select, cursor } }, res, next) =>
Thread.find(query, select, cursor)
.populate('user comments')
.then(threads => threads.map(thread => thread.view()))
.then(success(res))
.catch(next)
can someone please help me with population of this schema? I need to populate array of Staff by their userId.
var PlaceSchema = new Schema ({
name: { type: String, required: true, trim: true },
permalink: { type: String },
country: { type: String, required: true },
...long story :D...
staff: [staffSchema],
admins: [adminSchema],
masterPlace:{ type: Boolean },
images: []
});
var staffSchema = new Schema ({
userId: { type: Schema.Types.ObjectId, ref: 'Account' },
role: { type: Number }
});
var adminSchema = new Schema ({
userId: { type: Schema.Types.ObjectId, ref: 'Account'}
})
var Places = mongoose.model('Places', PlaceSchema);
I tried to use this query, but without success.
Places.findOne({'_id' : placeId}).populate('staff.userId').exec(function(err, doc){
console.log(doc);
});
Polpulation is intended as a method for "pulling in" information from the related models in the collection. So rather than specifying a related field "directly", instead reference the related fields so the document appears to have all of those sub-documents embedded in the response:
Places.findOne({'_id' : placeId}).populate('staff','_id')
.exec(function(err, doc){
console.log(doc);
});
The second argument just returns the field that you want. So it "filters" the response.
There is more information on populate in the documentation.