Home > Enterprise >  Create new document using mongoose and get back only selected fields
Create new document using mongoose and get back only selected fields

Time:10-09

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.

CodePudding user response:

You can use the post middleware on save, to clean up your document, like this:

schema.post('save', (doc) => {
  delete doc.myHiddenField;
});

This is required because Mongoose while saving returns the document as it is as it was being sent to MongoDB. However, in the case of find functions, it generates a projection based on the schema and sends it to MongoDB.

CodePudding user response:

This is a nice question that I didn't meet before.
As @Charchit_Kapoor mentioned, the 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 it's the right place to do it.

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') {
        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.

  • Related