推荐一个基于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 相关文章推荐
json 定义
Jun 10 Javascript
Jquery 数据选择插件Pickerbox使用介绍
Aug 24 Javascript
jQuery实现公告文字左右滚动的实例代码
Oct 29 Javascript
jquery插件tooltipv顶部淡入淡出效果使用示例
Dec 05 Javascript
jquery让指定的元素闪烁显示的方法
Mar 17 Javascript
AngularJS 中文API参考手册
Jul 28 Javascript
jQuery EasyUI tree增加搜索功能的实现方法
Apr 27 jQuery
vue2.0之多页面的开发的示例
Jan 30 Javascript
JS实现DOM删除节点操作示例
Apr 04 Javascript
Mint-UI时间组件起始时间问题及时间插件使用
Aug 20 Javascript
uni-app实现获取验证码倒计时功能
Nov 01 Javascript
一篇文章学会Vue中间件管道
Jun 20 Vue.js
微信小程序实现左右列表联动
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
PHP4实际应用经验篇(2)
2006/10/09 PHP
ThinkPHP框架实现session跨域问题的解决方法
2014/07/01 PHP
PHP实现WebService的简单示例和实现步骤
2015/03/27 PHP
php实现贪吃蛇小游戏
2016/07/26 PHP
php简单实现文件或图片强制下载的方法
2016/12/06 PHP
PHP实现15位身份证号转18位的方法分析
2019/10/16 PHP
JQuery扩展插件Validate—6 radio、checkbox、select的验证
2011/09/05 Javascript
一个关于jqGrid使用的小例子(行按钮)
2011/11/04 Javascript
常见表单重复提交问题整理及解决方法
2013/11/13 Javascript
jquery使用ajax实现微信自动回复插件
2014/04/28 Javascript
jQuery实现数秒后自动提交form的方法
2015/03/05 Javascript
js实现精美的图片跟随鼠标效果实例
2015/05/16 Javascript
jquery实现移动端点击图片查看大图特效
2020/09/11 Javascript
如何使用Bootstrap 按钮实例详解
2017/03/29 Javascript
js实现点击切换checkbox背景图片的简单实例
2017/05/08 Javascript
Vue2.0用户权限控制解决方案
2017/11/29 Javascript
浅谈从React渲染流程分析Diff算法
2018/09/08 Javascript
jquery实现Ajax请求的几种常见方式总结
2019/05/28 jQuery
生产制造追溯系统之再说条码打印
2019/06/03 Javascript
在vue项目中使用codemirror插件实现代码编辑器功能
2019/08/27 Javascript
基于js实现抽红包并分配代码实例
2019/09/19 Javascript
vue v-model的用法解析
2020/10/19 Javascript
Python的一些用法分享
2012/10/07 Python
python通过函数属性实现全局变量的方法
2015/05/16 Python
Python中有趣在__call__函数
2015/06/21 Python
Python实现的Excel文件读写类
2015/07/30 Python
Python实现字典排序、按照list中字典的某个key排序的方法示例
2018/12/18 Python
基于python 微信小程序之获取已存在模板消息列表
2019/08/05 Python
详解如何使用rem或viewport进行移动端适配
2020/08/14 HTML / CSS
C#笔试题集合
2013/06/21 面试题
兼职学生的自我评价
2013/11/24 职场文书
车间班组长的职责
2013/12/13 职场文书
个人贷款担保书
2014/04/01 职场文书
简易离婚协议书(范本)
2014/10/25 职场文书
2016年推广普通话宣传周活动总结
2016/04/06 职场文书
教你用eclipse连接mysql数据库
2021/04/22 MySQL