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

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 投入大量时间来编写额外的函数进行表单验证。

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

(0)

相关推荐

  • node.js连接mysql与基本用法示例

    本文实例讲述了node.js连接mysql与基本用法.分享给大家供大家参考,具体如下: 下载mysql模块 使用命令npm install mysql下载mysql模块 mysql引入模块 var mysql = require("mysql"); 创建连接池 使用createPool()创建一个mysql连接池,传入一个表参数作为连接信息 var pool = mysql.createPool({ host:"127.0.0.1", port:3306, //默认

  • 从零搭建docker+jenkins+node.js自动化部署环境的方法

    本次案例基于CentOS 7系统 适合有一定docker使用经验的人阅读 适合有一定linux命令使用经验的人阅读 1.docker部分 1.1.docker简介 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化.容器是完全使用沙箱机制,相互之间不会有任何接口 1.2.docker架构 简单的说,docker就是一个轻量级的linux系统.Docker 容器通过 Docker 镜像来创建.

  • Node.js EventEmmitter事件监听器用法实例分析

    本文实例讲述了Node.js EventEmmitter事件监听器用法.分享给大家供大家参考,具体如下: Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列. events 模块只提供了一个对象: events.EventEmitter.EventEmitter 的核心就是事件触发与事件监听器功能的封装. 该模块已被node.js默认引,不需要使用require()显示引入. EventEmitter 对象如果在实例化时发生错误,会触发 'error' 事件.当添加新的监

  • Docker使用编写dockerfile启动node.js应用

    编写 Dockerfile 以 express 自动创建的目录为例,目录结构如下: ├── /bin │ └── www ├── /node_modules ├── /public ├── /routes ├── /views ├── package-lock.json ├── package.json ├── ecosystem.config.js ├── app.js └── Dockerfile 在项目目录下新建 Dockerfile 文件 FROM node:10.15 MAINTAIN

  • Node.js原生api搭建web服务器的方法步骤

    node.js 实现一个简单的 web 服务器还是比较简单的,以前利用 express 框架实现过『nodeJS搭一个简单的(代理)web服务器』.代码量很少,可是使用时需要安装依赖,多处使用难免有点不方便.于是便有了完全使用原生 api 来重写的想法,也当作一次 node.js 复习. 1.静态 web 服务器 'use strict' const http = require('http') const url = require('url') const fs = require('fs'

  • 详解基于node.js的脚手架工具开发经历

    前言 我们团队的前端项目是基于一套内部的后台框架进行开发的,这套框架是基于vue和ElementUI进行了一些定制化包装,并加入了一些自己团队设计的模块,可以进一步简化后台页面的开发工作. 这套框架拆分为基础组件模块,用户权限模块,数据图表模块三个模块,后台业务层的开发至少要基于基础组件模块,可以根据具体需要加入用户权限模块或者数据图表模块.尽管vue提供了一些脚手架工具vue-cli,但由于我们的项目是基于多页面的配置进行开发和打包,与vue-cli生成的项目结构和配置有些不一样,所以创建项目

  • node.js微信小程序配置消息推送的实现

    在开发微信小程序时,有一个消息推送,它的解释是这样的. 消息推送具体的内容是下面的这个网址   https://developers.weixin.qq.com/miniprogram/dev/framework/server-ability/message-push.html,他介绍的也还可以,就是我这里换成了node代码. 消息推送 启用并设置消息推送配置后,用户发给小程序的消息以及开发者需要的事件推送,都将被微信转发至该服务器地址中. 在微信小程序的首页开发里面,开发设置中,微信的官网中,

  • Node.js如何对SQLite的async/await封装详解

    前言 本文主要给大家介绍的是关于Node.js对SQLite的async/await封装的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧 用于将每个SQLite函数同步化,并可以用await的接口. 注意:需要SQLite for Node模块和Node.js 8.0+,并支持async / await. SQLite最常用作本地或移动应用程序的存储单元,当需要从程序的各个部分访问数据时,回调不是最佳解决方案. 为了在程序程序中更自然地访问数据,我编写了一个将回调转换为

  • mocha的时序规则讲解

    前言 对于新手而言,mocha的时序就像谜一般,许多奇怪的测试样例的失败都是由于对时序不清楚.下面我就把我在测试工作中总结的时序规则部分与大家共享. describe里地时序 simple case describe('work',function(){ it('1',func(){}); it('2',func(){}); .... }); //按1,2,3...顺序执行 规则1:describe里地it的非异步部分按它们定义的顺序执行,它们所触发的回调的注册顺序也遵从it的注册顺序 hook

  • 详解在Node.js中发起HTTP请求的5种方法

    创建HTTP请求使现代编程语言的核心功能之一,也是很多程序员在接触到新的开发环境时最先遇到的技术之一.在Node.js中有相当多的解决方案,其中有语言内置功能,也有开源社区贡献的开发库.下面咱们来看一下比较流行的几种方式. 在开始之前,请先在自己的计算机上安装最新版的node.js和npm. HTTP - 标准库 首先是标准库中默认的 HTTP 模块.这个模块无需安装依赖外部即可使用,做到了真正的即插即用.缺点是与其他解决方案相比,用起来不是那么友好. 下面的代码将向NASA的API发送一个 G

随机推荐