就像所有其他框架一样,Mongoose提供了一种方法,在你将数据保存到数据库之前验证数据。数据验证对于确保 "坏 "数据不会被持久化在你的应用程序中非常重要。在向MongoDB插入数据时,使用Mongoose的一个好处是它对数据模式的内置支持,以及在数据被持久化时对其进行自动验证。如果没有Mongoose,你就不会得到这些。Mongoose的验证器很容易配置。在定义模式的时候,开发者可以为应该被验证的属性添加额外的选项。现在让我们看一下Mongoose中验证的一些基本例子。
开始使用required
现在我们在Mongoose中有一个没有验证的模式。换句话说,下面定义的所有属性都是可选的。如果你在创建文档时提供了每个属性,很好!如果没有,也很好。如果没有,那也很好!
const gameSchema = new mongoose.Schema({
title: String,
publisher: String,
tags: [String],
date: { type: Date, default: Date.now },
onSale: Boolean,
price: Number
});
当然,几乎总是需要验证任何你想持久保存的数据。我们可以修改模式,像这样添加验证。在下面的片段中,我们将游戏的title ,这是强制性的。它不再是可选的了。我们可以通过required 属性来实现这一点。
const gameSchema = new mongoose.Schema({
title: { type: String, required: true },
publisher: String,
tags: [String],
date: { type: Date, default: Date.now },
onSale: Boolean,
price: Number
});
随着我们对游戏标题的验证到位,让我们尝试在不指定标题的情况下将游戏保存到数据库。
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/mongo-games')
.then(() => console.log('Now connected to MongoDB!'))
.catch(err => console.error('Something went wrong', err));
const gameSchema = new mongoose.Schema({
title: { type: String, required: true },
publisher: String,
tags: [String],
date: { type: Date, default: Date.now },
onSale: Boolean,
price: Number
});
const Game = mongoose.model('Game', gameSchema);
async function saveGame() {
const game = new Game({
publisher: "Nintendo",
tags: ["adventure", "action"],
onSale: false,
price: 59.99,
});
const result = await game.save();
console.log(result);
}
saveGame();
我们可以在终端使用node index.js ,运行index.js来测试这个。
mongo-crud $node index.js
(node:10176) UnhandledPromiseRejectionWarning: Unhandled promise rejection
(rejection id: 2): ValidationError: Game validation failed: title: Path `title`
is required. (node:10176) [DEP0018] DeprecationWarning: Unhandled promise rejections
are deprecated. In the future, promise rejections that are not handled will
terminate the Node.js process with a non-zero exit code.
有趣的是。我们得到了很多关于未处理的拒绝承诺的错误信息。我们可以通过更新我们在saveGame() 函数中的逻辑来解决这个问题。然而,好的方面是,在列出的信息中,我们确实看到验证工作,因为游戏验证失败:标题。路径`title`是必须的,这告诉我们。让我们更新代码,通过实现一个try/catch块来正确处理承诺。
async function saveGame() {
const game = new Game({
publisher: "Nintendo",
tags: ["adventure", "action"],
onSale: false,
price: 59.99,
});
try {
const result = await game.save();
console.log(result);
} catch (err) {
console.log(err.message)
}
}
saveGame();
运行index.js文件,现在给我们一个更容易阅读的信息。
mongo-crud $node index.js
Game validation failed: title: Path `title` is required.
很好!验证开始工作了。需要注意的是,这个在Mongoose中验证的例子仅仅是在Mongoose中验证。这与数据库或MongoDb级别的数据验证没有关系。另一个需要注意的是,Mongoose中的这种验证是对像Joi这样的验证包的补充,我们在node rest api教程中使用了Joi。通过在REST层和Mongoose层同时使用验证,你可以确保有问题的文档不会被持久化到数据库中。
关于内置验证器的更多信息
在上面的章节中,我们看到了如何使用required 属性来使用户在持久化到数据库时必须提供游戏的标题。你也可以使用一个带有required 的函数来有条件地要求某些东西。在下面的片段中,我们说如果游戏正在销售,那么价格是必须的。
const gameSchema = new mongoose.Schema({
title: { type: String, required: true },
publisher: String,
tags: [String],
date: { type: Date, default: Date.now },
onSale: Boolean,
price: {
type: Number,
required: function () { return this.onSale }
}
});
现在我们可以尝试保存一个游戏,但我们将省略标题和价格,看看我们的验证规则是否仍然有效。我们插入游戏的逻辑在这里。
const Game = mongoose.model('Game', gameSchema);
async function saveGame() {
const game = new Game({
publisher: "Nintendo",
tags: ["adventure", "action"],
onSale: true,
});
try {
const result = await game.save();
console.log(result);
} catch (err) {
console.log(err.message)
}
}
saveGame();
运行程序显示,这些规则运行良好。价格和标题都是必需的,所以游戏不会被持久化。
mongo-crud $node index.js
Game validation failed: price: Path `price` is required., title: Path `title` is required.
minlength和maxlength
除了使一个字符串成为必需品外,你还可以指定它的最小长度和最大长度。考虑一下这个模式。
const gameSchema = new mongoose.Schema({
title: {
type: String,
required: true,
minlength: 4,
maxlength: 200
},
publisher: String,
tags: [String],
date: { type: Date, default: Date.now },
onSale: Boolean,
price: {
type: Number,
required: function () { return this.onSale }
}
});
现在,让我们提供一个标题,但只有3个字符,看看会发生什么。
const Game = mongoose.model('Game', gameSchema);
async function saveGame() {
const game = new Game({
title: "Pac",
publisher: "Nintendo",
tags: ["adventure", "action"],
onSale: true,
});
try {
const result = await game.save();
console.log(result);
} catch (err) {
console.log(err.message)
}
}
saveGame();
当我们运行这个程序时,验证器告诉我们,我们的标题太短了。
mongo-crud $node index.js
Game validation failed: price: Path `price` is required.,
title: Path `title` (`Pac`) is shorter than the minimum allowed length (4).
枚举验证
当创建一个游戏时,我们要给它分配一些标签。使用枚举验证,我们可以指定一个人可以使用的标签。下面我们说,一个游戏的标签必须是体育、赛车、动作或Rpg中的任何一个。
const gameSchema = new mongoose.Schema({
title: {
type: String,
required: true,
minlength: 4,
maxlength: 200
},
publisher: String,
tags: {
type: [String],
required: true,
enum: ['sports', 'racing', 'action', 'rpg']
},
date: { type: Date, default: Date.now },
onSale: Boolean,
price: {
type: Number,
required: function () { return this.onSale }
}
});
现在我们尝试用一个我们在枚举验证中没有考虑的标签--冒险--来保存一个游戏。
const Game = mongoose.model('Game', gameSchema);
async function saveGame() {
const game = new Game({
title: "Pacman",
publisher: "Nintendo",
tags: ["adventure", "action"],
onSale: true,
price: 29.99
});
try {
const result = await game.save();
console.log(result);
} catch (err) {
console.log(err.message)
}
}
saveGame();
果然,试图将该游戏插入数据库时失败了,我们得到的错误是 "adventure "不是路径 "tags "的有效枚举值。
mongo-crud $node index.js Game validation failed: tags.0: `adventure` is not a valid enum value for path `tags`.
自定义验证器
你也可以在Mongoose中设置一个自定义验证器。这里我们将修改标签的验证,使用户必须提供一个以上的标签。
const gameSchema = new mongoose.Schema({
title: {
type: String,
required: true,
minlength: 4,
maxlength: 200
},
publisher: String,
tags: {
type: [String],
validate: {
validator: function (v) {
return v.length > 1
},
message: 'You must provide more than 1 tag.'
}
},
date: { type: Date, default: Date.now },
onSale: Boolean,
price: {
type: Number,
required: function () { return this.onSale }
}
});
现在我们尝试保存一个游戏,但只提供一个标签。
const Game = mongoose.model('Game', gameSchema);
async function saveGame() {
const game = new Game({
title: "Pacman",
publisher: "Nintendo",
tags: ["arcade"],
onSale: true,
price: 29.99
});
try {
const result = await game.save();
console.log(result);
} catch (err) {
console.log(err.message)
}
}
saveGame();
运行该程序后,我们会得到我们所期望的验证错误。
mongo-crud $node index.js
Game validation failed: tags: You must provide more than 1 tag.
异步验证器
当你需要获取一些远程数据,或在持久化到数据库之前执行一些其他类型的异步任务时,异步验证就会发挥作用。为此,我们可以使用一个异步验证器。让我们来看看一个。我们将用setTimeout()函数来模拟异步工作。
const gameSchema = new mongoose.Schema({
title: {
type: String,
required: true,
minlength: 4,
maxlength: 200
},
publisher: String,
tags: {
type: [String],
validate: {
isAsync: true,
validator: function (v, callback) {
// Complete async task
setTimeout(() => {
const result = v.length > 1;
callback(result);
}, 2000);
},
message: 'You must provide more than 1 tag.'
}
},
date: { type: Date, default: Date.now },
onSale: Boolean,
price: {
type: Number,
required: function () { return this.onSale }
}
});
要启用异步验证,你需要做的就是给validate对象添加isAsync属性,并将其设置为true 。然后你就可以进行异步工作了,无论是获取远程数据、从文件系统读取数据,还是与数据库一起工作,验证都会正常进行。
Mongoose验证实例总结
在这个关于Mongoose验证的教程中,我们了解到在定义模式时,你可以将一个属性的类型设置为SchemaType对象。你用这个对象来定义给定属性的验证要求。我们可以用这样的代码添加验证。
new mongoose.Schema({
name: { type: String, required: true }
})
在文档可以被保存到数据库之前,验证逻辑由Mongoose执行。也可以通过调用validate()方法手动触发它。一些内置的验证器包括。
- 字符串:
minlength,maxlength,match。enum - 数字:
min,max - 日期:
min,max - 所有类型:
required
为了设置自定义验证,你可以设置验证对象并在验证属性中使用一个函数。
tags: [
type: Array,
validate: {
validator: function (v) { return v && v.length > 0; },
message: 'A game should have at least 1 tag.'
}
]
当与数据库或远程服务对话以执行验证时,需要使用一个异步验证器。你可以通过将isAsync 属性设置为true 来启用这一点。
validate: {
isAsync: true
validator: function(v, callback) {
// Do the validation, when the result is ready, call the callback
callback(isValid);
}
}
其他一些有用的SchemaType属性包括。
- 字符串:
lowercase,uppercase。trim - 所有类型:
get,set(定义一个自定义的getter/setter)
快乐的验证!