Hiero
Hiero

Reputation: 2205

How to catch the error when inserting a MongoDB document which violates an unique index?

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.

Upvotes: 24

Views: 40030

Answers (6)

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();
  }
});

Upvotes: 3

Miko Chu
Miko Chu

Reputation: 1382

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

Upvotes: 0

Waifu_Forever
Waifu_Forever

Reputation: 107

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 },
            });
        });
}

Upvotes: 7

Jason Cust
Jason Cust

Reputation: 10899

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
    });

  });
})

Upvotes: 64

chridam
chridam

Reputation: 103425

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!' });

Upvotes: 8

Jose Mato
Jose Mato

Reputation: 2799

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
            });

        });
    })

Upvotes: -1

Related Questions