使用Mongoose自定义错误消息

39

根据mongoose的文档,您可以像下面这样在模式中设置自定义错误消息:

 var breakfastSchema = new Schema({
  eggs: {
    type: Number,
    min: [6, 'Too few eggs'],
    max: 12
  },
  bacon: {
    type: Number,
    required: [true, 'Why no bacon?']
  }
});
所以我想做类似的事情:
var emailVerificationTokenSchema = mongoose.Schema({
   email: {type: String, required: true, unique: [true, "email must be unique"]},
   token: {type: String, required: true},
   createdAt: {type: Date, required: true, default: Date.now, expires: '4h'}
});

这个想法是,当你尝试保存其中一个令牌时,如果已经存在冲突的令牌,它将会弹出一个错误消息,上面写着“电子邮件必须唯一”。

然而,当我执行像这样的操作(保存具有相同电子邮件的令牌)时:

verificationToken.save( function (err) {
    if (err) {
      return console.log(err);
    }
    else {
      return console.log(err);
    }
});
我一直得到这个:
'E11000 duplicate key error: index ___.emailVerificationToken.$email_1 dup key: { : "_____@wdad.com

有什么想法吗?自定义信息不支持唯一参数吗?这是可行的处理方式吗?

11个回答

58
在Mongoose中,独特性不是一个验证参数(例如required),而是告诉Mongoose为该字段在MongoDB中创建唯一索引。唯一性约束完全在MongoDB服务器中处理。当您添加具有重复键的文档时,MongoDB服务器会返回您显示的错误(E11000...)。如果您想创建自定义错误消息,则必须自己处理这些错误。 Mongoose文档("Error Handling Middleware")为您提供了如何创建自定义错误处理的示例。
emailVerificationTokenSchema.post('save', function(error, doc, next) {
  if (error.name === 'MongoError' && error.code === 11000) {
    next(new Error('email must be unique'));
  } else {
    next(error);
  }
});

(尽管这不会为您提供特定的字段,以表明唯一性约束失败的原因)


你的回答一如既往地非常准确。如果可以的话,我想请教一下关于 QueryModel 的区别,因为我看到它们都有许多共同的方法,比如 Model.deleteMany()Query.prototype.deleteMany() - Medi
@Alexander 我非常确定 Model.deleteMany 创建(并返回)一个 Query 实例,然后在该实例上调用 Query.prototype.deleteMany。该查询实例表示“删除多个”操作。 - robertklep
仅供更新,现在Mongoose使用“MongoServerError”作为错误名称,而不是“MongoError”。 - Minas-Theodoros Charakopoulos

14

虽然您尝试了直接实现它,但您可能需要查看 mongoose-unique-validator ,它允许定义自定义错误消息以确保唯一性。

具体来说,关于自定义错误的部分应该对您有帮助。

要达到您想要的

"电子邮件必须是唯一的"

可以类似地实现如下:

var uniqueValidator = require('mongoose-unique-validator');
...
emailVerificationTokenSchema.plugin(uniqueValidator, { message: '{PATH} must be unique' });

这个插件正是我所需要的! - Adam
我点击了你发的链接,现在正在尝试使用自定义错误信息。我不仅想改变消息部分(就像你举的例子那样),而是要改变整个响应。 所以我运行了:uniqueValidator.defaults.message = 'Error, expected {PATH} to be unique.' 然后执行了:userSchema.plugin(uniqueValidator); 但是它报错了。你知道是为什么吗? - ImNeos

5
verificationToken.save( function (err) {
    if (err) {
      return res.status(400).send({
          message: (err.name === 'MongoError' && err.code === 11000) ? 'Email already exists !' : errorHandler.getErrorMessage(err)
      });
    }
    else {
      return console.log('No Error');
    }
  });

4

来自Schema的唯一值的错误消息真的不太友好。

这让我很困扰,但当我看到错误对象中剩余的键时,我就能够解决这个问题了。

在返回错误消息时,

 document.save().then(() => {
        return res.status(201).json({
          statusText: "created",
          message: "document created successfully",
          data: brand,
        });
      })
      .catch((error) => {

        // Set custom error for unique keys
        let errMsg;
        if (error.code == 11000) {
          errMsg = Object.keys(error.keyValue)[0] + " already exists.";
        } else {
          errMsg = error.message;
        }
        res.status(400).json({ statusText: "Bad Request", message: errMsg });
      });

如果您的模式中有多个唯一键,则此功能非常有用。 - whoismuktar

3
感谢所有的回答,我已经解决了我的问题。
我的架构如下。
var User = new Schema({
  username: {type: String, required: [true, "Username is required"], unique: true},
  email: {type: String, required: [true, "Email is required"], unique: true},
  password: {type: String, required: true, unique: false},
});

我建议使用像函数一样的方式。

你可以像这样使用:

User.post("save", function (error, doc, next) {

  if (error.keyValue.email != null && error.name === "MongoError" && error.code === 11000) {

    console.log("Email must be unique");
  } else if (error.keyValue.username != null && error.name === "MongoError" && error.code === 11000) {
    console.log("Username must be unique");
  } else {
    console.log("not found any idea, search for another reasons");
  }
});

2

更新Mongoose 5.x.x版本

MySchema.post('save', function (error, doc, next) {
    if (err.name === 'BulkWriteError' && error.code === 11000) 
        next(new Error('This item already exists, please try again'));
    else next(error);
});

1
你是对的,但条件语句的第一部分是不必要的。if (error.code === 11000) 就足够了... - ValRob

2

更新的mongoose版本5.x.x支持这个特定要求。

https://mongoosejs.com/docs/middleware.html#error-handling-middleware

您可以为模式添加一个POST方法,并按照以下方式操作: //保存用户后,如果用户重复,则返回错误
userSchema.post('save', function(error: any, doc: any, next: any) {
  if (error.name === 'MongoError' && error.code === 11000) {
    next(new Error('user_exists'));
  } else {
    next();
  }
});

2

只需应用一个中间件。

recordingSchema.post('save', function (error, _, next) {
    next( error.code === 11000 
      ?   new Error('This item field already exist')
      :   error)
});

2
您可以添加自定义的异步验证器。例如:
 {
      validator: (value) => {
        return UserModel.findOne({ email: value })
          .then(user => Promise.resolve(user == null))
          .catch(error => Promise.reject(false));
      },
      message: "User with this email already exists"
 }


1
这是我处理它的方式:

schema.post('save', function (error, doc, next) {
    let errorMessage

    // map with all unique items you defined in your schema 
    const errorsMap = {
        email: 'your message for existing email',
        otherUniqueItem: 'your message for existing item',
    }

    if (error.code === 11000) {
        errorMessage = Object
            .keys(error.keyValue)
            .map(key => errorMap[key])
            .join(', ')
    } else {
        errorMessage = error.message
    }

    if (errorMessage) next(new Error(errorMessage))
    else next()
})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接