Updating a model instance - node.js

I want to understand what the correct way to update a model instance is, using mongoose, ie:
Having:
User = {
username: {
type: String,
indexed: true
},
email: {
type: String,
indexed: true
},
name: String,
.........
};
I'm sending the whole form through ajax to a controller.
So far, i know of two options:
app.put('/users/', function(req, res){
var id = ObjectId(req.body._id);
User.findOne({_id: id}, function(err, user){
user.name = req.body.name;
....
....
user.save();
});
});
or:
app.put('/users/', function(req, res){
var id = ObjectId(req.body._id);
delete req.body._id
User.update({_id: id}, req.body, function(err){
.....
};
});
Both ways have disadvantages:
In the first approach i have to map all properties one by one;
In the second approach i have to delete all properties that can't be changed;
there is a third possible approach that would make me send from client-side, only the changed properties, but i think that would be a big hassle to.
Is there a good, standardized way that i'm not seeing, to do this?
Thanks

This is the approach I typically use, it involves a small npm package called mongoose-mass-assignement:
https://github.com/bhelx/mongoose-mass-assignment
The docs are pretty self explanatory, easy to use. It basically does the delete for you. So you add protected:true to your model, and then it deletes those properties for you. I like this approach because it still allows me to use the req.body as is. You can use this for updates and inserts as well.

A variant of the first approach is to use underscore's extend method to apply all properties of the body to your model instance:
app.put('/users/', function(req, res){
var id = ObjectId(req.body._id);
User.findOne({_id: id}, function(err, user){
_.extend(user, req.body);
user.save();
});
});
However, be sure to delete any properties you don't want the user to be able to set from req.body first.

Related

Mongoose Updating Certain Fields of a Document

I'm trying to implement an update method for our API and I'm kinda new to the Node so I didn't know what would be the best practice to carry out the task of updating some fields of a document. Let me elaborate, we have a user model which keeps basic info of a user like name, age, sex, school, bio, birthday etc. Our update method should work as this, the request of the method includes the new values of the fields provided such as {bio:'newBio'} or {school:'newSchool', name:'newName'} I must update the provided fields with the provided data and leave the rest as they are. I was wondering what the best approach to the problem at hand would be. Thanks in advance
The easiest approach what i can think of is to use $Set to perform the update operations.
an example would be :
var updatedUsers= function(db, callback) {
db.collection('users').updateMany(
{ "_id": "value"},
{
$set: { bio: "new bio" }
}
,
function(err, results) {
console.log(results);
callback();
});
};
and invoke your above function as :
MongoClient.connect(url, function(err, db) {
assert.equal(null, err);
updatedUsers(db, function() {
db.close();
});
});

Url parameter in express and mongoose

Im building a basic web app with node/express and mongoose. How do i replace :id in express parameter to a string
For example
mongoose model
var UserSchema = new Schema({
name: String,
});
var User = mongoose.model('User', UserSchema);
Let say there is a user name "Mr robot king" in the User's collection right now
app.get('/:id', function(req, res, next) {
User.findOne({ _id: req.params.id }, function(err, user) {
res.render('home');
});
});
result of the url
localhost:8080/5asdasd43241asdasdasd
What i want is
localhost:8080/Mr-robot-king
In essence, that wouldn't be too difficult:
app.get('/:name', function(req, res, next) {
var name = req.params.name.replace(/-/g, ' ');
User.findOne({ name : name }, function(err, user) {
res.json(user);
});
});
Some considerations:
Since your example replaces spaces with hyphens, and the code above does the opposite, user names with actual hyphens in them will break this substitution scheme;
If user names are allowed to contain characters that have special meaning in URL's (like & or ?), when building the links, you should encode them. So the user name jill&bob will yield the link localhost:8080/jill%26bob
You probably want to add an index to the name field to make sure that queries are fast;
EDIT: to generate these links, for instance from an EJS template, you can use something like this:
go to page

Mongoose nested reference population

I'm having trouble understanding some of the concepts behind mongoose's populate methods. I had an embedded approach working first, although, as I feared a big overhead of data and out-of-sync documents going around I tried changing the paradigm to ref other documents.
My Schema is similar to the following (removed irrelevant properties):
var userSchema = mongoose.Schema({
name: {type: String, default:''},
favorites:{
users:[{type: Schema.Types.ObjectId, ref: this}],
places:[{type: Schema.Types.ObjectId, ref: PlaceSchema}]
}
});
module.exports = mongoose.model('User', userSchema);
Now I'm trying to get a User's favorites like this:
User.findOne({_id: currentUser}).exec(function(err, user){
console.log(user);
if (err)
throw err;
if(!user){
console.log("can't find user");
}else{ // user found
user.populate('favorites.users');
user.populate('favorites.places');
// do something to the 'user.favorites' object
}
});
Although this doesn't work as intended, as both user.favorites.users and user.favorites.places come up undefined.
I thought that I could populate as above, but apparently, that's not the case. From what I read, I must be missing something indicating (maybe) the model of the ref'ed document? This flow is very new to me and I'm kinda lost.
Is there anyway I can get an array of users and places by populating my query result as above? I tried populate chained with exec and it doesn't work either. Is there a better way to achieve this result?
EDIT: In case it's needed, in the DB, a User document shows up as:
{
"_id": "56c36b330fbc51ba19cc83ff",
"name": "John Doe",
"favorites": {
"places": [],
"users": [
"56b9d9a45f1ada8e0c0dee27"
]
}
}
EDIT: A couple more details... I'm currently storing/removing the reference ObjectIds like this (note targetID is a String):
user.favorites.users.push({ _id: mongoose.Types.ObjectId(targetID)});
user.favorites.users.pull({ _id: mongoose.Types.ObjectId(targetID)});
Also, I need to populate the users and places with their respective documents aswell, I think that might not be clear in my original question.
Well, I figured out what I needed by paying proper attention to the docs (and also with #DJeanCar 's (+1'd) help/pointers).
By Mongoose's docs regarding Populating across multiple levels, I've reached this solution:
User.findOne({_id: currentUser})
.populate({path:"favorites.users", populate: "name"})
.populate({path:"favorites.places", populate: "name"})
.exec(function(err, user){
if(err)
throw err;
if(!user){
console.log("couldnt find source user");
}else{
// here, user.favorites is populated and I can do what I need with the data
}
});
Also, from what I could tell, you can also pass select: "field field field" in populate()'s options, should you need to filter the document fields you require after population.
Hope this helps someone with similar issues!
Try:
User
.findOne({_id: currentUser})
.populate('favorites.users')
.populate('favorites.places')
.exec( function (err, user) {
// user.favorites.users
// user.favorites.places
});

Expressjs rest api how to deal with chaining functionality

I am building a restful API using express, mongoose and mongodb. It works all fine but I have a question about how to deal with requests that contain more functionality than just one find, delete or update in the database. My user model looks as follows:
var UserSchema = new Schema({
emailaddress: {type: String, unique: true},
firstname: String,
lastname: String,
password: String,
friends: [{type: Schema.Types.ObjectId, unique: true}]
});
As you can see is the friends array just an array of ObjectIds. These ObjectIds refer to specific users in the database. If I want to retrieve an array of a user's friends I now have to look up the user that makes the request, then find all the users that have the same id as in the friends array.
Now it looks like this:
methods.get_friends = function(req, res) {
//find user.
User.findOne({_id: req.params.id}, function(err, user, next) {
if(err) next(err);
if(user) {
console.log(user);
//find friends
User.find({_id: {$in: user.friends}}, {password: 0}).exec(function (err,
friends, next) {
if(err) next(err);
if(friends) {
res.send(friends);
};
});
}
Would it be possible to seperate the lookup of the user in a certain method and chain the methods? I saw something about middleware chaining i.e. app.get('/friends', getUser, getFriend)but would that mean that I have to alter the req object in my middleware (getUser) method and then pass it on? How would you solve this issue? Would you perhaps change the mongoose model and save all friend data (means that it could become outdated) or would you create a method getUser that returns a promise on which you would collect the friend data?
I will be grateful for all the help I can get!
Thank you in advance.
Mongoose has a feature called population which exists to help in these kinds of situations. Basically, Mongoose will perform the extra query/queries that are required to load the friends documents from the database:
User.findOne({_id: req.params.id})
.populate('friends')
.exec(function(err, user) {
...
});
This will load any related friends into user.friends (as an array).
If you want to add additional constraints (in your example, password : 0), you can do that too:
User.findOne({_id: req.params.id})
.populate({
path : 'friends'
match : { password : 0 },
})
.exec(function(err, user) {
...
});
See also this documentation.

Querying nested documents using Mongoose (MongoDB)

I am starting out with mongodb and having hard time trying to query nested documents. I have two schemas:
var LinkSchema = new mongoose.Schema({
url: String,
name: String
});
var UserSchema = new mongoose.Schema({
name: String,
links: [LinkSchema]
});
As you can see, I am just tying to build a simple bookmarking tool. Each user has a name and a collection of links. Each link has a name and a url.
Now, what I am trying to do is for example, see if a link already exists in someone's links array. I would like to be able to do something like this (Trying to get vlad's link collection and then see if the query link already belongs to the collection or not):
app.get("/:query", function(req, res){
User.findOne({"name":"vlad"}, function(err, user){
user.links.find({"url":req.params.query}, function(err, foundLinks){
if(foundLinks){
res.send("link already exists!");
} else {
res.send("link doesn't exist!");
}
});
});
});
Of course, this code doesn't work, because apparently I can't do a "user.links.find()". I guess I can just do a user.links.map to extract only urls and then run a membership query against it. But I think this would be far from the right solution. There's gotta be a way to do something like this natively using DB queries. Can someone help? Thank you!
You can query an embedded document in mongoose like this
User.find({'links.url':req.params.query}, function(err, foundUsers){
// ---
});
and to find the links that belong to the user "vlad", you can write
User.find({name:'vlad','links.url':req.params.query}, function(err, foundUsers){
// ---
});
This will do the trick.
To find a specific link that belongs to a specific user you can do this
User.find({name:'vlad','links.url':req.params.query}, { 'links.$': 1 }, function(err, foundUsers){
// ---
});

Resources