作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Andrej Adamcik的头像

Andrej Adamcik

Andrej是一名屡获殊荣的全栈开发者,曾为Spotify等全球品牌开发项目, Gartner, 康卡斯特公司, 或幻想.

专业知识

工作经验

11

分享

在API中执行的基本任务之一是数据验证. 在本文中, 我将向您展示如何为数据添加无懈可击的验证,同时返回格式良好的数据.

中执行自定义数据验证 节点.js 既不容易又不迅速. 为了覆盖任何类型的数据,您需要编写许多功能. 虽然我已经尝试了一些节点.Js表单数据库-用于两者 快运和Koa-他们从来没有满足过我项目的需要. 扩展库存在问题,库不能处理复杂的数据结构或异步验证.

节点中的表单验证.js与Datalize

这就是为什么我最终决定编写自己的小而强大的表单验证库的原因 datalize. 它是可扩展的,因此您可以在任何项目中使用它并根据您的需求对其进行定制. 它验证请求的主体、查询或参数. 它还支持 异步 过滤器和复杂的JSON结构,如 数组 or 嵌套对象.

设置

Datalize可以通过npm安装:

NPM install——save datalize

要解析请求体,应该使用单独的库. 如果你还没有用过,我建议你使用 koa-body 为Koa或 分析体 为表达.

您可以将本教程应用于已经设置好的HTTP API服务器,或者使用下面的简单代码 Koa HTTP服务器 code.

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

const 应用程序 = new Koa();
Const 路由器 = new (require('koa-路由器'))();

//帮助返回路由中的错误
应用程序.上下文.错误= 函数(code, obj) {
这.状态=代码;
这.Body = obj;
};

//添加koa-body中间件来解析JSON和form-data body
应用程序.使用(bodyParser ({
enableTypes: ['json', 'form'],
多部分:没错,
强大的:{
maxFileSize: 32 * 1024 * 1024;
}
}));

/ /路线...

//将定义的路由作为中间件连接到Koa
应用程序.使用(路由器.路线());
//我们的应用程序将监听端口3000
应用程序.听(3000);

控制台.log('🌍API监听3000');

但是,这不是您应该使用的生产设置 日志记录、执行 授权、处理 错误等.),但是这几行代码可以很好地用于我将向您展示的示例.

注意:所有代码示例都使用Koa,但是数据验证代码也适用于Express. datalize库还提供了实现Express表单验证的示例.

A基本节点.js表单验证示例

假设您有一个Koa或Express web服务器和API中的一个端点,该端点在数据库中创建具有多个字段的用户. 有些字段是必需的, 有些只能有特定的值,或者必须格式化为正确的类型.

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

/**
 * @api {post} /创建用户
 * ...
 */
路由器.post('/', (ctx) => {
	Const data = CTX.请求.身体;
	Const 错误 = {};
	
	if (!字符串(数据.名称).削减()){
		错误.name =['需要输入姓名'];
	}
	
	if (!(/ ^ \ 0-9a-za-z \.+ _ \] + @ [\ 0-9a-za-z \.\+_]+\.[a-zA-Z]{2,} /美元).测试(String(数据.电子邮件))){
		错误.email = [' email无效.'];
	}
	
	如果(对象.键(错误).长度){
		返回ctx.错误(400年,{错误});
	}
	
	const 用户 =等待用户.创建({
			名称:数据.名字
			电子邮件:数据.电子邮件,
	});
	
	ctx.Body = 用户.toJSON ();
});

现在让我们重写这段代码,并使用datalize验证这个请求:

Const datalize = require('datalize');
Const field = datalize.场;

/**
 * @api {post} /创建用户
 * ...
 */
路由器.邮报》(' / ',datalize ([
	字段(名字).削减().需要(),
	字段(电子邮件).要求().电子邮件(),
]), (ctx) => {
	if (!ctx.form.isValid) {
		返回ctx.错误(400,{错误:CTX.form.错误});
	}
	
	const 用户 =等待用户.创建(ctx.形式);
	
	ctx.Body = 用户.toJSON ();
});

更短,更简洁,便于阅读. 使用datalize,您可以指定字段列表并链接到尽可能多的字段 规则 (如果输入无效则抛出错误的函数)或 过滤器 (格式化输入的函数).

规则和过滤器按照定义的顺序执行, 所以如果你想要先为一个字符串删除空白,然后检查它是否有任何值, 你必须定义 .削减() 之前 .要求().

然后Datalize将创建一个对象(可用) .form (在更广泛的上下文对象中),仅使用您指定的字段, 所以你不需要再列一遍. 的 .form.isValid 属性告诉您验证是否成功.

自动错误处理

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

要做到这一点,我们只需将这段代码添加到 引导文件 我们在哪里创建Koa/Express应用实例.

Const datalize = require('datalize');

//设置datalize在验证失败时抛出错误
datalize.设置(“autoVali日期”,真正的);

//只有Koa
//添加到Koa中间件链的最开始
应用程序.use(异步 (ctx, next) => {
	尝试{
		等待下一个();
	} catch (err) {
		If (err).错误){
			ctx.状态= 400;
			ctx.Body = err.toJSON ();
		} else {
			ctx.状态= 500;
			ctx.body = '内部服务器错误';
		}
	}
});


//只有Express
//添加到Express中间件链的最末端
应用程序.使用(函数(err, req, res, next) {
	If (err).错误){
		res.状态(400).发送(犯错.toJSON ());
	} else {
		res.发送(500).send('内部服务器错误');
	}
});

我们不需要再检查data是否有效,因为datalize会帮我们检查. 如果数据无效,它将返回一条带有无效字段列表的格式化错误消息.

查询验证

是的,您甚至可以非常容易地验证您的查询参数—它不必与 帖子 只请求. 我们只需使用 .查询() 方法,唯一的区别是数据存储在 .data 对象而不是 .form.

Const datalize = require('datalize');
Const field = datalize.场;

/**
 * @api {get} /列出用户
 * ...
 */
路由器.邮报》(' / ',datalize.查询([
	字段(关键字).削减(),
	字段(页面).默认的(1).数量(),
	字段(“perPage”).要求().Select ([10,30,50]),
]), (ctx) => {
	Const limit = CTX.data.perPage;
	Const 在哪里 = {
	};
	
	如果(ctx.data.关键字){
		在哪里.name ={{操作步骤.如:ctx.data.关键词+ '%'};
	}
	
	const 用户s =等待用户.findAll ({
		在那里,
		限制,
		抵消:(ctx.data.页- 1)*限制;
	});
	
	ctx.Body = 用户s;
});

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

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

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

Const datalize = require('datalize');
Const field = datalize.场;
const DOMAIN_ERROR = "Email的域名在其DNS记录中没有有效的MX(邮件)条目";

/**
 * @api {post} /创建用户
 * ...
 */
路由器.邮报》(' / ',datalize ([
	字段(名字).削减().需要(),
	字段(电子邮件).要求().电子邮件().custom((value) => {
		return new Promise((resolve, reject) => {
			dns.解决(值.split('@')[1], 'MX', 函数(err, 地址) {
				如果(err b| b| !地址| | !地址.长度){
					return reject(new Error(DOMAIN_ERROR));
				}
				
				解决();
			});
		});
	}),
	字段(类型).要求().选择((“管理”、“用户”)),
	字段(语言).数组().容器([
		字段(id).要求().id(),
		字段(水平).要求().Select(['初学者','中级','高级'])
	]),
	字段(“集团”).数组().id(),
]), 异步 (ctx) => {
	Const {languages, groups} = CTX.形式;
	删除ctx.form.语言;
	删除ctx.form.组织;
	
	const 用户 =等待用户.创建(ctx.形式);
	
	等待UserGroup.bulkCreate(集团.map(groupId => ({
		groupId,
		用户名:用户.id,
	})));
	
	等待UserLanguage.bulkCreate(语言.map(item => ({
		languageId:条目.id,
		用户名:用户.id,
		水平:项.的水平,
	));
});

如果没有数据的内置规则,我们需要进行验证, 控件创建自定义数据验证规则 .自定义() 方法(很好的名字,对吧?),并在那里编写必要的逻辑. 对于嵌套对象,有 .容器() 方法中指定字段列表的方式与 datalize () 函数. 可以在容器中嵌套容器,也可以使用 .数组() 过滤器,它将值转换为数组. 当 .数组() 过滤器不使用容器, 指定的规则或筛选器应用于数组中的每个值.

So .数组().select()(“读”、“写”) 会检查数组中的每个值是否都是 “读” or “写” 如果没有,它将返回一个包含所有有错误的索引的列表. 很酷,是吧??

/补丁

当涉及到更新数据时 /补丁 (or 帖子),您不必重写所有的逻辑、规则和过滤器. 你只需要添加一个额外的过滤器,比如 .可选的() or .补丁(),它将从上下文对象中删除未在请求中定义的任何字段. (.可选的() 将使它始终是可选的,然而 .补丁() 只有当HTTP请求的方法是 补丁.)您可以添加这个额外的过滤器,这样它就可以在数据库中创建和更新数据.

Const datalize = require('datalize');
Const field = datalize.场;

const 用户Validator = datalize([]
	字段(名字).补丁().削减().需要(),
	字段(电子邮件).补丁().要求().电子邮件(),
	字段(类型).补丁().要求().选择((“管理”、“用户”)),
]);

const 用户EditMiddleware = 异步 (ctx, next) => {
	const 用户 =等待用户.findByPk (ctx.参数个数.id);
	
	//如果没有找到用户,取消请求
	if (!用户){
		抛出新的错误('用户未找到.');
	}
	
	//在请求中存储用户实例,以便稍后使用
	ctx.User = User;
	
	返回下一个();
};

/**
 * @api {post} /创建用户
 * ...
 */
路由器.post('/', 用户Validator, 异步 (ctx) => {
	const 用户 =等待用户.创建(ctx.形式);
	
	ctx.Body = 用户.toJSON ();
});

/**
 * @api {put} /更新用户
 * ...
 */
路由器.put('/:id', 用户EditMiddleware, 用户Validator, 异步 (ctx) => {
	等待ctx.用户.更新(ctx.形式);
	
	ctx.Body = CTX.用户.toJSON ();
});

/**
 * @api {patch} /给用户打补丁
 * ...
 */
路由器.patch('/:id', 用户EditMiddleware, 用户Validator, 异步 (ctx) => {
	if (!Object.键(ctx.形式).长度){
		返回ctx.error(400, {message: 'Nothing to up日期 . '.'});
	}
	
	等待ctx.用户.更新(ctx.形式);
	
	ctx.Body = CTX.用户.toJSON ();
});

使用两个简单的中间件,我们可以为所有中间件编写大部分逻辑 帖子//补丁 方法. 的 用户EditMiddleware () 函数验证我们想要编辑的记录是否存在,否则抛出错误. 然后 用户Validator () 是否对所有端点进行验证. 最后, .补丁() 过滤器将从 .form 如果请求的方法没有定义,则调用 补丁.

节点.. js表单验证附加功能

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

该库包含一组基本的规则和过滤器, 但是您可以注册可用于任何字段的自定义全局过滤器, 所以你不必一遍又一遍地写同样的代码:

Const datalize = require('datalize');
const字段= datalize.场;

场.原型.日期 = 函数(format = 'YYYY-MM-DD') {
  返回这.add(功能(价值){
    Const 日期 = value ? Moment (value, format): null;

    if (!日期| | !日期.isValid ()) {
      抛出新的错误('% 5 '不是一个有效的日期.');
    }

    返回日期.格式(格式);
  });
};

场.原型.日期Time = 函数(format = 'YYYY-MM-DD HH:mm') {
  返回这.日期(格式);
};

有了这两个自定义过滤器,您可以将字段链接起来 .日期() or .日期Time () 用于验证日期输入的过滤器.

文件也可以使用datalize进行验证:有专门针对文件的过滤器,例如 .文件(), .mime (), .尺寸() 因此,您不必单独处理文件.

现在就开始编写更好的api

我一直在为节点使用datalize.js表单验证已经在几个生产项目中使用,包括小型和大型api. 它帮助我按时交付优秀的项目,减轻了压力,同时使它们更具可读性和可维护性. 在一个项目中,我甚至用它来验证WebSocket消息的数据,方法是编写一个简单的Socket包装器.IO的用法和在Koa中定义路由差不多,这很好. 如果有足够的兴趣,我可能会写一个教程.

我希望本教程将帮助您和我在节点中构建更好的api.Js,具有完美验证的数据 没有安全问题 或者内部服务器错误. 最重要的是, 我希望它将为您节省大量的时间,否则您将不得不使用JavaScript编写额外的表单验证函数.

了解基本知识

  • 是节点.“前端”还是“后端”??

    节点.Js是后端平台. 顾名思义,节点.js应用程序是用JavaScript (js)或任何可以编译或翻译成它的语言编写的.

  • 什么是节点.Js不好。?

    cpu繁重的计算在节点上不能很好地工作.Js,因为这会阻塞它的事件循环.

  • 表单验证是什么意思?

    用户可以将任何类型的数据发送到后端——也许它没有在前端进行验证, 或者直接通过你的API提交. 这些数据在后端主要以纯文本或二进制格式接收,必须对其进行解析和验证,以防止由不正确/恶意用户输入引起的错误.

  • Express和节点之间的区别是什么.js?

    Express是一个基于节点的web框架.js,而节点.js是在后端运行JavaScript的服务器环境.

  • 你用节点做什么.js为?

    节点.Js有很多用例,但它最适合用于实时应用.g. 聊天、数据流、协作服务)、服务器端呈现的web应用程序、cli和api.

  • 为什么需要表单验证?

    以确保输入数据的格式和类型正确, 该数据只包含允许的值等.,它必须经过验证. 否则,在将其插入数据库时可能会遇到问题. 验证还可以防止恶意用户数据导致的安全泄漏.

就这一主题咨询作者或专家.
预约电话
Andrej Adamcik的头像
Andrej Adamcik

位于 布拉迪斯拉发,布拉迪斯拉发地区,斯洛伐克

成员自 2015年1月27日

作者简介

Andrej是一名屡获殊荣的全栈开发者,曾为Spotify等全球品牌开发项目, Gartner, 康卡斯特公司, 或幻想.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

专业知识

工作经验

11

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

Toptal开发者

加入总冠军® 社区.