Dan Monero
Dan Monero

Reputation: 437

Prevent field modification with Mongoose Schema

Is there any way to set a field with an "unmodifiable" setting (Such as type, required, etc.) when you define a new Mongoose Schema? This means that once a new document is created, this field can't be changed.

For example, something like this:

var userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
    unmodifiable: true
  }
})

Upvotes: 12

Views: 10560

Answers (5)

sgy
sgy

Reputation: 3062

From version 5.6.0 of Mongoose, we can use immutable: true in schemas (exactly as the aforementioned answer on mongoose-immutable package). Typical use case is for timestamps, but in your case, with username it goes like this:

const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
    immutable: true
  }
});

If you try to update the field, modification will be ignored by Mongoose.


Going a little further than what have been asked by OP, now with Mongoose 5.7.0 we can conditionally set the immutable property.

const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
    immutable: doc => doc.role !== 'ADMIN'
  },
  role: {
    type: String,
    default: 'USER',
    enum: ['USER', 'MODERATOR', 'ADMIN'],
    immutable: true
  }
});

Sources: What's New in Mongoose 5.6.0: Immutable Properties and What's New in Mongoose 5.7: Conditional Immutability, Faster Document Arrays.

Upvotes: 35

desoares
desoares

Reputation: 861

You can do it with Mongoose only, in userSchema.pre save:

if (this.isModified('modified query')) {
    return next(new Error('Trying to modify restricted data'));
}
return next();

Upvotes: 1

Lubo Kirov
Lubo Kirov

Reputation: 21

I had the same problem with field modifications.

Try https://www.npmjs.com/package/mongoose-immutable-plugin

The plugin will reject each modification-attempt on a field and it works for

  1. Update
  2. UpdateOne
  3. FindOneAndUpdate
  4. UpdateMany
  5. Re-save

It supports array, nesting objects, etc. types of field and guards deep immutability.

Plugin also handles update-options as $set, $inc, etc.

Upvotes: 1

flipcc
flipcc

Reputation: 105

Please be aware that the documentation explicitly states that when using functions with update in their identifier/name, the 'pre' middleware is not triggered:

Although values are casted to their appropriate types when using update, the following are not applied:
- defaults
- setters
- validators
- middleware

If you need those features, use the traditional approach of first retrieving the document.

Model.findOne({ name: 'borne' }, function (err, doc) { if (err) .. doc.name = 'jason bourne'; doc.save(callback); })

Therefore either go with the above way by mongooseAPI, which can trigger middleware (like 'pre' in desoares answer) or triggers your own validators e.g.:

const theOneAndOnlyName = 'Master Splinter';
const UserSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
    default: theOneAndOnlyName
    validate: {
      validator: value => {
        if(value != theOneAndOnlyName) {
          return Promise.reject('{{PATH}} do not specify this field, it will be set automatically');
          // message can be checked at error.errors['username'].reason
        }
        return true;
      },
      message: '{{PATH}} do not specify this field, it will be set automatically'
    }
  }
});

or always call any update functions (e.g. 'findByIdAndUpdate' and friends) with an additional 'options' argument in the form of { runValidators: true } e.g.:

const splinter = new User({ username: undefined });
User.findByIdAndUpdate(splinter._id, { username: 'Shredder' }, { runValidators: true })
  .then(() => User.findById(splinter._id))
  .then(user => {
    assert(user.username === 'Shredder');

    done();
  })
  .catch(error => console.log(error.errors['username'].reason));

You can also use the validator function in a non-standard way i.e.:

...
validator: function(value) {
  if(value != theOneAndOnlyName) {
    this.username = theOneAndOnlyName;
  }
  return true;
}
...

This does not throw a 'ValidationError' but quietly overrides the specified value. It still only does so, when using save() or update functions with specified validation option argument.

Upvotes: 1

Rakan
Rakan

Reputation: 400

You can use Mongoose Immutable. It's a small package you can install with the command below, it allows you to use the "immutable" property.

npm install mongoose-immutable --save

then to use it:

var userSchema = new mongoose.Schema({
    username: {
        type: String,
        required: true,
        immutable: true
    }
});
userSchema.plugin(immutablePlugin);

Upvotes: 0

Related Questions