【二】:使用express-validator进行后端校验node
这个系列文章主旨就是经过写代码来入门,并不深刻。只是记录我平时使用到了什么新的技术或插件的入门过程~react
最近写node端后台写的比较多,慢慢的发现前端呢转node端虽然没有那么难,可是有不少细节的东西尚未掌握,好比之前先后端分离的时候,对于一些需求模糊的表单场景,前端可能约束会很松,大部分的约束都是后端去作的,而后用户提交的信息某个字段不合法也是后端反馈给咱们异常,前端再作处理。git
由于后端直接接触的就是数据库,每个字段都必须严格约束,因此对于接口字段的验证,特别是post(往数据库insert)的时候,验证必须严格,咱们总不能每个接口都本身写一套正则来进行校验吧,想想也是,express庞大的社区确定已经有相似的中间件了。去npm搜了一下关键字express + validate
。映入眼帘的就是这个 —— express-validator。github
// express-validator官网描述是一个基于validator.js封装的express中间件。
express-validator is a set of express.js middlewares that wraps validator.js validator and sanitizer functions.
复制代码
仍是沿用第一节的套路,无论你三七二十一,先按照官网示例,跑通一个Demo,而后我在慢慢来弄~ 这里我依然节省时间,直接使用我以前写过的全栈脚手架express-react-scaffold来直接使用express-validator
。数据库
关于这个脚手架的文章在这里新手搭建简洁的Node+React脚手架,正好也是个人第一篇文章,有不少小伙伴也点过赞,一直没时间维护,借此机会温故知新一下,简单回顾了一下,发现当时写的真心锉啊,借此机会小改一下吧~express
其实对于后端接口字段校验,首先想到的就是表单提交了,由于对于GET请求,不管是query仍是param,大部分校验工做前端来作就已经能够解决问题了,query和param的合法性经过了,通常后端也就不出问题了(固然,并非说后端就没必要校验了)。而post、put等这种涉及到操做数据库的请求,若是字段类型不匹配,就很容易发生未知错误,并且由于表单里不一样表单项会有繁琐的校验规则,因此后端必须控制好~npm
咱们先来看一下之前的注册接口:json
前端:
用户名:非空
邮箱:必须是邮箱类型
密码:非空
后端:
用户名:必须大于6位
邮箱:必须是邮箱类型
密码:必须大于6位
复制代码
从上面咱们能够看出,先后端约束条件不一样,也就是说可能存在前端输入合法然后端输入不合法的场景~后端
从文档的例子咱们能够知道,express-validator的校验只须要在路由path和handler中间插入校验规则数组,咱们来写一下。
// 原来的接口
// 用户注册接口
router.post('/register', (req, res) => {
User.findOne({ //查找是否存在
username: req.body.username,
},(err, user)=>{
if (err) {
res.send('server or db error');
} else {
if (user === null) {
const insertObj = {
username: req.body.username,
password: md5(req.body.password + MD5_SUFFIX),
email: req.body.email,
role: 10.0
};
const newUser = new User(insertObj);
newUser.save(insertObj, (err, doc) => {
if (err) {
res.json({ result: false, msg: '用户注册失败' });
} else {
console.log(doc);
res.json({ result: true, msg: '用户注册成功' });
}
});
} else {
res.json({ result: false, msg: '用户名已存在'});
}
}
});
});
复制代码
// 增长验证事后的接口
// 用户注册接口
router.post('/register', [
check('username').isLength({ min: 6 }),
check('email').isEmail(),
check('password').isLength({ min: 6 })
], (req, res) => {
// Finds the validation errors in this request and wraps them in an object with handy functions
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
User.findOne({ //查找是否存在
username: req.body.username,
},(err, user)=>{
if (err) {
res.send('server or db error');
} else {
if (user === null) {
const insertObj = {
username: req.body.username,
password: md5(req.body.password + MD5_SUFFIX),
email: req.body.email,
role: 10.0
};
const newUser = new User(insertObj);
newUser.save(insertObj, (err, doc) => {
if (err) {
res.json({ result: false, msg: '用户注册失败' });
} else {
console.log(doc);
res.json({ result: true, msg: '用户注册成功' });
}
});
} else {
res.json({ result: false, msg: '用户名已存在'});
}
}
});
});
复制代码
好,而后咱们来试一下:
测试用例: 用户名 - aaa, 用户邮箱 - aaa@126.com, 密码 - aaa
复制代码
如图,能够看到,前端经过以后,后端没经过,说明咱们写的内容生效了。因此!咱们的第一个validate demo也就写完了。
上面第一个例子虽然生效了,可是我其实仍是有点稀里糊涂,相信小伙伴们也同样,凭啥?为啥就那么加就经过了?别急,咱们一步一步来。 先来看看代码:
// 校验内容部分
[
check('username').isLength({ min: 6 }),
check('email').isEmail(),
check('password').isLength({ min: 6 })
]
// 校验结果部分
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
复制代码
校验内容部分就很简单了,无非就是约束条件,如今很简单,之后肯能会变得很复杂,可是不是要考虑的。而后就来看结果部分了,能够看到,经过validationResult(req)
获取校验结果,咱们将它打印出来看一看:
{
isEmpty: [Function],
array: [Function],
mapped: [Function],
formatWith: [Function],
throw: [Function]
}
复制代码
能够看到校验结果返回了几个api,咱们来猜一猜或者打印一下就知道了,由于代码里只用到了isEmpty()和array()
,并且意思很明显,就是若是errors.isEmpty()
为真,就表示校验经过,因此用脑壳想想isEmpty()
应该是bool类型返回校验是否经过,若是为假就是校验不经过,而后把不经过的数组信息返回给咱们。咱们就打印一下两者:
// isEmpty
errors.isEmpty() ====> false // 返回的是bool值,表示结果
errors.array()
[
{
location: 'body',
param: 'username',
value: 'aaa',
msg: 'Invalid value, }, { location: 'body', param: 'password', value: 'aaa', msg: 'Invalid value' } ] 复制代码
能够看到,errors.array()返回的是校验不经过的字段的数组以及对应的信息。因此关于总体的校验流程基本掌握了。接下来就是巩固加深提升的过程了~
上面基本了解了如何在后端使用express-validator,可是有一些点仍是不理解:
好比:在handler前面加上校验数组,数组的内容是咱们写的字段,那么字段若是写错呢?
再好比:他怎么知道我想校验的字段在哪?是query仍是param仍是body仍是header呢?
复制代码
带着疑惑,咱们在看读文档,等一下,读文档以前,其实咱们能够再看看上面的错误数组:
[
{
location: 'body',
param: 'username',
value: 'aaa',
msg: 'Invalid value, }, { location: 'body', param: 'password', value: 'aaa', msg: 'Invalid value' } ] 复制代码
嗯,很明显,错误数组对于咱们的字段判断是正确的,location字段它定位的是body,确实,咱们的post接口确实将数据放到了body里。所以,应该是express-validator会check全部与咱们规定值相匹配的req字段吧,带着疑问去查阅一下文档~
check API就是校验各类规则的api,其中包括各类封装好的校验函数,如:isString()、isInt()、isLength({})
等,除此以外还有不少限定范围的api,如图
可见,也就是上述咱们说的问题,咱们能够经过约定检索范围提高效率,好比register的接口,咱们只须要检验body的字段就好了,那么就可使用body来进行check,咱们来试一试:
const { body, validationResult } = require('express-validator/check');
[
body('username').isLength({ min: 6 }),
body('email').isEmail(),
body('password').isLength({ min: 6 })
]
复制代码
换完事后,结果依然成立,其余相似的check API也相似了,就是你校验的字段在哪里就用对应API去检验就行了,提高效率~。
出了上述限定范围,咱们还能够经过buildCheckFunction
来自定义范围,好比咱们校验某个字段id只有在body或query才有效,而且是UUID类型的数据,代码以下:
const { buildCheckFunction } = require('express-validator/check');
const checkBodyAndQuery = buildCheckFunction(['body', 'query']);
app.put('/update-product', [
// id must be either in req.body or req.query, and must be an UUID
checkBodyAndQuery('id').isUUID()
], productUpdateHandler)
复制代码
校验结果validationResult(req) 这个很简单了,就是把express req
传进去,而后返回咱们上面提到的那个error对象~这个就很少作介绍了,由于官方也没有详细介绍。
oneOf(validationChains[, message])
这个也很简单,就是只要几个条件之中的一个知足,咱们就认为校验是经过的~这个场景说实话我还确实没想过哪里能用到,不过仍是试一试,咱们在登陆接口尝试,将用户名验证是不是字符串,密码验证变成是不是数组,这确定是个假命题,不过最后结果不出意外是经过,由于用户名是正确的:
// router login - 登陆接口
oneOf([
body('username').isString(),
body('password').isArray()
])
最后打印出来的结果:validationResult(req).isEmpty() === true。
复制代码
验证结果的API,也算是最重要的API了,由于校验经过不经过,要返回给客户端什么信息,都是经过这个API获取的。
validationResult(req)
isEmpty()
这个上面说过了,就是返回一个bool值,表示check部分是否有错,有错就是false
,没错就是true
。通常使用就是:
if (!validationResult(req).isEmpty()) {
res.status(错误码).json({
错误信息
});
}
复制代码
formatWith(formatter) 这个api意义我我的以为也不是很大,不过算是锦上添花吧。就是能够自定义错误信息格式。
app.post('/create-user', yourValidationChains, (req, res, next) => {
const errorFormatter = ({ location, msg, param, value, nestedErrors }) => {
// 定义返回错误的样式,存入array数组
return `${location}[${param}]: ${msg}`;
};
const result = validationResult(req).formatWith(errorFormatter);
if (!result.isEmpty()) {
// { errors: [ "body[password]: must be at least 10 chars long" ] }
return res.json({ errors: result.array() });
}
...
});
复制代码
array([options])
存放返回的错误信息,参数能够设置是否只返回全部错误的第一条,默认返回全部错误。
Default :
{ onlyFirstError: false }
,若是想要默认返回第一条,设置该参数为true
便可
mapped()
这个API跟isArray()
基本一致,就是返回错误,不过isArray()
和mapped()
的区别就是一个返回的是数组,一个返回的是对象,mapped()
返回的是key和value键值对,value跟array数组返回的内容一致。
// 假设我把login接口的username和password的check验证都改为isArray()。
[
body('username').isArray(),
body('password').isArray()
],
// validationResult(req).mapped()
{
username: {
location: 'body',
param: 'username',
value: 'luffy',
msg: 'Invalid value'
},
password: {
location: 'body',
param: 'password',
value: '123456',
msg: 'Invalid value'
}
}
复制代码
throw()
使用这个api就是不使用isEmpty()
,经过throw()
一个error来返回错误。
try {
validationResult(req).throw();
// Oh look at ma' success! All validations passed! } catch (err) { console.log(err.mapped()); // Oh noes! } 复制代码
其实上面两个API我以为已经足够了,基本知足业务场景了,不过express-validator
还提供不少更完善的功能。下面这些API就简单过一下吧,若是有我以为能用获得的,就写个例子,我以为check就够用了~哈哈。
sanitize系列
这个与check API相似,也能够限定范围和自定义范围,用处与check API不同,check API是检验对应参数是否合法,sanitize系列API是能够帮咱们提早作一个转换工做,好比咱们后台要求的是数字1
,可是前端传过来的是字符串'1'
,就能够经过sanitize系列API进行转换。
const { buildSanitizeFunction } = require('express-validator/filter');
const sanitizeBodyAndQuery = buildSanitizeFunction(['body', 'query']);
app.put('/update-product', [
// 限定范围在body和query内,将id转换成整型
sanitizeBodyAndQuery('id').toInt()
], productUpdateHandler)
复制代码
validation chain
这个也不算新的API,应该就是特性吧,感受跟jQuery同样,不断的链式调用,每一次调用都返回新的结果~
// 检验weekday字段是否不在['sunday', 'saturday']内。
check('weekday').not().isIn(['sunday', 'saturday'])
复制代码
withMessage
这个不是新API,官方是列在Validation Chain API里的,不过我以为这个是个颇有用的API,全部就单独拿出来讲一下,就是错误消息能够自定义,咱们能够设置返回消息放在里面。
// 验证部分
[
body('username').isArray().withMessage('username类型不正确'),
body('password').isArray().withMessage('password类型不正确')
],
// 结果部分
{
username: {
location: 'body',
param: 'username',
value: 'luffy',
msg: 'username类型不正确'
},
password: {
location: 'body',
param: 'password',
value: '123456',
msg: 'password类型不正确'
}
}
复制代码
这篇文章,一如既往,仍是个人我的学习过程,若是有人没用过或者想要在本身的项目中使用,应该仍是个不错的教程~