Trying to work out mongoose relationship coding for REST API - node.js

This is a stupid question but I have tried to wrap my head around this via Google, code snippits, tutorials, and all of them lead me to examples in which the models are too shallow for the coding I want to do.
I have an app I want to develop where data is in the form of parents and children:-
- Organisation
- Projects that belong to those organisations
- Releases that belong to those projects
and so on, but I don't fully understand how I can write a route in express that follows said hierachy and I come from an SQL relational background. Do I use cookies, or part of the route? I know how to set up the model, from what I understand, using:
var organisationSchema = ({
name: String,
email: String,
description: String,
users: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}]
});
for Organisation and
var projectSchema = ({
name: String,
description: String,
users: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}]
}
for project but then how do I set up my post route to add the project to the correct organisation
router.route('/api/project')
.post(function(req, res){
project = new Project();
project.name = req.body.name;
project.organisation = req.body.organisation;
if (err)
res.send(err);
})
project.save(function(err){
if (err)
res.send(err);
res.json({ message: 'Project ' + project.name + ' created.'});
})
})
Do I need a cookie to populate the organisation in this example?

If your projects belong to organizations, you'll either want to include an array of objectIds in your organization schema which will contain project IDs or a field in your project schema that will contain the relevant organization ID.
You can send the proper organization either in the body, as you are, or in the URL parameters.
For example, something similar to this:
router.route('/api/:organizationId/project')
.post(function(req, res, next) {
var project = new Project({
name: req.body.name,
organization: req.params.organizationId
});
if(err) { return next(err); }
project.save(function(err, savedProject) {
if(err) { return next(err); }
return res.status(200).json({ message: "Project " + savedProject.name + " created." });
}
}
If you pay attention to the communication/messaging model and routes in this repository, it might help: https://github.com/joshuaslate/mern-starter/tree/master/server

Related

Mongoose schema for article

I'm building a news website, and I this mongoose schema:
let mongoose = require('mongoose');
let articleSchema = mongoose.Schema({
image1:{
type: String,
required: true
},
title:{
type: String,
required: true
},
author:{
type: String,
required: true
},
date:{
type: String,
required: true
},
updated:{
type: String,
default: 'not updated'
},
title_nd:{
type: String,
required: false
},
body:{
type: String,
required: true
},
comments: [commentsSchema],
likes:{ type:Number, default:0 }
});
let Article = module.exports = mongoose.model('Article', articleSchema);
And I want to add a form so users can add their comments.
The question is how do I create a new schema for comments and link it to article schema, and then if the user adds a comment the comment added to the database and then shows on the article comment section?
Modeling a separate schema for comment is not a good idea in my humble opinion, since it is a classic case of one to few mapping which is an ideal use case for embedding the document. To give you a basic idea about data modeling i am quoting here
You need to consider two factors:
Will the entities on the ā€œNā€ side of the One-to-N ever need to stand alone?
What is the cardinality of the relationship: is it one-to-few; one-to-many; or one-to-squillions?
Based on these factors, you can pick one of the three basic One-to-N schema designs:
Embed the N side if the cardinality is one-to-few and there is no need to access the embedded object outside the context of the parent object
Use an array of references to the N-side objects if the cardinality is one-to-many or if the N-side objects should stand alone for any reasons
Use a reference to the One-side in the N-side objects if the cardinality is one-to-squillions
Please refer to a very well written and articulated post 6 Rules of Thumb for MongoDB Schema Design: Part 1 from mongodb blogs.
Even after this if you think it is a good idea to link to another schema please refer to this SO question - Referencing another schema in Mongoose
so I found a solution for this:
// :id is all articles with all ids
router.post('/:id', function (req, res) {
let comment = {};
comment.body = req.body.body;
comment.user = req.user;
comment.date = new Date(Date.now()).toDateString();
// Express validator
req.checkBody('body').len(5, 100);
let errors = [];
errors = req.validationErrors();
if(errors) {
Article.findById(req.params.id, function (err, article) {
if(err)throw err;
req.flash('danger', 'Body minimum length is 5 and maximum 100!');
res.redirect('/articles/'+article.id);
});
} else {
Article.findById(req.params.id, function (err, article) {
if(err)throw err;
article.comments.push({'body':comment.body,'user':comment.user,'date':comment.date});
article.save(function (err) {
if (err) {
throw err;
}else {
req.flash('success', 'Comment added!');
res.redirect('/articles/'+article.id);
}
});
});
}
});
EDIT: code above in more readable form:
router.post('/:id', async (req, res) => {
let article = await Article.findById(req.params.id);
if (!article) res.status("403");
let articleUrl = "/articles/${article.id}";
let comment = {
body: req.body.body,
user: req.user,
date: new Date(Date.now()).toDateString();
};
if (commment.body.lengh >= 100 || comment.body.length <= 5) {
req.flash('danger', 'Body minimum length is 5 and maximum 100!');
return res.redirect(articleUrl);
}
articles.comments.push(comment);
await article.save();
req.flash('success', 'Comment added!');
res.redirect(articleUrl);
});

(Mongoose) How to get user._id from session in order to POST data including this user._id

I am new in Mongoose.
I'm developing a MEAN stack To do list with user authentification.
(In other words, a user can register login and create, get, update and delete the to do's).
It means 2 schemas: 'users' and 'tasks'
With a relationship one to many: a user can have many tasks, many tasks belongs to a user.
This is how it looks the 'tasks' Schema:
const TaskSchema = new Schema({
title:{
type: String,
required: true
},
owner:{
type: Schema.Types.ObjectId,
ref:'User'
}
});
In order to build the CRUD methods I will need the user._id as a 'owner' attribute, otherwhise any user could have access to the tasks list, create update or delete a task,
To get the user._id it I was thinking two options:
Angular2 at the front end would get the user._id from the localStorage of the browser where was stored previously to keep the user logged in.
const user = localStorage.getItem('user');
And then send it in the same object as I send the 'title' attribute.
I think this option is too insecure as anyone from the front-end could send any id.
Get the current user._id at the back-end from the sessions. (I would't know how to do it though). And include it in the new task object at the POST method, something like this:
.post('/task', function(req, res, next){ function(req, res, next){
var task = new Task({
title: req.body.title,
owner : req.user._id /// Does not do nothing
});
if(!task.title){
res.status(400);
res.json({
"error":"Bad Data"
});
} else{
task.save(task, function(err, task){
if(err){
res.send(err);
}
res.json(task);
});
}
});
Taking the second option (unless the former is better), how would you build the POST method?
Concretely, how can I get the current user._id from the session and include it the new Task object?
I look forward of receiving your feedback soon.
Thank you.
A bit different but:
User Model:
var UserSchema = new mongoose.Schema({
username: String,
password: String
});
Tasks Model:
var taskSchema = mongoose.schema({
text: String,
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
}
});
module.exports = mongoose.model("Task", taskSchema);
Create a task with post route:
var text = req.body.text;
var author = {
id: req.user._id,
username: req.user.username
};
var newTask = {text: text, author: author};
Task.create(newTask, function(err, addedTask){
// what you wanna do
});
Similarly with edit/update you can use a put route (edit) and delete route (method override for delete) with a 'checkTaskOwnership' middleware and then
Task.findByIdAndUpdate / Task.findByIdAndRemove
I think you should store user's _id in session. To store _id in the session use passport. It handles Authentication really well, and on successful authentication it stores users credentials in req.user. This req.user is present in all the requests. So for any Route, you can get the user's _id from req.user object. you wont need to send user's _id from the Frontend.
While saving Task use this:
var task = new Task({
title: req.body.title,
owner : req.user._id
});
task.save(function(err){...});
Read PassportJS docmentation to get more detailed information about Session and Authentication.

Issues with mongodb/nodejs/express/mongojs and findAndModify

Currently doing a online course to learn some Node.js, Express, and MongoDB.
In this course there is a api section where they teach you to do simple stuff, but i ran in to an issue, the course video shows him updating name of an item, and the api makes it possible to update more fields, his fields keep there value, my fields actually end up being null.
The code is
app.put('/products/:id', function(req, res){
db.products.findAndModify({query: {_id: mongojs.ObjectId(req.params.id)},
update:{$set:{
name: req.body.name,
category: req.body.category,
description: req.body.description
}},
new: true
}, function(err, doc){
if(err){
res.send(err);
} else {
console.log('Updating Product...');
res.json(doc);
}
});
});
Can any one explain to me how i avoid lets say category and description ending up being null if only the name is updated?
If req.body.category and req.body.description are undefined in your code:
update:{$set:{
name: req.body.name,
category: req.body.category,
description: req.body.description
}},
your fields will be set to null on the matching document.
See the mongodb set null in update and set field as empty for mongo object using mongoose

Creating and updating documents synchronously with mongoose

I'm wondering what the best approach would be to realize the following situation in Node.js and mongoose:
I'm having a collection with users and a collection with groups. I want users to be able to create new groups and add people to them (very similar to people creating a new whatsapp group). I use the following schemas for the user and group documents:
var userSchema = mongoose.Schema({
email: String,
hashed_password: String,
salt: String,
name: String,
groups: [{
_id: { type: Schema.Types.ObjectId, required: true, ref: 'groups' },
name: String
}]
});
var groupSchema= mongoose.Schema({
name: String,
creator: Schema.Types.ObjectId,
users: [{
_id: { type: Schema.Types.ObjectId, required: true, ref: 'users' },
rankInGroup: { type: Number, required: true }
}]
});
At this moment I have a function that takes the following arguments: the email address (groupCreator) of the user who is creating the group, the name of the new group (newGroupName), and the userids of the users (newGroupMembers) that need to be added to the group. This function first finds the users that need to be added to the group (using this method) and then adds the user ids of these users to the users array in the group document like this:
function(groupCreator, newGroupName, newGroupMembers , callback) {
userModel.findOne({
email: emailGroupCreator
}, function(err,creator){
//load document of user who creates the group into creator variable
var newGroup = new groupModel({
name: newGroupName,
creator: creator._id,
users: [{_id: creator._id, rank: 0}] //add group creator to the group
});
userModel.find({
email: { $in: newGroupMembers }
}, function(err,users){
//for now assume no error occurred fetching users
users.forEach(function(user) {
newGroup.users.push({_id: user._id, rank: 0}); //add user to group
user.groups.push({_id:newGroup._id,name:newGroup.name}); //add group to user
}
newGroup.save(function (err) {
//for now assume no error occurred
creator.groups.push({_id: newGroup._id, name: newGroup.name}); //add group to creator
creator.save(function (err) {
//for now assume no error occurred
/* save/update the user documents here by doing something like
newMembers.forEach(function(newMember) {
newMember.save(function (err) {
if(err){
callback(500, {success: false, response: "Group created but failure while adding group to user: "+newMember.email});
}
});
});
callback(200, {success: true, response: "Group created successfully!"});
*/
});
}
});
}
So I want this function to:
Find the user document of the group creator based on its email address stored in groupCreator
Create a new group (newGroup) and add the creator to its users array
Find all the users that need to be added to the group
Update the groups array in all the user documents with the groupid (newGroup._id) of the newly created group
Make a callback if all this is successfully executed
The problem here is that the updating of all the user documents of the users added to the group happens asynchronously, but I want to be sure that all the user documents are updated correctly before I return a success or failure callback. How can I update all the user documents before I continue with the rest of the code (maybe not using a foreach)? Is my initial approach of retrieving all the user documents good or are there better ways to do this?
So the bottom line question is; how can I save multiple user documents and continue with the rest of the code (send a callback to notify success or failure) after all the save actions are performed, or is there a way to save all the documents at once?
NB The reason why I want (some) of the same information in both the user and the group document is because I don't want to load all the group info for a user if he logs in, only the basic group information. See also this source under the section many-to-many relationships.
JohnnyHK pointed me in the right direction; async.each make it possible to iterate over the documents and update them one at a time. After that the rest of the code gets executed. This is how my code looks now:
async.each(groupMembersDocs, function (newMember, loopCallback) {
//console.log('Processing user ' + newMember.email + '\n');
userModel.update({
email: newMember.email
}, {
$push: { 'groups' : {_id: newGroup._id, name: newGroup.name} }
}, function (err, data) {
if(err){
console.log("error: "+err);
loopCallback('Failure.. :'+err);
} else{
newGroup.users.push({_id: newMember._id, rank: -1});
loopCallback();
}
});
}, function (err){
if(err){
console.log("error: "+err);
callback(500, {success: false, response: "Error while adding users to group"});
} else{
newGroup.save(function (err) {
callback(201, {success: true, response: "Group created successfully"});
});
}
})

Proper way to add a friend route in node.js and mongoose?

I'm planning to to create a route where a user could add another user as his/her friend, so that they could chat to each other once they are friends.
So basically once User A has sent a request to User B, User B will get a live notification about the request via socket.io
The problem right now is that, I couldn't come up with my own solution on how to implement the above scenario, from what I know, I should create two routes GET and POST
I'm using mongoose for database query, insert , update and delete
Here's my code
// GET route for getting the user's information -- Simple route
router.get('/users/:facebook_name', function(req, res) {
User.findOne(facebook_name, function(err, user) {
if (!user) {
res.json({message: "Couldn't find a user by that name"});
return;
}
res.json(user);
});
});
// POST route for adding a friend
router.post('/friendships/create/:facebook_name', ensureAuthenticated, function(req, res) {
// What should i put in this route to make the adding a friend feature works?
User.findOneAndUpdate(facebook_name, function(err, user) {
if (user) {
res.json({message: "You already send a friend request to that person"});
return;
}
// Send a live notification to the other user
socket.emit('sending request', {message: "added you as a friend"});
});
});
user Schema code -- Not really sure about this one either
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var UserSchema = new Schema({
friends: [{ type: Schema.Types.ObjectId, ref: 'User'}],
facebook: {
id: String,
token: String,
// email: String,
displayName: String,
photo: String
}
});
// Should I even create this schema?
var FriendsRequest = new Schema({
madeBy: [{ type: Schema.Types.ObjectId, ref: 'User'}],
})
module.exports = mongoose.model('User', UserSchema);
module.exports = mongoose.model('FriendsRequest', FriendsRequest);
I'm not entirely honest with you guys, in the POST route, i have no freaking idea on how to write the logic, because I'm really confuse right now, how the User B gonna get the live request notification? Should i create another route for that?
This is my problem when it comes to building slightly complex apps , i just couldn't come up with a good logic on how to do a certain feature even though it looks pretty easy. I've been stuck in this problem for almost 4 hours, browsing and reading the net, but I believe SO is the only place for me to find a clue on how to do something.
Thank you.
What you can do is create socket for each facebookName(if unique).
On Client Side:
socket.on('connection', function (data) {
socket.emit('setFacebookName', facebookName); });
}
Server saves each socket with facebookName:
socket.on('setFacebookName', function (facebookName) {
users[facebookName]=socket;
});
Now, when user sends chat request to that user in this request
// POST route for adding a friend
router.post('/friendships/create/:facebook_name', ensureAuthenticated, function(req, res) {
// What should i put in this route to make the adding a friend feature works?
User.findOneAndUpdate(facebook_name, function(err, user) {
if (user) {
res.json({message: "You already send a friend request to that person"});
return;
}
// Send a live notification to the other user
sendLiveNotification(facebook_name);
});
});
function sendLiveNotification(facebookName){
socket.on('send notification', function (facebookName) {
users[facebookName].emit('sending request', "has sent friend request");
});
}
You're trying to get a two step process, so you will need at least two calls where one is a request from the requester, and the other is the decision whether or not to allow that request from the requestee. You can handle your callback for the first function utilizing a Boolean where if it's a new request the user could be prompted with a popup on the client.
A good purpose of Mongoose is the extensions to the Schema that you can make, so here I'm adding two functions: one from the requester requesting requestee's friendship, and the other the decision of the requestee
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var UserSchema = new Schema({
friendsAccepted: [{ type: Schema.Types.ObjectId, ref: 'User'}],
friendsRequested: [{ type: Schema.Types.ObjectId, ref: 'User'}],
friendsPending: [{ type: Schema.Types.ObjectId, ref: 'User'}],
friendsRejected: [{ type: Schema.Types.ObjectId, ref: 'User'}],
facebook: {
id: String,
token: String,
// email: String,
displayName: String,
photo: String
}
});
UserSchema.statics.requesterInitiatedRequestForFriendship = function(requesterID, requesteeID, cb) {
mongoose.model('UserSchema').findOne({_id: requesterID}).exec(function(err, requester) {
if (err) return cb(err);
mongoose.model('UserSchema').findOne({_id: requesteeID}).exec(function(err, requestee) {
if (err) return cb(err);
if (requestee.friendsAccepted(requesterID) === -1 &&
requestee.friendsRequested(requesterID) === -1 &&
requestee.friendsPending(requesterID) === -1 &&
requestee.friendsRejected(requesterID) === -1) {
requestee.friendsPending.push(requesterID);
requester.friendsRequested.push(requesterID);
requestee.save();
requester.save();
cb(null, true);
} else {
cb(null, false);
};
});
});
};
UserSchema.statics.requesteeDecidedOnFriendship = function(requesterID, requesteeID, allowed, cb) {
mongoose.model('UserSchema').findOne({_id: requesterID}).exec(function(err, requester) {
if (err) return cb(err);
mongoose.model('UserSchema').findOne({_id: requesteeID}).exec(function(err, requestee) {
if (err) return cb(err);
if ((requestee.friendsAccepted(requesterID) === -1 &&
requestee.friendsRequested(requesterID) === -1 &&
requestee.friendsPending(requesterID) > -1 &&
requestee.friendsRejected(requesterID) === -1) &&
requester.friendsRequested(requesteeID) > -1) {
requestee.friendsPending.forEach(function(uid, idx) {
if (uid === requesterID) {
requestee.friendsPending.splice(idx, 1);
return;
};
});
requester.friendsRequested.forEach(function(uid, idx) {
if (uid === requesteeID) {
requester.friendsRequested.splice(idx, 1);
return;
};
});
if (allowed) {
requestee.friendsAccepted.push(requesterID);
requester.friendsAccepted.push(requesteeID);
} else {
requestee.friendsRejected.push(requesterID);
requester.friendsRejected.push(requesteeID);
}
requestee.save();
requester.save();
};
cb(null);
});
});
}
module.exports = mongoose.model('User', UserSchema);
So a couple things happening:
hasn't been tested
it's not DRY
it's limited without an additional Friendship Schema
With a Friendship Schema, you can define levels of rejection (e.g. "not at this time", etc.), you can more well flush out details and granular control for the changing behavior of friendships. In the above, you can see that once you're rejected, it's pretty fatalistic in that it's determined at no time you shall become friends! So to get more of that type of behavior, I'd definitely go with a Friendship Schema with it's statics and methods flushed out, as should be users.

Resources