'Why can't I delete a mongoose model's object properties?

When a user registers with my API they are returned a user object. Before returning the object I remove the hashed password and salt properties. I have to use

user.salt = undefined;
user.pass = undefined;

Because when I try

delete user.salt;
delete user.pass;

the object properties still exist and are returned.

Why is that?



Solution 1:[1]

To use delete you would need to convert the model document into a plain JavaScript object by calling toObject so that you can freely manipulate it:

user = user.toObject();
delete user.salt;
delete user.pass;

Solution 2:[2]

Non-configurable properties cannot be re-configured or deleted.

You should use strict mode so you get in-your-face errors instead of silent failures:

(function() {
    "use strict";
     var o = {};
     Object.defineProperty(o, "key", {
         value: "value",
         configurable: false,
         writable: true,
         enumerable: true
     });
     delete o.key;
})()
// TypeError: Cannot delete property 'key' of #<Object>

Solution 3:[3]

Rather than converting to a JavaScript object with toObject(), it might be more ideal to instead choose which properties you want to exclude via the Query.prototype.select() function.

For example, if your User schema looked something like this:

const userSchema = new mongoose.Schema({

    email: {

        type: String,
        required: true,
    },
    name: {

        type: String,
        required: true
    },
    pass: {

        type: String,
        required: true
    },
    salt: {

        type: String,
        required: true
    }
});

module.exports = {

    User: mongoose.model("user", userSchema)
};

Then if you wanted to exclude the pass and salt properties in a response containing an array of all users, you could do so by specifically choosing which properties to ignore by prepending a minus sign before the property name:

users.get("/", async (req, res) => {

    try {

        const result = await User
            .find({})
            .select("-pass -salt");

        return res
            .status(200)
            .send(result);
    }
    catch (error) {

        console.error(error);
    }
});

Alternatively, if you have more properties to exclude than include, you can specifically choose which properties to add instead of which properties to remove:

        const result = await User
            .find({})
            .select("email name");

Solution 4:[4]

Another solution aside from calling toObject is to access the _doc directly from the mongoose object and use ES6 spread operator to remove unwanted properties as such:

user = { ...user._doc, salt: undefined, pass: undefined }

Solution 5:[5]

Old question, but I'm throwing my 2-cents into the fray....

You question has already been answered correctly by others, this is just a demo of how I worked around it.

I used Object.entries() + Array.reduce() to solve it. Here's my take:

// define dis-allowed keys and values
const disAllowedKeys = ['_id','__v','password'];
const disAllowedValues = [null, undefined, ''];

// our object, maybe a Mongoose model, or some API response
const someObject = {
  _id: 132456789,
  password: '$1$O3JMY.Tw$AdLnLjQ/5jXF9.MTp3gHv/',
  name: 'John Edward',
  age: 29,
  favoriteFood: null
}; 

// use reduce to create a new object with everything EXCEPT our dis-allowed keys and values!
const withOnlyGoodValues = Object.entries(someObject).reduce((ourNewObject, pair) => {
  const key = pair[0];
  const value = pair[1]; 
  if (
    disAllowedKeys.includes(key) === false &&
    disAllowedValues.includes(value) === false
  ){
    ourNewObject[key] = value; 
  }
  return ourNewObject; 
}, {}); 

// what we get back...
// {
//   name: 'John Edward',
//   age: 29
// }

// do something with the new object!
server.sendToClient(withOnlyGoodValues);

This can be cleaned up more once you understand how it works, especially with some fancy ES6 syntax. I intentionally tried to make it extra-readable, for the sake of the demo.

Read docs on how Object.entries() works: MDN - Object.entries() Read docs on how Array.reduce() works: MDN - Array.reduce()

Solution 6:[6]

I use this little function just before i return the user object. Of course i have to remember to add the new key i wish to remove but it works well for me

const protect = (o) => {
    const removes = ['__v', '_id', 'salt', 'password', 'hash'];
    m = o.toObject();
    removes.forEach(element => {
        try{
            delete m[element]
        }
        catch(O_o){}
    });
    
return m
}

and i use it as I said, just before i return the user.

return res.json({ success: true, user: await protect(user) });

Alternativly, it could be more dynamic when used this way:

const protect = (o, removes) => {
        m = o.toObject();
        removes.forEach(element => {
            try{
                delete m[element]
            }
            catch(O_o){}
        });
        
    return m
    }

 return res.json({ success: true, user: await protect(user, ['salt','hash']) });

Solution 7:[7]

The delete operation could be used on javascript objects only. Mongoose models are not javascript objects. So convert it into a javascript object and delete the property. The code should look like this:

const modelJsObject = model.toObject();
delete modlelJsObject.property;

But that causes problems while saving the object. So what I did was just to set the property value to undefined.

model.property = undefined;

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 JohnnyHK
Solution 2 Esailija
Solution 3
Solution 4 dillon
Solution 5 TJBlackman
Solution 6
Solution 7