This is the 31st day of my participation in the August More Text Challenge

Mongoose’s unique index unique option all works so that each document must have a unique value for a given path. For example, here’s how to tell Mongoose that a user’s email must be unique.

const mongoose = require('mongoose')
​
const userSchema = new mongoose.Schema({
  email: {
    type: String.unique: true // Email must be unique}});const User = mongoose.model('User', userSchema)
Copy the code

If you try to create two users with the same name, you will get a duplicate key error.

MongoError:E11000 repeat key error set
await User.create([
  { email: '[email protected]' },
  { email: '[email protected]'}])const doc = new User({ email: '[email protected]' })
MongoError:E11000 repeat key error set
await doc.save()
Copy the code

Updates can also cause duplicate key errors. For example, if you create a user with a unique E-mail address and then update its E-mail address to a non-unique value, you will get the same error.

await User.create({ email: '[email protected]' })
​
MongoError:E11000 repeat key error set
await User.updateOne({ email: '[email protected]' }, { email: '[email protected]' })
Copy the code

uniqueDefine indexes, not validators

A common problem is that the unique option tells Mongoose to define a unique index. This means that Mongoose does not check for uniqueness when you use validate().

await User.create({ email: '[email protected]' })
​
const doc = new User({ email: '[email protected]' })
await doc.validate() // No errors are thrown
Copy the code

When writing automated tests, it is important that UNIQUE defines an index rather than a validator. If you drop a database connected to the User model, the unique index is also dropped, and duplicate indexes can be saved.

await mongoose.connection.dropDatabase()
​
// Succeeded because the unique index disappeared!
await User.create([
  { email: '[email protected]' },
  { email: '[email protected]'}])Copy the code

In production environments, databases are usually not deleted, so this is rarely an issue in production environments.

When writing Mongoose tests, it is often recommended to use deleteMany() to clear data between tests rather than dropDatabase(). This ensures that all documents are deleted without cleaning up database-level configurations such as indexes and collations, and deleteMany() is also much faster than dropDatabase().

However, if you choose to delete the database between tests, you can regenerate all unique indexes using the model.syncIndexes () method.

await mongoose.connection.dropDatabase()
​
// Rebuild all indexes
await User.syncIndexes()
​
MongoError:E11000 repeat key error set
await User.create([
  { email: '[email protected]' },
  { email: '[email protected]'}])Copy the code

To deal withnull

Null is a different value, and you cannot save two email users with NULL. Also, you cannot save two users without the email attribute.

// Throw because both documents have undefined
await User.create([
  {},
  {}
])
​
// Throw because both documents have NULL
await User.create([
  { email: null },
  { email: null}])Copy the code

One solution is to use the required attribute, which disallows null and undefined values:

const userSchema = new mongoose.Schema({
  email: {
    type: String.required: true.unique: true // Email must be unique}})Copy the code

If you want the email to be unique, unless it is not defined, you can instead define a Sparse Index on the email, as shown below.

const userSchema = new mongoose.Schema({
  email: {
    type: String.// Email must be unique unless it is not defined
    index: {
      unique: true.sparse: true}}})Copy the code

User friendly repeat key error

To make MongoDB E11000 error messages user-friendly, you can use the Mongoose-beautiful-unique-Validation package.

const schema = new Schema({ name: String })
schema.plugin(require('mongoose-beautiful-unique-validation'))
​
const UserModel = mongoose.model('User', schema)
​
const doc = await UserModel.create({ name: 'O.O' })
​
try {
  // Try to create a document with the same _id. This will always fail because the MongoDB collection always has a unique index on _id.
  await UserModel.create(Object.assign({}, doc.toObject()))
} catch (err) {
  // _id is not unique.
  console.log(err.errors['_id'].message)
}
Copy the code