Issue
I’m creating a new document using mongoose, and the newly created document I get back contains ALL THE FIELDS, including the ones which I marked as { select: false } in the schema.
Weird, right?
To reference, whenever i’m using any of the Model.find(), Model.findOne(), etc. commands, those marked fields do not come back, as expected.
So to me, this behavior of returning ALL fields upon creation is weird…
Could it be that the { select: false } only applies to find operations?
Anyway, so my question is, how do I get back a "clean" document after a successful create?
Or, if that can’t be done for some reason, is there a built-in function to mongoose that can clean all { select: false } fields based on the model’s schema?
The example model:
const { Schema, model } = require('mongoose');
const userSchema = new Schema({
nickname: { type: String, required: true },
email: { type: String, required: true },
myHiddenField: { type: String, required: true, select: false },
});
const UserModel = model('user', userSchema);
module.exports = UserModel;
The document creation:
// Way number 1: using save
// const user = new UserModel({ nickname: "hello", email: "world", myHiddenField: "bluh bluh" });
// const userResult = (await user.save()).toObject();
// Way number 2: using save
const userResult = (await UserModel.create({ nickname: "hello", email: "world", myHiddenField: "bluh bluh" })).toObject();
return userResult;
Any of the two create forms above create a user document, and bring back a document that contains my hidden fields.
Solution
This is a nice question that I didn’t meet before.
mongoose
‘s save function doesn’t use the schema to build a projection
, so you’d have to do it post save, and since mongoose offers a post
middleware – seems like the right place to do it in.
Here is a complete solution for you to use:
const { Schema, model } = require('mongoose');
// Step 1: create a new schema
const userSchema = new Schema({
nickname: { type: String, required: true },
email: { type: String, required: true },
myHiddenField: { type: String, required: true, select: false },
someObject: { type:
{
a: { type: Number },
b: { type: Boolean, select: false },
c: { type: String },
}, required: true
},
});
// Step 2: create a mongoose 'post' middleware
userSchema.post('save', (doc) => {
const docObj = doc.toObject();
const checkPropertiesRecursively = (objWithProperties, subObject) => {
const keys = Object.keys(objWithProperties);
for (let i = 0; i < keys.length; i++) {
const curProperty = objWithProperties[keys[i]];
if (curProperty.select === false) {
delete subObject[keys[i]];
} else if (typeof curProperty.type === 'object' && Array.isArray(curProperty.type)) {
checkPropertiesRecursively(curProperty.type, subObject[keys[i]]);
}
}
};
checkPropertiesRecursively(userSchemaTemplate, docObj);
return docObj;
});
// Step 3: create a Model from the schema
const User = model('user', userSchema);
module.exports = User;
Code Explanation
– An automation function
Inside the post
middleware, I created a function that scans your schema for fields that you have unselected, and deletes them for you. You’re probably thinking "Why not just delete them manually with "delete object.key"? I know what those fields are!".
While that’s true, it is good practice to have it be done for you automatically. As you probably know, models change, and the unselected of today, could be the selected of tomorrow, and vice-versa. And so, when that happens, you’d only want to update one place, instead of two. In this scenario, that one place would be the schema.
– A recursive function
I know some people are struggling with recursion, so i’ll put it in simple laymen terms: you gave an example of a schema that has only top level fields, right? but what if it also contained an object field? That object field would also contain fields, which some of them inner fields could be selected, and some are not. So, this is just to cover your basics, to handle slightly more complex situations. Notice that in the schema example I gave there’s an extra field called someObject, with 3 inner fields: a,b,c. I marked b as unselected, an so the recursive function will catch it and delete it. To be fair? I’m missing here the treatment of an Array type field that contains objects, but you can handle it the same way within the recursion.
– Declaring post BEFORE the Model creation
As mongoose
‘s documentation states:
"Calling pre() or post() after compiling a model does not work in Mongoose in general".
Meaning, the userSchema.post(‘save’, fn) must come before the const User = model(‘user’, userSchema);
Otherwise, the post(‘save’) middleware will not fire.
– Return the doc
Notice that at the end of the middleware, i’m returning the altered document. This is important! Or else you wouldn’t enjoy the fruits of your hard work.
Answered By – Tal Kohavy
This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0