user1082754
user1082754

Reputation:

Mongoose schema: Validating unique field, case insensitive

I have a userSchema like so:

var userSchema = new Schema({
    name: {
      type: String
    , required: true
    , validate: [validators.notEmpty, 'Name is empty']
    }
  , username: {
      type: String
    , required: true
    , unique: true
    , validate: [validators.notEmpty, 'Username is empty']
    }
});

The username field is supposed to be unique. Mongoose will throw an error if this username already exists in the database. However, it is not case insensitive, which I need it to be.

Am I right in thinking that the only way to achieve a case insensitive unique check is to write my own validation rule, which will perform a query on the collection? Is it OK to write validation checks like this, creating more connections to the collection? I will need to do something similar for email, too.

Upvotes: 20

Views: 32643

Answers (9)

aygunyilmaz
aygunyilmaz

Reputation: 419

... with mongoose on NodeJS that query:

const countryName = req.params.country;

{ 'country': new RegExp(`^${countryName}$`, 'i') };

or

const countryName = req.params.country;

{ 'country': { $regex: new RegExp(`^${countryName}$`), $options: 'i' } };

// ^australia$

or

const countryName = req.params.country;

{ 'country': { $regex: new RegExp(`^${countryName}$`, 'i') } };

// ^turkey$

A full code example in Javascript, NodeJS with Mongoose ORM on MongoDB

// get all customers that given country name
app.get('/customers/country/:countryName', (req, res) => {
    //res.send(`Got a GET request at /customer/country/${req.params.countryName}`);

    const countryName = req.params.countryName;

    // using Regular Expression (case intensitive and equal): ^australia$

    // const query = { 'country': new RegExp(`^${countryName}$`, 'i') };
    // const query = { 'country': { $regex: new RegExp(`^${countryName}$`, 'i') } };
    const query = { 'country': { $regex: new RegExp(`^${countryName}$`), $options: 'i' } };

    Customer.find(query).sort({ name: 'asc' })
        .then(customers => {
            res.json(customers);
        })
        .catch(error => {
            // error..
            res.send(error.message);
        });
});

Upvotes: 7

Simon Jerry
Simon Jerry

Reputation: 36

I had the same issue while working on a project.I simply make it simple with two lines of code. converting all incoming value to small letters .

let getUsername = req.body.username;
let username = getUsername.toLowerCase();

Upvotes: 0

Kristianmitk
Kristianmitk

Reputation: 4778

A collation with strength: 2 on the index solves this issue.

index: {
  unique: true,
  collation: {
    locale: 'en',
    strength: 2
  }
}

Place this in your Schema creation code as follows:

var userSchema = new Schema({
  ...
  username: {
    type: String,
    required: true,
    index: {
      unique: true,
      collation: { locale: 'en', strength: 2 }
    }
});

Note: make sure the index on the model gets updated - you might need to do this manually.

Upvotes: 14

vanduc1102
vanduc1102

Reputation: 6245

I use mongoose-unique-validator

Example :

const mongoose = require('mongoose');
const uniqueValidator = require('mongoose-unique-validator');
const { Schema } = mongoose;

const UserSchema = new Schema({
  name: {
    type: String,
    required: true,
    unique: true,
    index: true,
    maxlength: 100,
    trim: true,
    uniqueCaseInsensitive: true
  },
  username: {
    type: String,
    required: true,
    unique: true,
    index: true,
    maxlength: 100,
    trim: true,
    uniqueCaseInsensitive: true
  }
});

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

module.exports = mongoose.model('User', UserSchema);

Upvotes: 1

nilakantha singh deo
nilakantha singh deo

Reputation: 1006

The best way is to use already existing npm packages as shared below. https://www.npmjs.com/package/mongoose-unique-validator

To make it case sensitive you can follow uniqueCaseInsensitive in the same page.

No need to write your own validation logic when there is already a package available for this(follow Avinash's post too).

Upvotes: 1

Uday Hiwarale
Uday Hiwarale

Reputation: 4167

Very simple solution

username : {
        trim:true,
        //lowercase:true,

        type:String,
        required:[true, '{PATH} is required.'],
        match : [
            new RegExp('^[a-z0-9_.-]+$', 'i'),
            '{PATH} \'{VALUE}\' is not valid. Use only letters, numbers, underscore or dot.'
        ],
        minlength:5,
        maxlength:30,
        //unique:true

        validate : [
            function(un, cb){
                console.log(v);
                student.findOne({username:/^un$/i}, function(err, doc){
                    if(err) return console.log(err);
                    if(!_.isEmpty(doc)) return cb(false);
                    return cb(true);
                });
            },
            'Username already exists.'
        ]
    },

Here, I am using async validation and checking in my model student if same field exist. Use can obviously use regex if you want.

But I would not recommend this method, it just doesn't fit in my head.

Instead stick with { type: String, lowercase: true, trim: true, unique:true } approach and copy the original username to some other field in case you need it.

Upvotes: 0

Avinash
Avinash

Reputation: 419

I don't know if you are doing this in node. But you can use a npm like this: https://github.com/blakehaswell/mongoose-unique-validator to check unique validation on the fields of collections. Other way could be checking in the collection every time a new requests come. http://timstermatic.github.io/blog/2013/08/06/async-unique-validation-with-expressjs-and-mongoose/ You can refer the material here and use it the way suitable for your case.

Upvotes: 2

Sahil
Sahil

Reputation: 504

How about using a regular expression?

var pattern = [ /some pattern/, "{VALUE} is not a valid user name!" ];

{ type: String, match: pattern }

For further reference: http://mongoosejs.com/docs/api.html#schematype_SchemaType-required

Upvotes: -2

sqreept
sqreept

Reputation: 5534

What about using:

{ type: String, lowercase: true, trim: true }

to achieve your purpose?

Upvotes: 20

Related Questions