Cannot delete json element - node.js

I have a node js function:
function func() {
USER.find({},function(err, users){
user = users[0];
console.log(user); // {"name":"mike", "age":15, "job":"engineer"}
user.name = "bob"; //{"name":"bob", "age":15, "job":"engineer"}
delete user.name;
console.log(user); // {"name":"mike", "age":15, "job":"engineer"} name still there??
});
}
Here USER is a mongoose data model and find is to query the mongodb. The callback provide an array of user if not err. The user data model looks like
{"name":"mike", "age":15, "job":"engineer"}.
So the callback is invoked and passed in users, I get the first user and trying to delete the "name" from user. The wired part is I can access the value correctly and modify the value. But if I 'delete user.name', this element is not deleted from json object user. Why is that?

As others have said, this is due to mongoose not giving you a plain object, but something enriched with things like save and modifiedPaths.
If you don't plan to save the user object later, you can also ask for lean document (plain js object, no mongoose stuff):
User.findOne({})
.lean()
.exec(function(err, user) {
delete user.name; // works
});
Alternatively, if you just want to fetch the user and pay it forward without some properties, you can also useselect, and maybe even combine it with lean:
User.findOne({})
.lean()
.select('email firstname')
.exec(function(err, user) {
console.log(user.name); // undefined
});

Not the best workaround, but... have you tried setting to undefined?
user.name = undefined;

Related

Express, Mongoose, db.findOne always returns same document

I am attempting a CRUD app with MEAN stack. I am using mongoose in Express to call to the MongoDB. I am using the FindOne function with a specified parameter, and it's always returning the same (incorrect) document.
I know I am connected to the correct database, since I get a document back from that collection, but it's always the same document, no matter what I pass as the parameter.
module.exports = mongoose.model('Player', playersSchema, 'players'); //in player.js
const Player = require('./model/players');
app.get('/api/player/:id', (req, res) =>{
Player.findOne({id: req.params.playerId},
function(err, player) {
if(err) {
res.json(err);
}
else {
res.json(player);
}
});
});
I have 3 separate "players", with three distinct "playerID" fields (38187361, 35167321, 95821442). I can use Postman to GET the following URL, for example:
http://localhost:3000/api/player/anythingyouWantInHere
and it will return 38187361, the first document. I've been over this website, many tutorials, and the Mongoose documentation and I can't see what I'm doing wrong..
I'd like to eventually find by playerId OR username OR email, but one hurdle at a time...
From the mongoose documentation of findOne, if you pass Id a null or an empty value, it will query db.players.findOne({}) internally which will return first document of the collection everytime you fetch. So make sure you are passing non-empty id here.
Note: conditions is optional, and if conditions is null or undefined,
mongoose will send an empty findOne command to MongoDB, which will
return an arbitrary document. If you're querying by _id, use
findById() instead.
Your route is '/api/player/:id', so the key on the req.params object will be 'id' not 'playerId'.
I don't know what/where/if you're populating the playerId param, but if you update your query to call req.params.id it should actually change the document based on the path as you seem to be wanting to do.
I had the same problem, and it was that the name of column's table was different from the model I had created.
In my model the name of the wrong column was "role" and in my table it was "rol".

Strange result when nesting query of two different collections

I have a need to check two separate collections in Mongo to see if a phone number exists.
I first created a global variable called 'ownerId'
I then look in one collection call 'Profile'. If the email value I pass exists in the 'emails' array of a document in that collection, I fill the 'ownerId' variable I created with a value in that document called 'owner_id'.
I then look in another collection called 'User', which has a single email field. If the email value I pass exists in a document in that collection, I fill the 'ownerId' variable I created with the '_id' of that document.
I have my queries nested in a couple 'then()' statements.
Here is my code:
Profile.findOne({'emails.email_address':req.body.invited_email}, function(err, result){
if(result)
ownerId = result.owner_id;
}).then(function(){
User.findOne({'email':req.body.invited_email}, function(err, result2){
if(ownerId !== null)
ownerId = result2._id;
})
}).then(function(){
console.log(' --------------- THIS IS AN EXISTING EMAIL OWNER ID: ' + ownerId);
})
The result is not as I expect.
If the 'Profile' query is true and finds a match, then it will console log the ownerId with a value.
If the second 'User' query is true, but there is not match for the 'Profile' it will console log 'null'. I expect it to console log the _id value of the User result.
Can anyone see the error in my logic?
I wouldn't mix callbacks and promises. I would recommend using async/await for this. It makes asynchronous code look synchronous. For example:
let owner_id;
try {
let profile = await Profile.findOne({ 'emails.email_address': req.body.invited_email });
let user = await User.findOne({ 'email': req.body.invited_email }):
if (profile) {
owner_id = profile.owner_id;
} else if (user) {
owner_id = user.owner_id;
} else {
console.log('no owner id found');
}
} catch(err) {
console.log(err);
}
The code is wrapped in try/catch to handle errors, just like the catch() method for ordinary promises. The functions using await needs the async keyword.
await is used to wait for promises to finish. No callbacks are needed. You can see the code looks like ordinary synchronous code.

Limit posted fields for insert

I'm trying to limit the fields a user can post when inserting an object in mongodb. I know ho i can enforce fields to be filled but I can't seem to find how to people from inserting fields that I don't want.
This is the code I have now for inserting an item.
app.post("/obj", function (req, res) {
var newObj = req.body;
//TODO filter fields I don't want ?
if (!(newObj .id || newObj .type)) {
handleError(res, "Invalid input", "Must provide a id and type.", 400);
return;
}
db.collection(OBJ_COLLECTION).insertOne(newObj, function(err, doc) {
if (err) {
handleError(res, err.message, "Failed to create new object.");
} else {
res.status(201).json(doc.ops[0]);
}
});
});
There's likely JS native ways to do this, but I tend to use Lodash as my toolbox for most projects, and in that case what I normally do is setup a whitelist of allowed fields, and then extract only those from the posted values like so:
const _ = require('lodash');
app.post("/obj", function (req, res) {
var newObj = _.pick(req.body, ['id', 'type','allowedField1','allowedField2']);
This is pretty straightforward, and I usually also define the whitelist somewhere else for reuse (e.g. on the model or the like).
As a side note, I avoid using 'id' as a field that someone can post to for new objects, unless I really need to, to avoid confusion with the autogenerated _id field.
Also, you should really look into mongoose rather than using the straight mongodb driver, if you want to have more model-based control of your documents. Among other things, it will strip any fields off the object if they're not defined in the schema. I still use the _.pick() method when there are things that are defined in the schema, but I don't want people to change in a particular controller method.

How to update in one query in waterline

In SQL, I can update a field with inline calculation like this-
UPDATE user SET count=count+1 WHERE id=userID
It requires only one database operation.
But, in waterline, my only option now is to query the user, change the value, and save back into the database.
User.findOne(userID).exec(function(err, user) {
if(user) {
user.count++;
user.save(function(err) {
if(!err) console.log("success");
});
}
});
It requires double database access. How can I reduce the overhead?
You can execute native querys with waterline so I would go that route.
User.query("query string here", function (err,result){//Handle errors and result here});
http://sailsjs.org/documentation/reference/waterline-orm/models/query

Does MongooseJS return a new fresh result object on save()?

By default MongoDB on collection.save() returns a WriteResult object as stated in the documentation:
The save() returns a WriteResult object that contains the status of the insert or update operation.
But with Mongoose (and I guess the underlying mongodb driver in node) you can add a second parameter that is populated with the entire object that you just inserted and with the new _id:
var user = new User(req.body);
user.save(function (err, userResult) {
if (err) {
log.error(err);
}
log.debug('User data: ', userResult);
});
So my question:
Does userResult contain retrieved data from Mongo and it's a fresh object OR is the object passed already passed into the save() method and from the database call is merged with only some partial data like the generated _id and/or created date?
If you take a look at Model.prototype.save():
https://github.com/Automattic/mongoose/blob/8cb0e35/lib/model.js#L254
It looks like you get back the same model instance (self) in your userResult.
To answer your question, the object returned is a new object -- not the original one (so user != userResult)
Mongoose supports a few options -- new: true and lean: false settings may be of interest to you to modify how data is returned in different operations (if you want it to be a new option or do not care).
You can verify this 'new object' is the case by using the following:
var user = new User(req.body);
user.save(function (err, userResult) {
if (err) {
log.error(err);
}
user.name = 'TEST';
console.log(`${userResult.name} will be undefined but ${user.name} will not be`);
log.debug('User data: ', userResult);
});

Resources