插入违反唯一索引的 MongoDB 文档时如何捕获错误?

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

我正在构建一个 MEAN 应用程序。

这是我的用户名架构,用户名应该是唯一的。

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

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

在我的 post 路线上,我这样保存用户:

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

        });
    })

如果我再次使用相同的用户名post,我会得到这个错误:

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

有人可以解释如何发送 json 之类的 { succes: false, message: 'User already exist!' }

而不是错误

注意:我post用户后我会自动认证,不需要密码或其他东西。

试试这个:

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

        });
    })

您也可以试试这个不错的包 mongoose-unique-validator,它可以让错误处理变得更容易,因为当您试图违反唯一性时,您会收到 Mongoose 验证错误约束,而不是来自 MongoDB:

的 E11000 错误
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!' });

您需要测试从保存方法返回的错误,看看它是否因用户名重复而抛出。

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

  });
})

以下是使用类型错误而不是字符串来验证它的方法:

// 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 Error('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 })
    }
  }
}

2022年更新。 看起来 err.name 已更改。之前,此错误 return 为 MongoError,但现在为 MongoServerError。有一个关于 Mongoose 不直接处理 MongoError 的完整故事,基本上当 ServerError 出现时 mongoose return 它是原样。

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

但是现在,这个错误不再是 MongoError,而是 MongoServerError,它扩展了 MongoError https://mongodb.github.io/node-mongodb-native/4.0/classes/mongoerror.html

这里有两个工作示例:

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