Mongo - Data Validation
Validation
1 | const courseSchema = new mongoose.Schema({ |
之前我们对于表的定义是这样的,所以当我创建一个空对象也是可以通过验证的。MongoDB不会在意我们的课程是否有单价。所以我们需要required验证器
1 | const courseSchema = new mongoose.Schema({ |
这时候当我们去掉name信息之后,运行createCourse()函数,catch会捕捉到下面的报错
Course validation failed: name: Path name
is required.
我们也可以手工处理验证
1 | await course.validate(); |
不想Mysql,mongodb是没有必须填写这种验证的。所以验证只有写在mongoose里面才会起作用。当我们尝试写入数据库时,mongoose会做验证,不通过就不会向数据库写入
当然我们可以使用Joi来验证字符串,一般在RESTful API种使用Joi. Mongoose在写入数据库之前验证数据是否合法
因为可能客户端将合法的数据写在了请求当中,但是当我们在创建http 服务的course对象的时候,也许我们忘记把name属性从req.body.name中读取出来了。所以利用mongoose可以确保数据库中不会出现不合法的数据文档
Built-in Validators
我们现在假设,price只有当isPublished为true得时候才是必需的。现在我们来写这个验证器
1 | const courseSchema = new mongoose.Schema({ |
注意,在这个特定的地方是不能用箭头函数的,因为箭头函数没有this,它使用的this是继承而来的。所以箭头函数中的this指的是courseSchema。
运行后,我们发现
Course validation failed: price: Path price
is required., name: Path name
is required.
所以验证既可以是一个简单的值,也可以是一个验证条件的函数
当然我们也可以有附加的验证器
比如,在string类型中
1 | name: { |
我们还可以用enum属性(python中的数组).也就是说,我们添加的新的内容,必须在enum给出的范围之内
1 | category:{ |
对于数字,我们可以有min 和max
1 | price: { |
Custom Validators
有时候内建的验证器并不能满足我们的需求
例如tags属性,他是一个字符串数组,假设我们要求每个课程必须有一个tag,那么我们在这里无法使用required。因为当使用了required,传入一个空数组,mongoose也会认为这是合法的。所以我们要自定义验证器了
我们设置validate属性是一个对象,这个对象中有个属性是validator,这是一个函数,函数里面写验证逻辑
此外还需要一条提示信息。
1 | tags:{ |
现在当tags数组为空,tags: null 或者不传入tags的时候,就会出现以下报错:
Course validation failed: price: Path price
is required., name: Path name
is required., tags: A course must have one tag
Async Validators
验证有时候需要读取数据库或者远端的http服务,我们不能直接得到结论。这时候需要异步验证
1 | tags:{ |
首先设置isAsync 属性为true,其次这一个回调函数。这里用setTimeout()代替
现实项目当中这个结果可能来自于对文件系统,数据库或者远端服务返回值的计算
Validation Errors
现在我们来扩展Error对象的细节
1 | try { |
我们可以看到这里 一开始是验证错误的对象,然后是错误信息的提示。然后是堆栈的追踪信息。
SchemaType Options
当定义schema的时候,可以直接定义属性的类型,也可以使用对象:type,required,enum等等属性。
现在我们再来了解几个有用的schema对象的属性
lowercase: 当lowercase is true时,mongoose会自动将字符串转为小写
uppercase: 当uppercase is true时,mongoose会自动将字符串转为大写
自定义get,set。比如我想要对price数值四舍五入小数部分。
这样当写入price的时候,set属性会自动四舍五入
get的作用是在数据库中取得浮点数的时候会自动以四舍五入的方式呈现(数据库中没有改变)
1 | price:{ |
Project- Add Persistence to Genres API
genre.js
1 | const Joi = require("joi"); |
index.js
1 | const mongoose = require('mongoose') |
Project- Build the Customers API
1 | const Joi = require('joi'); |
Restructuring the Project
现在一个js文件虽然只有80多行,但以后肯定时很庞大的,所以我们要进行代码重构。
所以我们把一些对象都放到models文件夹中,routes专心处理api
在models中我们新建一个customer.js存放
1 | const mongoose = require("mongoose"); |
这样我们实现了 single responsibility principle
在customer model中,我们实现了对customer对象的定义和验证。也就是定义了在node中一个customer对象应该长什么样子
routes 中的customer.js 就是全部处理关于customer的路由逻辑
在最后我们导出,并在routes中接收。注意,我们这里用析构而不直接定义,是因为我们不想这样写:customer.Customer,太难看了
const {Customer,validate}= require(‘../model/customer’);
同样的我们对genre.js进行重构