Mongoose, find, return specific properties - node.js

I have this get call:
exports.getBIMFromProject = function(req, res){
mongoose.model('bim').find({projectId: req.params['prj_id']}, function(err, bim){
if(err){
console.error(err);
res.send(500)
}
res.send(200, bim);
});
};
Where do I specify which properties I want to return? Can't find it in the docs. The above returns the entire object. I only want a few properties returned.
This is my schema:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var bimSchema = new Schema({
projectId: Number,
user: String,
items:[
{
bimObjectId: Number,
typeId: String,
position:{
floor: String,
room:{
name: String,
number: String
}
}
}
]
});
mongoose.model('bim', bimSchema);
I don't want the items array included in my rest call.

You use projection. The first example in the mongoose query docs has a projection operation tucked in.
NB: not real code b/c I highlighted the important bits with triple stars
// find each person with a last name matching 'Ghost', ***selecting the `name` and `occupation` fields***
Person.findOne({ 'name.last': 'Ghost' }, ***'name occupation'***, function (err, person) {
if (err) return handleError(err);
console.log('%s %s is a %s.', person.name.first, person.name.last, person.occupation) // Space Ghost is a talk show host.
})
The Person schema isn't specified but I think the example is clear enough.

Mongoose provides multiple ways to project documents with find, findOne, and findById.
1. Projection as String:
// INCLUDE SPECIFIC FIELDS
// find user and return only name and phone fields
User.findOne({ email: email }, 'name phone');
// EXCLUDE SPECIFIC FIELD
// find user and return all fields except password
User.findOne({ email: email }, '-password');
2. Projection by passing projection property:
// find user and return just _id field
User.findOne({ email: email }, {
projection: { _id: 1 }
});
3. Using .select method:
// find user and return just _id and name field
User.findOne({ email: email }).select('name');
// find user and return all fields except _id
User.findOne({ email: email }).select({ _id: 0 });
You can do the same with find and findById methods too.

MyModel.find({name: "john"}, 'name age address', function(err, docs) {
console.log(docs);
});
This will return fields - name, age and address only.

With the help of .select() this is possible.
If number of fields required are less from total fields then,
.select('projectId user') can be used
Else number of fields to be ignored are less from total fields then,
.select('-items') can be used.
So for getting a field, simply, space separated string of fields can be passed and for ignoring the field, space separated string with "-" before the field can be used.
For more documentation.

Find Specific properties also avoid some properties
await User.find({ email: email }, { name: 1, phone: 1, status: 1, password: 0 });

.Select() method is used to select which fields are to be returned in the query result
let result = await MyModel.find({ user : "user" }).select('name lastname status')

Related

Unable to Append Array Within MongoDB (mongoose) Document

I'm trying to append an empty array in a mongo database with a String that is created in the front-end (website ui), the relevant code snipets are as following:
mongoose Schema
email: String,
displayName: String,
googleId: String,
toIgnore: [{toIgnoreURL: String}]
})
document creation with passport & passport-google-oauth20
User.findOne({email: email.emails[0].value}).then((currentUser)=>{
if(currentUser){
// user does already exist
console.log('welcome back!', currentUser)
done(null, currentUser)
}
else{ // user doesn't exist yet
new User({
email: email.emails[0].value,
displayName: email.displayName,
googleId: email.id,
toIgnore: []
}).save().then((newUser)=>{
console.log('new user created: ' + newUser)
done(null, newUser)
});
}
})
And lastly the attempt to append the toIgnore array property of the 'User' collection (of the currently logged in user)
User.update(
{email: emailThisSession},
{$push: {toIgnore: {toIgnoreURL: url}}})
In mongodb I see that the following document is successfully created
_id
:ObjectId(
IdOfDocumentInMongoDB)
toIgnore
:
Array
email
:
"myactualtestemail"
googleId
:
"longgoogleidonlynumbers"
__v
:
0
(Also as seen in attached image)
document in mongodb ui
I don't seem to figure out how to actually populate the 'toIgnore' array.
For instance, when console logging the following
var ignoreList = User.findOne({email:emailThisSession}).toIgnore;
console.log(ignoreList)
The output is undefined
Note that console logging the url variable does indeed print the value I want to append to the array!
I tried any format combination I could think of in the Schema builder and document creation, but I can't find figure the right way to get it done!
Any help would be appreciated!
Update, using promises didn't work either
User.findOne({email:emailThisSession}).then((currentUser)=>{ //adding .exec() after findOne({query}) does not help as in User.findOne({email:emailThisSession}).exec().then(...)
console.log(currentUser.toIgnore, url) //output is empty array and proper value for url variable, empty array meaning []
currentUser.toIgnore.push(url)
});
While adjusting the Schema as follows:
const userSchema = new Schema({
email: String,
displayName: String,
googleId: String,
toIgnore: []
})
Solution
I simply needed to change the update command to
User.updateOne(
{email: emailThisSession},
{$push: {toIgnore: {toIgnoreURL: url}}}).then((user)=>{
console.log(user)
})
Thanks #yaya!
Unable to add array element within a document with mongoose
define your schema as:
const UserSchema = new mongoose.Schema({
...
toIgnore: [{toIgnoreURL: String}]
})
then you can create an object like this :
new User({
...,
toIgnore: [] // <-- optional, you can remove it
})
To check the value:
User.findOne({...}).then(user => {
console.log(user.toIgnore)
});
Your update statement should be:
User.update(
{email: emailThisSession},
{$push: {toIgnore: {toIgnoreURL: url}}}
).then(user => {
console.log(user)
})
So in your case, this is undefined:
User.findOne({email:emailThisSession}).toIgnore
Since findOne is async. for getting the result, you can whether pass it a callback or you can use promises (User.findOne({...}).then(user => console.log(user.toIgnore)))
Update:
While adjusting the Schema as follows: new Schema({..., toIgnore: []})
Here is the problem with your update. you should change it back to : toIgnore: [{toIgnoreURL: String}]

Values are set as null inside the database when the respective values are not passed while updating

I want to update the some keys and values inside the database. I already have some fields filled like firstname, lastname and now I want to update those documents with profile picture, contact number etc.
When I pass those values using update then the fields which are already set up become null, i.e firstname and lastname become null. I want firstname and lastname set as before if the user doesn't want to update those values and update only the later values inside the document.
If the user wants to update both firstname and lastname as well as profile photo, then I want those be updated too.
I have used the update method of mongo and request for the new value for updating. I passed the new values through postman but do not pass the old values which were already in the database. When updated, it shows null for those whose values.
router.put('/:id/edit',upload.array("images",2),function(req,res,next){
shopid= req.params.id,
console.log(req.body);
User.find({"_id":shopid}).update({
firstname: req.body.firstname,
lastname: req.body.lastname,
contact_no : req.body.contact_no,
images:{
shop_logo:req.files[0].path,
shop_picture: req.files[1].path,
},
shopname:req.body.shopname,
updated_at: Date.now(),
},function(err,data){
if(err){
res.json(err);
}else{
res.json(data);
}
});
});
The result:
_id: "5ce29ba3e0c2d825d9e5a39f"
firstname: null
lastname: "Gyaawali"
geo: Array
shopkeeper: true
created_at: 2019-05-20T12:20:51.407+00:00
updated_at: 2019-05-23T15:00:04.442+00:00
__v: 0
contact_no: 9837949483
pan_no:"55343fASDE"
images: Object
shop_logo: "public/shop_logo/images-1558623604437-flag-map-of-nepal-logo-C43E9EAFA..."
shop_picture: "public/shop_logo/images-1558623604439-flat-sale-background_23-21477500..."
shopname: "Hello.shop"
I dont expect the firstname field to be changed. But it is changed to null.
Because doing that query: you are updating also those fields. Even you don't send any value to them, you are updating those fields with null (for example req.body.firstname is null because the firstname is not into the post body). If you don't want to update them, you should not include those fields to the query.
If you don't want to update firstname and lastname but the other fields yes, the query should be something like this:
User.find({"_id":shopid}).update({
contact_no : req.body.contact_no,
images:{
shop_logo:req.files[0].path,
shop_picture: req.files[1].path,
},
shopname:req.body.shopname,
updated_at: Date.now(),
},function(err,data){
if(err){
res.json(err);
}else{
res.json(data);
}
});
Hope this answer your question!
Just pre-check the keys if it exists in req.body then only update it. Like this below :
router.put("/:id/edit", upload.array("images", 2), function (req, res, next) {
let shopid = req.params.id;
let {
firstname,
lastname,
contact_no,
shopname
} = req.body;
let updateObj = {};
firstname && (updateObj.firstname = firstname);
lastname && (updateObj.lastname = lastname);
contact_no && (updateObj.contact_no = contact_no);
shopname && (updateObj.shopname = shopname);
updateObj.updated_at = Date.now();
updateObj.images = {
shop_logo: req.files[0].path,
shop_picture: req.files[1].path
};
User.find({
_id: shopid
}).update(
updateObj,
function (err, data) {
if (err) {
res.json(err);
} else {
res.json(data);
}
}
);
});

How do I prevent Schema fields from being inserted into subdocument?

I'm making a dating app in node js and vue, and everything works however I wish to exclude password from being inserted into subdocument upon creation of a conversation. Right now I know that i can say .select('-password') when using User.findOne() but it doesn't work, when adding the user schema as a subdoc to my Conversations schema, which has user_one and user_two, each referring to a User schema. I need the password field, so I can't ommit it when creating a schema. Right Now my code looks like this:
User.findOne({ _id: fromUserId }, (errUserOne, userOne) => {
User.findOne({ _id: toUserId }, (errUserTwo, userTwo) => {
conversation = new Conversation({
_id: mongoose.Types.ObjectId(),
user_one: userOne,
user_two: userTwo
});
conversation.save();
const message = new Message({
_id: mongoose.Types.ObjectId(),
conversation_id: conversation._id,
message: req.body.message,
user_id: fromUserId
});
message.save();
res.sendStatus(201);
});
});
However this code saves the password to the Conversation collection, which I don't want.
User.find({ _id: :{ $in : [fromUserId,toUserId] }, { password:0 } , (err, userArray) => {
//your code goes here
});
Two things, You are querying two time for getting users. You can merge it into single query and for excluding the password field you can pass {password:0}. Which will exclude it in the documents.
also while you define Conversation schema don't make user_one and user_two type of user. Instead define only whatever properties of user you want to save like:
var Conversation = new Schema({
_id : ObjectId,
user_one : {
_id: ObjectId,
//all other fields
},
user_two : {
_id: ObjectId,
//all other fields
},
});

Mongoose MODEL update() vs save()

There were a question about update() vs save(), but it was targeting some different stuff (I guess, purely related mongoose.Schema methods, but not to the actual document)
I have a following scenario, where user logs in to website:
I need to load document (find it by userModel.email)
Check if a userModel.password hash matches to what was recieved
Update userModel.lastLogin timestamp
Append authorization event to userModel.myEvents[] array
So I am wondering - what is a proper way to go?
1)
let foundUser = userModel.findOne({ email: recievedEmail });
if(foundUser.password != recievedPassword)
return res.status(400).json({e: "invalid pass"});
foundUser.lastLogin = new Date();
foundUser.myEvents.push(authEvent)
foundUser.save();
2)
let foundUser = userModel.findOne({ email: recievedEmail });
if(foundUser.password != recievedPassword)
return res.status(400).json({e: "invalid pass"});
foundUser.update({
$push: { myEvents: authEvent },
$set: { lastLogin: new Date() }
});
foundUser.save();
3)
let foundUser = userModel.findOne({ email: recievedEmail });
if(foundUser.password != recievedPassword)
return res.status(400).json({e: "invalid pass"});
userModel.updateOne({_id: foundUser._id}, {$push: ...
// seems no save is required here?
4)
// I am doing it wrong, and you have faster/higher/stronger variant?
First of all, you don't need to call foundUser.save() when you are using foundUser.update() method.
And, all the above methods are almost equally efficient as there are two calls being made to the database. So, it comes down to your personal preference.
And, one more method with just one call to the database can be executed in this manner:-
let foundUser = await userModel.findOneAndUpdate(
{ email: recievedEmail, password: hashedPassword },
{ $set: { lastLogin: new Date() }, $push: { myEvents: authEvent } }
);
In this method, if a user with given email and password exists, that user will be updated and corresponding updated document will be returned in a foundUser variable. So you don't have to perform an additional check on password: If findOneAndUpdate() returns a document, it means password and email matched. You have just to check for null or undefined on the returned document for no match.

How to remove mongo specific fields from result (NodeJS, Mongoose)

I want to remove all Mongo specific fields (like '_id') from query result. Is there a simple method to do this or should I remove fields manually? If yes, then which are that fields and how to do that?
I'm using NodeJS and Mongoose
You can use select() method for remove the field from your query:
Model.find({}).select("-removed_field").then (resp => {
// your code
});
You should specified the "-" before field name, to be remove this field.
If you want remove several fields - you can specified their as array:
Model.find({}).select(["-removed_field1", "-removed_field2" ... ]).then (resp => {
// your code
});
Also you can to select only specified fields, using this method without "-"
Model.find({}).select(["field1", "field2" ... ]).then (resp => {
// your code
});
If you want hide _id property you can use text argument with prefix - which will exclude this or that field from the result, for get sepecifict fields you should pass like this:
Entity.find({ ... }, 'field1 field2', function(err, entity) {
console.log(entity); // { field1: '...', field2: '...' }
});
You can specify a field to be excluded from results by using the optional 2nd parameter projection string of the find method:
Model.find({}, "-a -b").then (res => {
// objects in the res array will all have the
// 'a' and 'b' fields excluded.
});
https://mongoosejs.com/docs/api.html#model_Model.find (see projection)
you can use mongoose instance method two show specific fields from all documents
const userSchema = new mongoose.Schema({
email: {
type: String,
},
name: {
type: String,
maxlength: 128,
index: true,
trim: true,
},
});
userSchema.method({
transform() {
const transformed = {};
const fields = ['name', 'email'];
fields.forEach((field) => {
transformed[field] = this[field];
});
return transformed;
},
});
module.exports = mongoose.model('User', userSchema);
if You want to remove any specific fields like _id, You can try in two ways:
Suppose Here you try to find a user using User Model
User.find({ email: email }, { _id: 0 });
OR
const user = User.find({ email: email });
delete user._doc._id;
OP mentioned "from result", as far as I understood, it means, removing from the query result i.e. query result will contain the field, but will be removed from the query result.
A SO answer here mentions, that to modify a query result (which are immutable), we've to convert the result to Object using toObject() method (making it mutable).
To remove a field from a query result,
let immutableQueryResult = await Col.findById(idToBeSearched)
let mutableQueryResult = immutableQueryResult.toObject()
delete mutableQueryResult.fieldToBeRemoved
console.log(mutableQueryResult)
Another way of getting the mutable result is using the _doc property of the result:
let immutableQueryResult = await Col.findById(idToBeSearched)
let mutableQueryResult = immutableQueryResult._doc // _doc property holds the mutable object
delete mutableQueryResult.fieldToBeRemoved
console.log(mutableQueryResult)

Resources