9

I want to make the key email unique across that collection but i cant getting this working, here is my server code.

// Create a schema
var userSchema = new mongoose.Schema({
    email: { type: String, required: true},
    password: String
});

var userModel = mongoose.model("user", userSchema);

router.post('/postuser', (req, res) => {
    console.log('Requested data to server: ' + JSON.stringify(req.body._user));
var user = new userModel({
    email: req.body._user.email,
    password: req.body._user.password
});
// user.isNew = false;
user.save((err, data) => {
    console.log('Analyzing Data...');
    if(data) {
        console.log('Your data has been successfully saved.');
        res.json(data);
}
else {
  console.log('Something went wrong while saving data.');
  console.log(err);
  res.send(err);
}

})
});

Note: I also try email: { type: String, required: true, unique: true} but its not working and show below error.

name: 'MongoError',

message: 'E11000 duplicate key error collection: hutreservationsystem.users

index: _Email_1 dup key: { : null }',

driver: true,

code: 11000,

index: 0,

errmsg: 'E11000 duplicate key error collection: hutreservationsystem.users index: _Email_1 dup key: { : null }',

getOperation: [Function],

toJSON: [Function],

toString: [Function] }

Ahmer Ali Ahsan
  • 5,636
  • 16
  • 44
  • 81
  • 2
    using unique:true is the right way to go... you probably have to flush your database though before it works – FluffyNights May 14 '17 at 10:43
  • I concur with @FluffyNights, `unique : true` is how you create a unique index for a field. You're not explaining what _"its not working"_ means: do you get an error when you add the unique index (if so, you need to clean up your database, because you can only add a unique index if there are no duplicates in the database already)? Do you get errors when inserting new data (if so, it may be because when you try to add a duplicate in a unique index, MongoDB throws an error)? – robertklep May 14 '17 at 12:39
  • @robertklep Kindly check my updated question. – Ahmer Ali Ahsan May 14 '17 at 18:59
  • @AhmerAliAhsan there are probably documents in your database that have an empty `email` field (which you should probably remove; your schema depends on `email` being defined anyway). – robertklep May 14 '17 at 19:01
  • @robertklep I am providing user object while sending request to server. Also at the first time data was successfully inserted in database but at second time its shows me above error. – Ahmer Ali Ahsan May 14 '17 at 19:04
  • @AhmerAliAhsan the error suggests that `email` is undefined or `null`. Before saving, add `console.log(user)` to make sure it looks okay. – robertklep May 14 '17 at 19:08
  • @AhmerAliAhsan oh, and by the way: if you are adding a new user with an existing e-mail address, you _will_ get that error, because you told MongoDB that e-mail addresses should be unique, so it's an error when you try to add a new document with an existing e-mail address. You have to specifically check for that particular error if you want to tell your users that they can't register that e-mail address. – robertklep May 14 '17 at 19:17
  • @robertklep yes it shows me error when I enter same email. But also it shows me same error when I enter different email address. I don't know what I am doing wrong in my code. – Ahmer Ali Ahsan May 15 '17 at 06:17
  • @AhmerAliAhsan did you add `console.log(user)` before saving to make sure the document is alright? – robertklep May 15 '17 at 06:48
  • @robertklep Yes I checked it. It shows me my user data before saving. I also encounter another problem here if I provide option like unique false or index false then flush my database and regenerate it. Its automatically generate unique indexes so if I send other data to my collection it shows me above error which I mentioned above. – Ahmer Ali Ahsan May 15 '17 at 07:31
  • How do you flush the database? Simply removing all data from the database won't remove any indexes, for that you need to either drop the collection, or drop the indexes. – robertklep May 15 '17 at 07:33
  • @robertklep I drop my collection first. then run my node server again. – Ahmer Ali Ahsan May 15 '17 at 07:35
  • @AhmerAliAhsan are you sure that when you regenerate the database, all the data you are inserting into it is valid (each user has an e-mail address and a password, and also all e-mail addresses are unique)? – robertklep May 15 '17 at 08:00
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/144229/discussion-between-robertklep-and-ahmer-ali-ahsan). – robertklep May 15 '17 at 08:07

6 Answers6

10

A short answer using this tool mongoose-unique-validator

npm install --save mongoose-unique-validator

and in your model

var mongoose = require('mongoose')
var uniqueValidator = require('mongoose-unique-validator')
var userSchema = new mongoose.Schema({
    email: { type: String, required: true, unique: true},
    password: String
});

userSchema.plugin(uniqueValidator)
var userModel = mongoose.model("user", userSchema);

That's it! (Notice unique: true)

Now, there is no email duplication in your collection.

Bonus! : you can access err

.catch(err => console.log(err))

so in your example

// user.isNew = false;
user.save((err, data) => {
    console.log('Analyzing Data...');
    if(data) {
        console.log('Your data has been successfully saved.');
        res.json(data);
}
else {
  console.log('Something went wrong while saving data.');
  console.log(err);
  res.send(err);
}

accessing err >> so you can res.send(err.message) >> 'Validation failed'

{
    message: 'Validation failed',
    name: 'ValidationError',
    errors: {
        email: {
            message: 'Error, expected `email` to be unique. Value: `example@gmail.com`',
            name: 'ValidatorError',
            kind: 'unique',
            path: 'email',
            value: 'example@gmail.com'
        }
    }
    }
Ahmed Younes
  • 964
  • 12
  • 17
  • 1
    Thank you, this worked for me. I've been trying to keep my usernames unique and avoid duplicates being added. Now I get an error which I can surface to the user. – Freddie May 14 '20 at 23:50
8

Async Custom Validator

var userSchema = new mongoose.Schema({
    password: String,
    email: {
        type: String,
        lowercase: true,
        required: true,
        validate: {
            isAsync: true,
            validator: function(value, isValid) {
                const self = this;
                return self.constructor.findOne({ email: value })
                .exec(function(err, user){
                    if(err){
                        throw err;
                    }
                    else if(user) {
                        if(self.id === user.id) {  // if finding and saving then it's valid even for existing email
                            return isValid(true);
                        }
                        return isValid(false);  
                    }
                    else{
                        return isValid(true);
                    }

                })
            },
            message:  'The email address is already taken!'
        },
    }
});

You may like to change the validator code to es6.

Talha Awan
  • 4,573
  • 4
  • 25
  • 40
  • Your code works on when I enter same email address. But when I enter different email address its shows me error which I have shown in my updated question. – Ahmer Ali Ahsan May 15 '17 at 06:20
  • 1
    The error shows that you're trying to insert null twice. Only one null is allowed to maintain uniqueness. If you're using the above code you should remove the uniqueness constraint from email `{ unique: true}`. Also check [this](http://stackoverflow.com/a/24430345/) – Talha Awan May 15 '17 at 12:28
  • Now my code is working. you're right I am trying to insert my model twice. Thats why I am getting above error. – Ahmer Ali Ahsan May 15 '17 at 17:07
2
email: {
    type: String,
    trim: true,
    unique: true, // note - this is a unqiue index - not a validation
    validate: {
        validator: function(value) {
            const self = this;
            const errorMsg = 'Email already in use!';
            return new Promise((resolve, reject) => {
                self.constructor.findOne({ email: value })
                    .then(model => model._id ? reject(new Error(errorMsg)) : resolve(true)) // if _id found then email already in use 
                    .catch(err => resolve(true)) // make sure to check for db errors here
            });
        },
    }
},
  • Welcome to StackOverflow! Please [edit your answer](https://stackoverflow.com/posts/59009408/edit) to include an explanation for your code, and how your solution differs from the other ones here. This will increase your answer's quality and make it more likely that it'll get upvoted :) – Das_Geek Nov 23 '19 at 16:30
1

I implemented the following code to see if anything was wrong:

var mongoose = require('mongoose');
var bodyParser = require('body-parser');
var express = require('express');
var http = require('http');

var app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

var Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/test');

// Create a schema
var userSchema = new mongoose.Schema({
    email: { type: String, required: true, unique: true},
    password: String
});

var userModel = mongoose.model("user", userSchema);

app.post('/postuser', (req, res) => {
  console.log('Requested data to server: ' + JSON.stringify(req.body._user));
  var user = new userModel({
      email: req.body._user.email,
      password: req.body._user.password
  });
  // user.isNew = false;
  user.save((err, data) => {
      console.log('Analyzing Data...');
      if(data) {
          console.log('Your data has been successfully saved.');
          res.json(data);
      }
      else {
        console.log('Something went wrong while saving data.');
        console.log(err);
        res.send(err);
      }
  })
});

http.createServer(app).listen(3000, function(){
  console.log('Express server listening on port 3000');
});

And I made sure that no collection by the name of users existed in my local MongoDB database. Moreover, I used Postman for sending API requests to my server at http://localhost:3000. There seemed to be no issue as I continued to add users with differing email values. And I only got the following error when I entered an email with a duplicate value

{
  "code": 11000,
  "index": 0,
  "errmsg": "E11000 duplicate key error collection: test.users index: email_1 dup key: { : \"hot@mail.com\" }",
  "op": {
    "email": "hot@mail.com",
    "password": "1234567",
    "_id": "5919a3428c13271f6f6eab0f",
    "__v": 0
  }
}

These are the JSON requests that I sent:

{"_user": {"email": "hot@mail.com", "password": "1234"}}
{"_user": {"email": "sammy@mail.com", "password": "1234"}}
{"_user": {"email": "tommy@mail.com", "password": "1234"}}
{"_user": {"email": "tommy@mail.ae", "password": "1234567"}}
{"_user": {"email": "hot@mail.com", "password": "1234567"}}

The error mentioned above was sent back on the last request as the email hot@mail.com is repeated. If you view the link http://mongoosejs.com/docs/api.html#schematype_SchemaType-unique , you'll see that the E11000 error is only sent when the email entered is not unique. Moreover, your email can't be an empty string or not be present as that violates the required property.

Anas Ayubi
  • 227
  • 1
  • 3
  • 9
1

ES6:

const userSchema = new Schema({
    name: String,
    id: {
        type: String,
        required: true,
        unique: true,
        validate: async (value) => {
            try {
                const result = await userModel.findOne({ id: value })
                if (result) throw new Error("duplicity detected: id :" + value);
            } catch (error) {
                throw new Error(error);
            }
        }
    }
})

const userModel = mongoose.model<Document>('users', userSchema);

ES6 (TypeScript):

const userSchema = new Schema({
    name: String,
    id: {
        type: String,
        required: true,
        unique: true,
        validate: async (value: any): Promise<any> => {
            try {
                const result: Document | null = await userModel.findOne({ id: value })
                if (result) throw new Error("duplicity detected: id :" + value);
            } catch (error) {
                throw new Error(error);
            }
        }
    }
})

const userModel: Model<Document, {}> = mongoose.model<Document>('users', userSchema);
Rafi Henig
  • 5,950
  • 2
  • 16
  • 36
0

In your user schema set attribute email as unique (unique: true).

var userSchema = new mongoose.Schema({ email: { type: String, required: true,  unique: true}, });