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?
Oops! You just have to restart mongo.
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()
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/
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
index: true
for my unique index to be created.
async Foo.createIndexes()
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)
}
);
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\" }"
}
})
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
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})
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.
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.
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.
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.
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);
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');
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.
check that autoIndex in the schema is true, it maybe set false(default true) when you use mongoose.connect options
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();
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;
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.
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()
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.
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.
Removing all documents from the collection:
db.users.remove({})
And a restart, as others mentioned, worked for me
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.
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.
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.
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)
})
in your connect function do not forget to mention useCreateIndex: true
mongoose.connect(url, {
useNewUrlParser: true,
useCreateIndex: true,
useFindAndModify: false,
useUnifiedTopology: true,
})
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");
}
}
}}
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');
});
Success story sharing