推荐一个基于Node.js的表单验证库


Posted in Javascript onFebruary 15, 2019

API 在执行过程中的一个基本任务是数据验证。 在本文中,我想向你展示如何为你的数据添加防弹验证,同时返回风格良好的格式。

在 Node.js 中进行自定义数据验证既不容易也不快。 为了覆盖所有类型的数据,需要写许多函数。 虽然我已经尝试了一些 Node.js 的表单库 —— Express 和 Koa ——他们从未满足我的项目需求。 这些扩展库要么不兼容复杂的数据结构,要么在异步验证出现问题。

使用 Datalize 在 Node.js 中进行表单验证

这就是为什么我最终决定编写自己的小巧而强大的表单验证库的原因,它被称为 datalize。 它是可扩展的,因此你可以在任何项目中使用它,并根据你的要求进行自定义。 它能够验证请求的正文、查询或参数,还支持async 过滤器和复杂的JSON结构,如 数组 或 嵌套对象。

Github:https://github.com/flowstudio/datalize

配置

Datalize可以通过npm安装:

npm install --save datalize

要解析请求的正文,你应该使用其他的库。 如果你还没有用过,我建议使用 koa-body for Koa 或 body-parser for Express。

你可以将本教程用于已配置好的HTTP API服务器,也可以使用以下简单的Koa HTTP服务器代码。

const Koa = require('koa');
const bodyParser = require('koa-body');

const app = new Koa();
const router = new (require('koa-router'))();

// helper for returning errors in routes
app.context.error = function(code, obj) {
this.status = code;
this.body = obj;
};

// add koa-body middleware to parse JSON and form-data body
app.use(bodyParser({
enableTypes: ['json', 'form'],
multipart: true,
formidable: {
maxFileSize: 32 * 1024 * 1024,
}
}));

// Routes...

// connect defined routes as middleware to Koa
app.use(router.routes());
// our app will listen on port 3000
app.listen(3000);

console.log('? API listening on 3000');

但是,这不是生产环境下的设置(你还应该使用logging,强制 授权, 错误处理等),不过这几行代码用于向你正常展示后面的例子足够了。

注意:所有代码示例都基于 Koa,但数据验证代码也同样适用于 Express。 datalize 库还有一个实现 Express 表单验证的例子。

一个基本的Node.js表单验证案例

假设你的 API 中有一个 Koa 或 Express Web 写的服务和一个端点,用于在数据库中创建包含多个字段的用户数据。其中某些字段是必需的,有些字段只能具有特定值,或者必须格式化为正确的类型。

你可以像这样写一个简单的逻辑:

/**
 * @api {post} / Create a user
 * ...
 */
router.post('/', (ctx) => {
  const data = ctx.request.body;
  const errors = {};
  
  if (!String(data.name).trim()) {
    errors.name = ['Name is required'];
  }
  
  if (!(/^[\-0-9a-zA-Z\.\+_]+@[\-0-9a-zA-Z\.\+_]+\.[a-zA-Z]{2,}$/).test(String(data.email))) {
    errors.email = ['Email is not valid.'];
  }
  
  if (Object.keys(errors).length) {
    return ctx.error(400, {errors});
  }
  
  const user = await User.create({
      name: data.name,
      email: data.email,
  });
  
  ctx.body = user.toJSON();
});

下面让我们重写这段代码并使用 datalize 验证这个请求:

const datalize = require('datalize');
const field = datalize.field;

/**
 * @api {post} / Create a user
 * ...
 */
router.post('/', datalize([
  field('name').trim().required(),
  field('email').required().email(),
]), (ctx) => {
  if (!ctx.form.isValid) {
    return ctx.error(400, {errors: ctx.form.errors});
  }
  
  const user = await User.create(ctx.form);
  
  ctx.body = user.toJSON();
});

短小精悍并易于阅读。 使用 datalize,你可以指定字段列表,并为它们链接尽可能多的规则(用于判断输入是否有效并抛出错误的函数)或过滤器(用于格式化输入的函数)。

规则和过滤器的执行顺序与它们定义的顺序相同,所以如果你想要先切分含有空格的字符串,然后再检查它是否有值,则必须在 .trim() 之前定义 .required()。

然后,Datalize 将只使用你指定的字段创建一个对象(在更广泛的上下文对象中以 .form 形式提供),因此你不必再次列出它们。 .form.isValid 属性会告诉你验证是否成功。

自动错误处理

如果我们不想检查表单是否对每个请求都有效,可以添加一个全局中间件,如果数据未通过验证,则取消请求。

为此,我们只需将这段代码添加到我们创建的 Koa / Express 应用实例的 bootstrap 文件中。

const datalize = require('datalize');

// set datalize to throw an error if validation fails
datalize.set('autoValidate', true);

// only Koa
// add to very beginning of Koa middleware chain
app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    if (err instanceof datalize.Error) {
      ctx.status = 400;
      ctx.body = err.toJSON();
    } else {
      ctx.status = 500;
      ctx.body = 'Internal server error';
    }
  }
});


// only Express
// add to very end of Express middleware chain
app.use(function(err, req, res, next) {
  if (err instanceof datalize.Error) {
    res.status(400).send(err.toJSON());
  } else {
    res.send(500).send('Internal server error');
  }
});

而且我们不必检查数据是否有效,因为 datalize 将帮我们做到这些。 如果数据无效,它将返回带有无效字段列表的格式化错误消息。

查询验证

是的,你甚至可以非常轻松地验证查询参数——它不仅仅用于POST请求。 我们也可以只使用.query()辅助方法,唯一的区别是数据存储在 .data 对象而不是 .form 中。

const datalize = require('datalize');
const field = datalize.field;

/**
 * @api {get} / List users
 * ...
 */
router.post('/', datalize.query([
  field('keywords').trim(),
  field('page').default(1).number(),
  field('perPage').required().select([10, 30, 50]),
]), (ctx) => {
  const limit = ctx.data.perPage;
  const where = {
  };
  
  if (ctx.data.keywords) {
    where.name = {[Op.like]: ctx.data.keywords + '%'};
  }
  
  const users = await User.findAll({
    where,
    limit,
    offset: (ctx.data.page - 1) * limit,
  });
  
  ctx.body = users;
});

还有一个辅助方法用于参数验证:.params()。 通过在路由的 .post() 方法中传递两个 datalize 中间件,可以同时对查询和表单数据进行验证。

更多过滤器,数组和嵌套对象

到目前为止,我们在 Node.js 表单验证中使用了非常简单的数据。 现在让我们尝试一些更复杂的字段,如数组,嵌套对象等:

const datalize = require('datalize');
const field = datalize.field;
const DOMAIN_ERROR = "Email's domain does not have a valid MX (mail) entry in its DNS record";

/**
 * @api {post} / Create a user
 * ...
 */
router.post('/', datalize([
  field('name').trim().required(),
  field('email').required().email().custom((value) => {
    return new Promise((resolve, reject) => {
      dns.resolve(value.split('@')[1], 'MX', function(err, addresses) {
        if (err || !addresses || !addresses.length) {
          return reject(new Error(DOMAIN_ERROR));
        }
        
        resolve();
      });
    });
  }),
  field('type').required().select(['admin', 'user']),
  field('languages').array().container([
    field('id').required().id(),
    field('level').required().select(['beginner', 'intermediate', 'advanced'])
  ]),
  field('groups').array().id(),
]), async (ctx) => {
  const {languages, groups} = ctx.form;
  delete ctx.form.languages;
  delete ctx.form.groups;
  
  const user = await User.create(ctx.form);
  
  await UserGroup.bulkCreate(groups.map(groupId => ({
    groupId,
    userId: user.id,
  })));
  
  await UserLanguage.bulkCreate(languages.map(item => ({
    languageId: item.id,
    userId: user.id,
    level: item.level,
  ));
});

如果我们需要验证的数据没有内置规则,我们可以用 .custom() 方法创建一个自定义数据验证规则(很不错的名字,对吗?)并在那里编写必要的逻辑。 对于嵌套对象,有 .container() 方法,你可以在其中用和 datalize() 函数相同的方式指定字段列表。 你可以将容器嵌套在容器中,或使用 .array() 过滤器对其进行补充,这些过滤器会将值转换为数组。 如果在没有容器的情况下使用 .array() 过滤器,则指定的规则或过滤器将被用于数组中的每个值。

所以 .array().select(['read', 'write']) 将检查数组中的每个值是 'read' 还是 'write' ,如果有任何一个值不是其中之一,则返回所有错误的索引列表。 很酷,对吧?

PUT/PATCH

在使用 PUT/PATCH (或 POST)更新数据时,你不必重写所有逻辑、规则和过滤器。 只需添加一个额外的过滤器,如 .optional() 或 .patch() ,如果未在请求中定义,它将从上下文对象中删除任何字段。 ( .optional() 将使它始终是可选的,而 .patch() 只有在 HTTP 请求的方法是 PATCH 时才会使它成为可选项。)你可以添这个额外的过滤器,以便它可以在数据库中创建和更新数据。

const datalize = require('datalize');
const field = datalize.field;

const userValidator = datalize([
  field('name').patch().trim().required(),
  field('email').patch().required().email(),
  field('type').patch().required().select(['admin', 'user']),
]);

const userEditMiddleware = async (ctx, next) => {
  const user = await User.findByPk(ctx.params.id);
  
  // cancel request here if user was not found
  if (!user) {
    throw new Error('User was not found.');
  }
  
  // store user instance in the request so we can use it later
  ctx.user = user;
  
  return next();
};

/**
 * @api {post} / Create a user
 * ...
 */
router.post('/', userValidator, async (ctx) => {
  const user = await User.create(ctx.form);
  
  ctx.body = user.toJSON();
});

/**
 * @api {put} / Update a user
 * ...
 */
router.put('/:id', userEditMiddleware, userValidator, async (ctx) => {
  await ctx.user.update(ctx.form);
  
  ctx.body = ctx.user.toJSON();
});

/**
 * @api {patch} / Patch a user
 * ...
 */
router.patch('/:id', userEditMiddleware, userValidator, async (ctx) => {
  if (!Object.keys(ctx.form).length) {
    return ctx.error(400, {message: 'Nothing to update.'});
  }
  
  await ctx.user.update(ctx.form);
  
  ctx.body = ctx.user.toJSON();
});

使用两个简单的中间件,我们可以为所有 POST/PUT/PATCH 方法编写大多数逻辑。 userEditMiddleware() 函数验证我们要编辑的记录是否存在,否则便抛出错误。 然后 userValidator() 对所有端点进行验证。 最后 .patch() 过滤器将删除 .form 对象中的任何字段(如果其未定义)或者假如请求的方法是 PATCH 的话。

Node.js表单验证附加功能

在自定义过滤器中,你可以获取其他字段的值并根据该值执行验证。 还可以从上下文对象中获取任何数据,例如请求或用户信息,因为它们都是在自定义函数的回调参数中提供的。

该库涵盖了一组基本规则和过滤器,不过你可以注册能与任何字段一起使用的自定义全局过滤器,所以你不必一遍又一遍地写相同的代码:

const datalize = require('datalize');
const Field = datalize.Field;

Field.prototype.date = function(format = 'YYYY-MM-DD') {
 return this.add(function(value) {
  const date = value ? moment(value, format) : null;

  if (!date || !date.isValid()) {
   throw new Error('%s is not a valid date.');
  }

  return date.format(format);
 });
};

Field.prototype.dateTime = function(format = 'YYYY-MM-DD HH:mm') {
 return this.date(format);
};

有了这两个自定义过滤器,你就可以用 .date() 或 .dateTime() 过滤器链接字段对日期输入进行验证。

文件也可以使用 datalize 进行验证:只有 .file(), .mime(), 和 .size() 等文件才有特殊的过滤器,所以你不必单独处理文件。

立即开始编写更好的API

对于小型和大型API,我已经在好几个生产项目中用 datalize 进行 Node.js 表单验证。 这有助于我按时提供优秀项目、减轻开发压力,同时使其更具可读性和可维护性。 在一个项目中,我甚至用它来通过对 Socket.IO 进行简单封装,来验证 WebSocket 消息的数据,其用法与在 Koa 中的定义路由几乎完全相同,所以这很好用。 如果很多人有兴趣的话,我也可以为此编写一个教程。

我希望本教程能够帮助你在 Node.js 中构建更好的API,并使用经过完美验证的数据,而不会出现安全问题或内部服务器错误。 最重要的是,我希望它能为你节省大量时间,否则你将不得不用 JavaScript 投入大量时间来编写额外的函数进行表单验证。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
firefo xml 读写实现js代码
Jun 11 Javascript
jquery移除、绑定、触发元素事件使用示例详解
Apr 10 Javascript
JavaScript的Number对象的toString()方法
Dec 18 Javascript
基于jQuery实现选取月份插件附源码下载
Dec 28 Javascript
JS验证逗号隔开可以是中文字母数字
Apr 22 Javascript
DIV+CSS+jQ实现省市联动可扩展
Jun 22 Javascript
javascirpt实现2个iframe之间传值的方法
Jun 30 Javascript
怎样判断jQuery当前元素是隐藏还是显示
Nov 23 Javascript
Vue.js第四天学习笔记
Dec 02 Javascript
使用smartupload组件实现jsp+jdbc上传下载文件实例解析
Jan 05 Javascript
浅谈Vuejs Prop基本用法
Aug 17 Javascript
基于JavaScript实现一个简单的Vue
Sep 26 Javascript
微信小程序实现左右列表联动
May 19 #Javascript
webpack-url-loader 解决项目中图片打包路径问题
Feb 15 #Javascript
微信小程序实现单选选项卡切换效果
Jun 19 #Javascript
一秒学会微信小程序制作table表格
Feb 14 #Javascript
React通过redux-persist持久化数据存储的方法示例
Feb 14 #Javascript
微信小程序实现简易table表格
Jun 19 #Javascript
微信小程序制作表格的方法
Feb 14 #Javascript
You might like
德生PL330测评
2021/03/02 无线电
PHP中使用register_shutdown_function函数截获fatal error示例
2015/04/21 PHP
Zend Framework框架路由机制代码分析
2016/03/22 PHP
PHP简单装饰器模式实现与用法示例
2017/06/22 PHP
PHP实现在对象之外访问其私有属性private及保护属性protected的方法
2017/11/20 PHP
php和C#的yield迭代器实现方法对比分析
2019/07/17 PHP
tp5框架基于Ajax实现列表无刷新排序功能示例
2020/02/10 PHP
javascript 鼠标拖动图标技术
2010/02/07 Javascript
js 将json字符串转换为json对象的方法解析
2013/11/13 Javascript
通过action传过来的值在option获取进行验证的方法
2013/11/14 Javascript
JQUERY dialog的用法详细解析
2013/12/19 Javascript
基于jquery和svg实现超炫酷的动画特效
2014/12/09 Javascript
多个jQuery版本共存的处理方案
2015/03/17 Javascript
Angularjs注入拦截器实现Loading效果
2015/12/28 Javascript
一篇文章掌握RequireJS常用知识
2016/01/26 Javascript
vue.js实现带日期星期的数字时钟功能示例
2018/08/28 Javascript
vue-cli监听组件加载完成的方法
2018/09/07 Javascript
js尾调用优化的实现
2019/05/23 Javascript
JavaScript 扩展运算符用法实例小结【基于ES6】
2019/06/17 Javascript
jQuery实现中奖播报功能(让文本滚动起来) 简单设置数值即可
2020/03/20 jQuery
prettier自动格式化去换行的实现代码
2020/08/25 Javascript
vue使用transition组件动画效果的实例代码
2021/01/28 Vue.js
fastcgi文件读取漏洞之python扫描脚本
2017/04/23 Python
Python使用QRCode模块生成二维码实例详解
2017/06/14 Python
python之从文件读取数据到list的实例讲解
2018/04/19 Python
Python中交换两个元素的实现方法
2018/06/29 Python
Python参数传递及收集机制原理解析
2020/06/05 Python
英国手工制作的现代与经典的沙发和床:Love Your Home
2020/09/26 全球购物
入党积极分子自我鉴定
2014/02/18 职场文书
《月迹》教学反思
2014/02/19 职场文书
学习决心书范文
2014/03/11 职场文书
三八节标语
2014/06/27 职场文书
小学生九一八纪念日83周年演讲稿500字
2014/09/17 职场文书
董事长致辞
2015/07/29 职场文书
演讲稿:态度决定一切
2019/04/02 职场文书
如何利用 CSS Overview 面板重构优化你的网站
2021/10/24 HTML / CSS