ChatGPT解决这个技术问题 Extra ChatGPT

Mongoose Unique index not working!

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?

Bad news, it is still problem with mongod v2.4.3, mongoose v3.6.20
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.
Check this post if you adding a new unique field in the existing model- stackoverflow.com/questions/24430220/…

f
foobar

Oops! You just have to restart mongo.


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?
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?
This isn't true. You don't need to restart mongo to add an index.
for the unique thing.. I HAD to restart mongo.. and it worked.. Thanks!
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?
j
jpillora

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()

db.dropDatabase() wipes your database!
@IsaacPak he said it casually like it was some common operation 😂
N
Neil Strain

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/


How to drop a certain index?
s
solstice333

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


The only answer to the question that addresses the problem rather than revolves around it.
This answer helped me. All I was missing was the index: true for my unique index to be created.
for some reason passing unique: true works but as indexes: {...unique:true} dont.. ty
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()
P
Pierre Maoui

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)
  }
);

w
woj.sierak

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\" }"
    }
})

M
Manohar Reddy Poreddy

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

not required, by default it is true

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

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

If you miss anything, do it 2 times


D
Damien Romito

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})

It says drop index not the collection
m
moeabdol

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.


A bit late to the party, but what good is an ORM that's "a little loose when enforcing unique indices"
What good are belittling comments that don't help in any way. This solution is solid and production ready.
D
Derek Hill

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

The collection already has an index of the same name 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.


I
Isaac Pak

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

case insensitive use the uniqueCaseInsensitive option in your schema ie. email: { type: String, index: true, unique: true, required: true, uniqueCaseInsensitive: true } 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.


c
chen Jacky

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.


J
Josef

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);

A
Ademola Adegbuyi

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');


T
TrampolineTales

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.


L
Lee Quinze

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


S
Samir Hosny

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();

W
Waddah

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;


R
Rafael Mejía

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.


P
Pulkit Aggarwal

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()

B
Balepur

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:

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

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


what is the meaning of 1 here in the object { "email": 1 }?
Z
Zero0Ho

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.


S
StefanBob

Removing all documents from the collection:

db.users.remove({})

And a restart, as others mentioned, worked for me


n
nick

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.


n
nishant srivastava

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.


What version were you using?
H
Hasan

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.


H
Henrique Van Klaveren

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)
})

P
Paul Roub

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

mongoose.connect(url, {
  useNewUrlParser: true,
  useCreateIndex: true,
  useFindAndModify: false,
  useUnifiedTopology: true,
})

g
gimbeljw333

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");
        }
    }
}}

A
Alaeddine

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');
});