Getters/Setters in Mongoose

Mongoose getters and setters allow you to execute custom logic when getting or setting a property on a Mongoose document. Getters let you transform data in MongoDB into a more user friendly form, and setters let you transform user data before it gets to MongoDB.

Getters

Suppose you have a User collection and you want to obfuscate user emails to protect your users' privacy. Below is a basic userSchema that obfuscates the user's email address.

const userSchema = new Schema({
  email: {
    type: String,
    get: obfuscate
  }
});

// Mongoose passes the raw value in MongoDB `email` to the getter
function obfuscate(email) {
  const separatorIndex = email.indexOf('@');
  if (separatorIndex < 3) {
    // 'ab@gmail.com' -> '**@gmail.com'
    return email.slice(0, separatorIndex).replace(/./g, '*') +
      email.slice(separatorIndex);
  }
  // 'test42@gmail.com' -> 'te****@gmail.com'
  return email.slice(0, 2) +
    email.slice(2, separatorIndex).replace(/./g, '*') +
    email.slice(separatorIndex);
}

const User = mongoose.model('User', userSchema);
const user = new User({ email: 'ab@gmail.com' });
user.email; // **@gmail.com

Keep in mind that getters do not impact the underlying data stored in MongoDB. If you save user, the email property will be 'ab@gmail.com' in the database.

By default, Mongoose executes getters when converting a document to JSON, including Express' res.json() function.

app.get(function(req, res) {
  return User.findOne().
    // The `email` getter will run here
    then(doc => res.json(doc)).
    catch(err => res.status(500).json({ message: err.message }));
});

To disable running getters when converting a document to JSON, set the toJSON.getters option to false in your schema as shown below.

const userSchema = new Schema({
  email: {
    type: String,
    get: obfuscate
  }
}, { toJSON: { getters: false } });

To skip getters on a one-off basis, use user.get() with the getters option set to false as shown below.

user.get('email', null, { getters: false }); // 'ab@gmail.com'

Setters

Suppose you want to make sure all user emails in your database are lowercased to make it easy to search without worrying about case. Below is an example userSchema that ensures emails are lowercased.

const userSchema = new Schema({
  email: {
    type: String,
    set: v => v.toLowerCase()
  }
});

const User = mongoose.model('User', userSchema);

const user = new User({ email: 'TEST@gmail.com' });
user.email; // 'test@gmail.com'

// The raw value of `email` is lowercased
user.get('email', null, { getters: false }); // 'test@gmail.com'

user.set({ email: 'NEW@gmail.com' });
user.email; // 'new@gmail.com'

Mongoose also runs setters on update operations, like updateOne(). Mongoose will upsert a document with a lowercased email in the below example.

await User.updateOne({}, { email: 'TEST@gmail.com' }, { upsert: true });

const doc = await User.findOne();
doc.email; // 'test@gmail.com'

In a setter function, this can be either the document being set or the query being run. If you don't want your setter to run when you call updateOne(), you add an if statement that checks if this is a Mongoose document as shown below.

const userSchema = new Schema({
  email: {
    type: String,
    set: toLower
  }
});

function toLower(email) {
  // Don't transform `email` if using `updateOne()` or `updateMany()`
  if (!(this instanceof mongoose.Document)) {
    return email;
  }
  return email.toLowerCase();
}

const User = mongoose.model('User', userSchema);
await User.updateOne({}, { email: 'TEST@gmail.com' }, { upsert: true });

const doc = await User.findOne();
doc.email; // 'TEST@gmail.com'

Differences vs ES6 Getters/Setters

Mongoose setters are different from ES6 setters because they allow you to transform the value being set. With ES6 setters, you would need to store an internal _email property to use a setter. With Mongoose, you do not need to define an internal _email property or define a corresponding getter for email.

class User {
  // This won't convert the email to lowercase! That's because `email`
  // is just a setter, the actual `email` property doesn't store any data.
  set email(v) {
    return v.toLowerCase();
  }
}

const user = new User();
user.email = 'TEST@gmail.com';

user.email; // undefined