24

I'm building a MEAN app.

This is my Username schema, the username should be unique.

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

module.exports = mongoose.model('User', new Schema({ 
    username: { type: String, unique: true }
}));

On my post route I save the user like this:

app.post('/authenticate', function(req, res) {
        var user = new User({
            username: req.body.username
        });

        user.save(function(err) {
            if (err) throw err;

            res.json({
                success: true
            });

        });
    })

If I post with the same username again I get this error:

MongoError: insertDocument :: caused by :: 11000 E11000 duplicate key error index:

Can someone explain how instead of the error to send a json like { succes: false, message: 'User already exist!' }

Note: After I post the user I will automatically authentificate, dont need password or something else.

Philipp
  • 67,764
  • 9
  • 118
  • 153
Hiero
  • 2,182
  • 7
  • 28
  • 47

6 Answers6

64

You will need to test the error returned from the save method to see if it was thrown for a duplicative username.

app.post('/authenticate', function(req, res) {
  var user = new User({
    username: req.body.username
  });

  user.save(function(err) {
    if (err) {
      if (err.name === 'MongoError' && err.code === 11000) {
        // Duplicate username
        return res.status(422).send({ succes: false, message: 'User already exist!' });
      }

      // Some other error
      return res.status(422).send(err);
    }

    res.json({
      success: true
    });

  });
})
Martijn van Wezel
  • 1,120
  • 14
  • 25
Jason Cust
  • 10,743
  • 2
  • 33
  • 45
  • 2
    500 shouldn't be used as status code since it isn't internal server error. So you can use 400 for data conflict. – Deva Jun 27 '19 at 04:20
  • 1
    This treatment is not useful if you have more than one unique field because it always returns the same error code. – krekto Jul 04 '19 at 15:47
  • 2
    What if I have two unique fields in a document? How would give clear message which field is causing error. For example: we can have User with email and alias field both of which should be unique? – Prajeet Shrestha Jul 24 '19 at 20:20
  • You should do `if(err instanceof MongoServer && err.code === 11000)` instead, according to https://mongodb.github.io/node-mongodb-native/4.3/index.html#error-handling. You could `res.sendStatus(409)` http code instead of that object. Read here https://stackoverflow.com/questions/3290182/which-status-code-should-i-use-for-failed-validations-or-invalid-duplicates – Iglesias Leonardo Sep 23 '22 at 16:16
  • Use now `MongoServerError` instead of `MongoError`. – Gagan Nov 07 '22 at 09:35
8

You can also try out this nice package mongoose-unique-validator which makes error handling much easier, since you will get a Mongoose validation error when you attempt to violate a unique constraint, rather than an E11000 error from MongoDB:

var mongoose = require('mongoose');
var uniqueValidator = require('mongoose-unique-validator');

// Define your schema as normal.
var userSchema = mongoose.Schema({
    username: { type: String, required: true, unique: true }
});

// You can pass through a custom error message as part of the optional options argument:
userSchema.plugin(uniqueValidator, { message: '{PATH} already exists!' });
chridam
  • 100,957
  • 23
  • 236
  • 235
6

2022 Update. Looks like the err.name changed. Before, this error was returning as a MongoError, but now it is a MongoServerError. There's a whole story about Mongoose not handling MongoError directly, basically when a ServerError appears mongoose return it as it is.

NOTE: violating the constraint returns an E11000 error from MongoDB when saving, not a Mongoose validation error.## Heading ##

But now, this error is not a MongoError anymore, it's a MongoServerError now, which extends MongoError https://mongodb.github.io/node-mongodb-native/4.0/classes/mongoerror.html

Here two working examples:

app.post('/authenticate', function(req, res) {
  var user = new User({
    username: req.body.username
  });

  user.save(function(err) {
    if (err) {
      if (err.name === 'MongoServerError' && err.code === 11000) {
        // Duplicate username
        return res.status(422).send({ success: false, message: 'User already exist!' });
      }

      // Some other error
      return res.status(422).send(err);
    }

    res.json({
      success: true
    });

  });
})
async function store(req: Request, res: Response) {
    const { email, password }: IUser = req.body;

    const user: IUser = new User({
        email: email,
        password: await hashPassword(password),
    });

    user
        .save()
        .then(result => {
            return res.status(201).json({
                message: 'Successful registration.',
                data: { email: result.email },
            });
        })
        .catch(err => {    
            if (err.name === 'MongoServerError' && err.code === 11000) {
                //There was a duplicate key error
                return res.status(400).json({
                    message: 'Email already in use.',
                    data: { err },
                });
            }
            return res.status(400).json({
                message: "You didn't give us what we want!",
                data: { err },
            });
        });
}
Waifu_Forever
  • 87
  • 3
  • 7
  • This is working for me, but isn't it kinda messy? Is there a cleaner way to handle the error? – buryo Jul 29 '22 at 20:54
3

If you are using mongoose>4.5.0 you can use this error handling middleware based on their documentation:https://mongoosejs.com/docs/middleware.html#error-handling-middleware

//Checking for unique keys when you have multiple indexes
UserSchema.post("save", function (error, doc, next) {
  if (error.name === "MongoServerError" && error.code === 11000) {
    const keyPattern = Object.keys(error.keyPattern);
    const key = keyPattern[0];
    next(new Error(`${key} already taken!`));
  } else {
    next();
  }
});
0

Here's how you validate it using the type error instead of string:

// your own error in a diff file
class UniqueError extends Error {
  constructor(message) {
    super(message)
  }
}

// in your service file
const { MongoError } = require('mongodb')

class UserService {
  async createUser(userJSON) {
    try {
      return await User.create(userJSON)
    } catch (e) {
      if (e instanceof MongoError && e.code === 11000) {
        throw new UniqueError('Username already exist')
      }
      throw e
    }
  }
}

// in your controller file
class UserController {
  async create(req, res) {
    const userJSON = req.body
    try {
      return res.status(201).json(await userService.createUser(userJSON))
    } catch (e) {
      if (e instanceof UniqueError) {
        return res.status(422).json({ message: e.message })
      }
      return res.status(500).json({ message: e.message })
    }
  }
}
Miko Chu
  • 1,087
  • 14
  • 22
-1

Try this:

app.post('/authenticate', function(req, res) {
        var user = new User({
            username: req.body.username
        });

        user.save(function(err) {
            if (err) {
                // you could avoid http status if you want. I put error 500 
                return res.status(500).send({
                    success: false,
                    message: 'User already exist!'
                });
            }

            res.json({
                success: true
            });

        });
    })
Jose Mato
  • 2,709
  • 1
  • 17
  • 18
  • Thanks, works as excepted, I was thinking about this but I thought that error can return something else than the existing user error, I am wrong? – Hiero Jun 02 '15 at 10:53
  • 4
    Yes, but this is the common case: "try to save a user" and if there is an error you think that the user is duplicate. You could show to the user a simple message like "user already exist" and then store on your logs ther real error to see if there are mistakes with database. – Jose Mato Jun 02 '15 at 10:56
  • Not recommended, specific error handling should be done. Error code and error message should at least be handled. – Diego Gallegos Nov 08 '18 at 22:58
  • Agree Diego Gallegos, for a production use case always it's recommended handle the logic vs operational errors, is not the same "record not found" than "database can't connect / timeout / whatever" – Jose Mato Nov 09 '18 at 10:53
  • 1
    better with try-catch and read the number of error – Marco Pestrin Mar 09 '21 at 22:54
  • Totally agree Marco, to simplify things is not necessary but to really know the error that MongoDB is returning then it's better to check the number code as you indicated – Jose Mato Mar 11 '21 at 13:45