7
votes

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

6
using unique:true is the right way to go... you probably have to flush your database though before it worksFluffyNights
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
@robertklep Kindly check my updated question.Ahmer Ali Ahsan
@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
@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

6 Answers

8
votes

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.

5
votes

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: `[email protected]`',
            name: 'ValidatorError',
            kind: 'unique',
            path: 'email',
            value: '[email protected]'
        }
    }
    }
2
votes
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
            });
        },
    }
},
1
votes

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: { : \"[email protected]\" }",
  "op": {
    "email": "[email protected]",
    "password": "1234567",
    "_id": "5919a3428c13271f6f6eab0f",
    "__v": 0
  }
}

These are the JSON requests that I sent:

{"_user": {"email": "[email protected]", "password": "1234"}}
{"_user": {"email": "[email protected]", "password": "1234"}}
{"_user": {"email": "[email protected]", "password": "1234"}}
{"_user": {"email": "[email protected]", "password": "1234567"}}
{"_user": {"email": "[email protected]", "password": "1234567"}}

The error mentioned above was sent back on the last request as the email [email protected] 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.

1
votes

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

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

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