134

I'm trying to let MongoDB detect a duplicate value based on its index. I think this is possible in MongoDB, but through the Mongoose wrapper things appear to be broken. So for something like this:

User = new Schema ({
  email: {type: String, index: {unique: true, dropDups: true}}
})

I can save 2 users with the same email. Darn.

The same issue has been expressed here: https://github.com/LearnBoost/mongoose/issues/56, but that thread is old and lead to nowhere.

For now, I'm manually making a call to the db to find the user. That call is not expensive since "email" is indexed. But it would still be nice to let it be handled natively.

Does anyone have a solution to this?

foobar
  • 10,854
  • 18
  • 58
  • 66
  • 1
    Bad news, it is still problem with mongod v2.4.3, mongoose v3.6.20 – damphat Oct 21 '13 at 13:11
  • Unique seems to work on one of my hosts, but fails to enforce uniques using exactly same node/mongoose code on a different host. The host that works properly runs single mongod 3.4.10, the one that does not - runs replica set with mongod 3.2.17.On both hosts, I'm creating a collection from scratch, so the existing dups are not an issue. I've tried most of the solutions on this page and the one that worked was mongoose-unique-validator from @Isaac Pak. – Maksym Jan 11 '18 at 16:08
  • Check this post if you adding a new unique field in the existing model- https://stackoverflow.com/questions/24430220/e11000-duplicate-key-error-index-in-mongodb-mongoose – Nick Dec 06 '21 at 12:10
  • And the simple solution to this is restarting the mongod service. `sudo systemctl stop mongod` and `sudo systemctl start mongod` :) –  May 08 '23 at 17:35

35 Answers35

138

Oops! You just have to restart mongo.

foobar
  • 10,854
  • 18
  • 58
  • 66
  • 4
    I am having the same issue, but I can't find out how to restart mongo on OSX. When I kill the process, it will just spawn again automatically and the unique index is still not working... any ideas? – ragulka Sep 13 '12 at 19:38
  • 12
    what do you mean "oops you just have to restart mongo"?! are you saying it is impossible to add an index without bringing the database down? – andrewrk Dec 13 '13 at 01:16
  • 8
    This isn't true. You don't need to restart mongo to add an index. – JohnnyHK Dec 09 '14 at 14:04
  • 1
    for the unique thing.. I HAD to restart mongo.. and it worked.. Thanks! – Muhammad Ahsan Ayaz Feb 01 '15 at 17:20
  • 3
    I tried restarting. Also reindexing. Had to drop the database to resolve this. Guessing the issue is that duplicates already existed before adding the index so in a production db I would need to remove duplicates and then restart? – isimmons Jun 19 '15 at 04:30
  • @isimmons dropping the collection worked for me. Thank you for the comment. – Cole Murray May 07 '16 at 17:45
  • I restarted both mongod and quit out of all clients, then restarted mongod, and I still get the same behavior. I'm using mongo v3.4.17 and mongoose 5.2.15. I'm getting deprecation warnings regarding collection.ensureIndex though, and it's telling me to use createIndexes instead, so I'm testing out Model.createIndexes()... – solstice333 Sep 18 '18 at 21:53
  • Restart command for mongodb in windows: mongod --port 27017 --dbpath=E:\data\db\ – Sanjeev Kumar Oct 30 '19 at 14:06
45

Oops! You just have to restart mongo.

And re-index too, with:

mongo <db-name>
> db.<collection-name>.reIndex()

In testing, since I don't have important data, you can also do:

mongo <db-name>
> db.dropDatabase()
jpillora
  • 5,194
  • 2
  • 44
  • 56
37

I ran into the same issue: I added the unique constraint for the email field to our UserSchema after already having added users to the db, and was still able to save users with dupe emails. I resolved this by doing the following:

1) Remove all documents from the users collection.

2) From the mongo shell, execute the command: db.users.createIndex({email: 1}, {unique: true})

Regarding step 1, note that from Mongo's docs:

MongoDB cannot create a unique index on the specified index field(s) if the collection already contains data that would violate the unique constraint for the index.

https://docs.mongodb.com/manual/core/index-unique/

Neil Strain
  • 586
  • 4
  • 4
23

I've done something like this:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const FooSchema = new Schema({
   name: { type: String, required: true, index: true, unique: true }
});

const Foo = mongoose.model('Foo', FooSchema);

Foo.createIndexes();

module.exports = Foo

I added the Foo.createIndexes() line b.c. I was getting the following deprecation warning when the code was being ran:

(node:21553) DeprecationWarning: collection.ensureIndex is deprecated. Use createIndexes instead.

I'm not sure if Foo.createIndexes() is asynchronous, but AFAIK things seem to be working fine

solstice333
  • 3,399
  • 1
  • 31
  • 28
  • The only answer to the question that addresses the problem rather than revolves around it. – Saksham Gupta Jan 28 '19 at 16:07
  • This answer helped me. All I was missing was the `index: true` for my unique index to be created. – elcid Feb 16 '20 at 16:26
  • for some reason passing unique: true works but as indexes: {...unique:true} dont.. ty – darren z Mar 05 '22 at 04:14
  • Most relevant solution for me. I was trying to populate a db with data which had duplicates and Mongoose never stopped me from doing it in spite of adding a unique index. I now understand that indexes have to be created explicitly before populating duplicates, else mongoose skips creating the index. Remember to make it async for indexing to fully complete: `async Foo.createIndexes()` – Rajath Kedilaya Jun 24 '22 at 07:18
16

Steps to fix the issue:

1 . Add unique: true to the attributes.

let schema = new mongoose.Schema(
    {
        name: {
            type: String,
            unique: true,
            required: [true, "name required."],
        }
    }
);

module.exports = mongoose.model("role", schema);

2 . Drop the collection - for example role (last line)

  • This is simple way to fix - if you have already duplicate values.
  • You can also delete all records in collection so that there are duplicate values for the unique column (name above)

3 . Restart the Node.js server, that uses mongoose library.


How some of other answers here, are not correct?

  • The autoIndex option is set to true

    • not required, by default it is true
  • Restart the db

    • not required, you only need to restart the Node.js server

  • Follow above 3 steps in sequence
    • If you miss anything, do it 2 times
Manohar Reddy Poreddy
  • 25,399
  • 9
  • 157
  • 140
  • Just to point out you don't necessary need to delete or drop the affected collection. In place of step 2, I ran an update to all affected entries to update the field in some way to avoid duplicates. That worked fine for me. – Victory Ifebhor Sep 09 '22 at 18:53
14

This behavior happen also if you have left some duplicates in Mongo. Mongoose will try to create them in Mongo when your application starts up.

To prevent this, you can handle this error this way :

yourModel.on('index', function(err) {
  if (err?) {
    console.error(err)
  }
);
Pierre Maoui
  • 5,976
  • 2
  • 27
  • 28
11

Ok, i was able to resolve this from the mongoshell by adding the index on the field and setting the unique property:

db.<collectionName>.ensureIndex({fieldName: 1}, {unique: true});

Shell should respond in this way:

{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}

Now to test quickly from the mongoshell:

var doc = {fieldName: 'abc'};
db.<collectionName>.insert(doc)

Should give: WriteResult({ "nInserted" : 1 })

But when repeating again:

db.<collectionName>.insert(doc)

Will give:

WriteResult({
    "nInserted" : 0,
    "writeError" : {
        "code" : 11000,
        "errmsg" : "insertDocument :: caused by :: 11000 E11000 duplicate key error index: fuelConsumption.users.$email_1  dup key: { : \"martyna@martycud.com\" }"
    }
})
woj.sierak
  • 513
  • 6
  • 17
7

According the documentation : https://docs.mongodb.com/v2.6/tutorial/modify-an-index/

To modify an existing index, you need to drop and recreate the index.

DO NOT RESTART MONGO !

1 - drop the collection

db.users.drop()

2 - reindex the table

db.users.ensureIndex({email: 1, type: 1}, {unique: true})
Damien Romito
  • 9,801
  • 13
  • 66
  • 84
7

Mongoose is a little loose when enforcing unique indices from the application level; therefore, it's preferred to either enforce your unique indices from the database itself using the mongo cli or explicitly tell mongoose that you're serious about the unique index by writing the following line of code just after your UserSchema:

UserSchema.index({ username: 1, email: 1 }, { unique: true});

This will enforce the unique index on both username and email fields in your UserSchema. Cheers.

moeabdol
  • 4,779
  • 6
  • 44
  • 43
7

Mongoose will silently fail to add a unique index when either:

  1. The collection already has an index of the same name
  2. The collection already contains documents with duplicates of the indexed field

In the first case, list the indexes with db.collection.getIndexes(), and drop the old index with db.collection.dropIndex("index_name"). When you restart the Mongoose application it should correctly add the new index.

In the second case you need to remove the duplicates before restarting the Mongoose application.

Derek Hill
  • 5,965
  • 5
  • 55
  • 74
4

an old question, but for anyone still having this issue, you probably are not applying indexes properly:

if you have autoIndex in connection options set to false then one option would be to make it a true or remove this property altogether which would revert it to its default which is true, HOWEVER, this is not recommended in production as it would cause a hit to performance, the better approach would be to explicitly call createIndexes on your model, which would properly create the indices as defined in your schema.

so the syntax for the example in the original question can be as follows:

const userSchema = new mongoose.Schema({
  email: { type: String, required: true, index: true, unique: true },
  // other fields
});

// methods, statics, hooks... etc

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

User.createIndexes();

module.exports = User;

Waddah
  • 2,459
  • 1
  • 14
  • 5
  • Using atlas and mongodb, using the .createIndexes() method was actually the only thing that worked... not sure what changed, I never had problems using unique true but for some reason this time, only this worked – Peege151 Feb 07 '23 at 22:58
3

mongoose-unique-validator

How to use this plugin:

1) npm install --save mongoose-unique-validator

2) in your schema follow this guide:

// declare this at the top
var mongoose = require('mongoose');
var uniqueValidator = require('mongoose-unique-validator');

// exampleSchema = mongoose.Schema({}) etc...

exampleSchema.plugin(uniqueValidator);

// module.exports = mongoose.model(...) etc....

3) mongoose methods

When using methods like findOneAndUpdate you will need to pass this configuration object:

{ runValidators: true, context: 'query' }

ie. User.findOneAndUpdate(
      { email: 'old-email@example.com' },
      { email: 'new-email@example.com' },
      { runValidators: true, context: 'query' },
      function(err) {
        // ...
    }

4) additional options

  1. case insensitive

    use the uniqueCaseInsensitive option in your schema

    ie. email: { type: String, index: true, unique: true, required: true, uniqueCaseInsensitive: true }

  2. custom error messages

    ie. exampleSchema.plugin(uniqueValidator, { message: 'Error, expected {PATH} to be unique.' });

Now you can add/delete the unique property to your schemas without worrying about restarting mongo, dropping databases, or creating indexes.

Caveats (from the docs):

Because we rely on async operations to verify whether a document exists in the database, it's possible for two queries to execute at the same time, both get 0 back, and then both insert into MongoDB.

Outside of automatically locking the collection or forcing a single connection, there's no real solution.

For most of our users this won't be a problem, but is an edge case to be aware of.

Isaac Pak
  • 4,467
  • 3
  • 42
  • 48
3

Newest answer: there is no need to restart mongodb at all, if colleciton has same name indexes already, mongoose will not recreate your indexes again, so, drop colleciton's existing indexes firstly, and now, when you run mongoose, it will create new index, above process solved my problem.

chen Jacky
  • 507
  • 4
  • 8
3

Restarting and using plugins didn't work for me plus it a little overkill to use plugins for something we're all sure mongo could do on it's own.

So here's the fix. in your connect function add this to the options object(2nd param)

const options = {
  autoIndex: true, //this is the code I added that solved it all
}
mongoose.connect(process.env.MONGO_URI, options);
Josef
  • 2,869
  • 2
  • 22
  • 23
pius
  • 31
  • 2
2

I faced the same issue for awhile and did a lot of searching and the solution for me was the createIndexes() function.

I wish that helps.

so the code will be like that.

User = new Schema ({
   email: {type: String, unique: true}
});
User.createIndexes();
1

You can also resolve this issue by dropping the index;

let's assume you want to remove the unique index from collection users and field username, type this:

db.users.dropIndex('username_1');

Ademola Adegbuyi
  • 934
  • 1
  • 16
  • 25
1

If the table/collection is empty, then create unique index for the field:

db.<collection_name>.createIndex({'field':1}, {unique: true})

If the table/collection is not empty, then drop the collection and create index:

db.<collection_name>.drop()
db.<collection_name>.createIndex({'field':1}, {unique: true})

Now restart mongoDB.

TrampolineTales
  • 808
  • 3
  • 14
  • 31
1

check that autoIndex in the schema is true, it maybe set false(default true) when you use mongoose.connect options

Lee Quinze
  • 11
  • 1
1

What worked for me- dropping collection

what did not work for me - restart server, restart mongo

vishnu p s
  • 51
  • 6
1

None of these answers worked for me (had to create my indexes manually), and it was because the indexes were being created in the background and I was seeding data as soon as a connection was established. This meant the data was added before the indexes were created, and apparently even though there was no duplicate, the indexes would not be created once this happens (according to the docs this shouldn't be so but it is what appears to have happened). There are two ways I know of to solve this without manually creating indexes:

  1. The first- and best imho- is to use mongoose.connection.syncIndexes() after creating a connection to reconstruct the indexes of all collections to match all schemas (it will even create collections for you in the process), and only when this is complete (.then() or await) do you seed data or process requests.

  2. An alternative is to use mongoose.model.createIndexes() to create indexes for the specific models you want to ensure indexes for; you also have to do this before seeding/processing.

Apparently there was also an ensureIndexes() method for this

Tofi A.
  • 83
  • 1
  • 7
1

I fixed this issue by manually adding indexes in mongodDB compass.

Steps I did - I have MongoDB compass installed on my computer

  1. Connect to my database
  2. Select collections - In my case it's the users collection.
  3. On users collection, You will see the tab menu and select indexes
  4. On the tab indexes, click the button Create Index. The popup will show up
  5. In the popup, there is an index field which has the list of all the fields. Select one of the field that you want it to be unique. In my case it's the email
  6. On the select type, click 1(asc)
  7. On the options, select the checkbox Create unique index
  8. Finally, click the 'Create indexes' button to save it.

And that is it. The unique functionality should work. Don't forget to add it in your schema.

e.g

email: {
    type: String,
    required: [ true, 'Email is required.' ],
    unique: true,
    trim: true,
    lowercase: true,
    minLength: [ 5, 'Email is too short or invalid' ],
    maxLength: [ 40, 'Email is too long or invalid' ],
    validate: [ validator.isEmail, 'Invalid email' ]
},
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
0

If you are using the option autoIndex: false in the connection method like this:

mongoose.connect(CONNECTION_STRING, { autoIndex: false });

Try removing that. If that doesn't work, try restarting mongodb as many suggested in this thread.

Rafael Mejía
  • 485
  • 4
  • 9
0

You can define your schema as

User = new Schema ({
  email: {
      type: String,
      unique: true
  }
})

But maybe this will not work when there is already a document and after that, you have changed the schema of the User. You can create an index on email for this User collection if you don't want to drop the collection. Using this command you can create an index on email.

db.User.createIndex({email:1},{unique: true})

Or you can just drop the collection and add the user again.
For Dropping the collection, you can enter this:

db.User.drop()
Pulkit Aggarwal
  • 2,554
  • 4
  • 23
  • 33
0

When I encountered this problem, I tried dropping the database, re-starting the server (nodemon) many times and none of the tricks didn't work at all. I found the following work around, through Robo 3T:

  1. In the Robo 3T, double click on the Database to open the collections
  2. Open the collections to reveal the Collection in question. Make sure your collections are empty, to begin with
  3. Right-click on the Indexes Folder. By default, you will see the _id_ as the default. Now, choose Add Index
  4. Choose a name, say email for e-mail field, for example
  5. Provide Keys as JSON. For example

    {
       "email": 1
    }
    
  6. Click on the Unique checkbox

  7. Save

This will make sure no duplicate emails are saved in the MongoDB.

Balepur
  • 138
  • 1
  • 7
0

Make sure your collection has no redundant of the field you just put the unique index on.

Then just restart your app (mongoose). It just silently add index fails.

Zero0Ho
  • 166
  • 1
  • 1
  • 9
0

Removing all documents from the collection:

db.users.remove({})

And a restart, as others mentioned, worked for me

StefanBob
  • 4,857
  • 2
  • 32
  • 38
0

If your MongoDB is working as a service (easy way of finding this is if you do not need to connect to the database without starting the mongod.exe file via terminal), then after doing the changes you might need to restart the service and/or drop your database fully.

It is quite strange because for some users just dropping a single collection worked. Some of them just needed to drop the database. But those did not worked for me. I dropped the database, then restarted the MongoDB Server service.

To restart a service search Services on the Windows search bar then find the MongoDB service, double click to open then stop and start the service again.

If the other methods did not work for you, I believe this will do the job.

nick
  • 367
  • 3
  • 15
0

In my case mongoose was outdated. i checked it by running npm outdated on CMD. and updated 'mongoose'.

Please tell if that worked for you as well.

0

When you connect your database with the application add this option: "audoIndex: true" for example in my code I did this:

const options = {
// your options go here
...
// this code is the solution
audoIndex: true
}
mongoose.connect(DB_URI, options);

I also dropped the collection that I have problem with and recreated it to make sure that it will work. I found this solution at: https://dev.to/emmysteven/solved-mongoose-unique-index-not-working-45d5 I also tried solutions like "restart MongoDB" but didn't work for me.

Hasan
  • 155
  • 1
  • 2
  • 13
0

In my case we need define schema.index to create our indexes. Please check the Mongoose documentations Indexes https://mongoosejs.com/docs/guide.html#indexes,

  • Note, after make changes in your schema, remember the restart the server to check.

Now take a look in code below to test:

const schemaUser = new mongoose.Schema(
  {
    username: {
      type: String,
      required: true,
      index: true,
      unique: true,
      dropDups: true,
    },
    hash: String,
    created: {
      type: Date,
      default: Date.now,
    },
  },
  {
    autoCreate: true, // auto create collection
    autoIndex: true, // auto create indexes
  }
)
// define indexes to be create
schemaUser.index({ username: 1 })

const User = mongoose.model('Users', schemaUser)
const newUser = new Users({ username: 'wintzer' })
newUser.save(function (err) {
  if (err) console.log(err)
})
Henrique Van Klaveren
  • 1,502
  • 14
  • 24
0

in your connect function do not forget to mention useCreateIndex: true

mongoose.connect(url, {
  useNewUrlParser: true,
  useCreateIndex: true,
  useFindAndModify: false,
  useUnifiedTopology: true,
})
Paul Roub
  • 36,322
  • 27
  • 84
  • 93
Hicham Mounadi
  • 429
  • 6
  • 8
0

You don't really need to use 'unique: true' or the mongoose-unique-validator plugin, you can simply use a custom async validate() in your type definition that uses countDocuments():

Inside your Schema... (assuming your Schema is for a User model)

email: {type: String, required: true, trim: true, lowercase: true, async validate(value) {
    const count = await mongoose.models.User.countDocuments({email: value});
    if (count > 0) {
        const existing = await  mongoose.models.User.findOne({email: value});
        if (!(existing._id.toString() === this._id.toString())) {
            throw new Error("Email not unique");
        }
    }
}}
0

If you wouldn't have specified to auto index the data which means to check for uniqueness, mongoose wouldn't do that

Simply make them to true while connecting to the database

mongoose.connect('connection url', {
    useUnifiedTopology: true,
    useNewUrlParser: true,
    useCreateIndex: true, //make this true
    autoIndex: true, //make this also true
})
.then(() => {
    console.log('Connected to mongoDB');
});
Alaeddine
  • 49
  • 1
0

For anyone still facing this issue, the above answers didn't solve my problem. What did solve it was going to mongodb compass, then open the collection, then go to indexes tab and click the create an index button, then in the options tick the create unique index checkbox, then create that index.

Abdi mussa
  • 171
  • 11
0

In my case helps to handle index error event

const userSchema = new mongoose.Schema({
    name: {type: String, required: true, trim: true,},
    password: {
        type: String, required: true, trim: true, minLength: 7,
        validate(value) {
            if (value.includes('password')) {
            }
        }
    },
    email: {
        type: String, required: true, index: true, unique: true, trim: true, lowercase: true,
        validate(value) {
            if (!validator.isEmail(value)) {
                throw new Error('Email is invalid');
            }
        }
    },
    age: {
        type: Number, default: 1,
        validate(value) {
            if (value <= 0) {
                throw new Error('Age must be a positive number');
            }
        }
    }
}, {
    timestamps: true,
    get: v => v.toDateString()
})

const User = mongoose.model('User', userSchema);
    
User.on('index', function (err) {
    if (err) console.error(err); // error occurred during index creation
})

Details in documentation: https://mongoosejs.com/docs/api.html#model_Model.ensureIndexes

keraj
  • 31
  • 1
  • 6