Валидация данных в JavaScript-приложениях на практике быстро выходит за рамки простых проверок строк и чисел. По мере роста проекта набор правил становится громоздким, дублируется между слоями приложения и усложняет сопровождение. В таких условиях используется подход построения правил через билдер, который позволяет собирать цепочки проверок в декларативной форме и переиспользовать их как строительные блоки.
Билдер правил представляет собой слой абстракции над базовыми валидаторами библиотеки validator.js или над композиционными API наподобие цепочек проверок в экосистеме Express. Его задача — превратить набор разрозненных условий в структурированное описание валидации, где каждая часть логически связана с остальными.
В основе билдера лежит принцип fluent API: каждый метод возвращает новый контекст или текущий объект, расширенный дополнительным правилом. Это позволяет формировать последовательности вызовов без промежуточных переменных.
Типичная структура выглядит как набор шагов:
Такой подход делает правила независимыми от места применения и позволяет собирать их динамически.
class RuleBuilder {
constructor(field) {
this.field = field;
this.rules = [];
}
isString() {
this.rules.push(v => typeof v === 'string');
return this;
}
notEmpty() {
this.rules.push(v => v && v.length > 0);
return this;
}
minLength(len) {
this.rules.push(v => typeof v === 'string' && v.length >= len);
return this;
}
build() {
return {
field: this.field,
validate: value => this.rules.every(fn => fn(value))
};
}
}
Одним из ключевых преимуществ билдера является возможность композиции. Отдельные наборы правил можно объединять в более сложные конструкции, избегая дублирования логики.
Базовые правила выделяются в отдельные функции или классы:
const baseString = () =>
new RuleBuilder()
.isString()
.notEmpty();
const shortText = () =>
baseString()
.minLength(3);
const longText = () =>
baseString()
.minLength(10);
Такая структура позволяет формировать библиотеку валидаторов внутри проекта, не привязываясь к конкретным моделям данных.
Билдер часто используется как оболочка над функциями библиотеки validator.js, которая предоставляет набор низкоуровневых проверок (email, URL, numeric и др.). Вместо прямого вызова функций создаётся единый слой композиции.
import isEmail from 'validator/lib/isEmail';
class ExtendedRuleBuilder extends RuleBuilder {
isEmail() {
this.rules.push(v => isEmail(v));
return this;
}
isNumeric() {
this.rules.push(v => !isNaN(parseFloat(v)));
return this;
}
}
Такой подход позволяет унифицировать интерфейс валидации независимо от источника правил.
В реальных сценариях правила часто зависят от контекста. Билдер поддерживает условные ветвления, позволяя добавлять проверки только при выполнении определённых условий.
class ConditionalRuleBuilder extends RuleBuilder {
when(condition, fn) {
if (condition) {
fn(this);
}
return this;
}
}
Применение условной логики позволяет описывать сложные бизнес-правила без разветвления кода в основной логике приложения.
Некоторые проверки требуют обращения к внешним источникам: базе данных, API или файловой системе. Билдер расширяется поддержкой асинхронных функций.
class AsyncRuleBuilder extends RuleBuilder {
async test(value) {
for (const rule of this.rules) {
const result = rule(value);
if (result instanceof Promise) {
if (!(await result)) return false;
} else if (!result) {
return false;
}
}
return true;
}
}
Асинхронные правила особенно важны при проверке уникальности значений, существования сущностей или прав доступа.
На выходе билдер формирует схему — объект, содержащий описание поля и функцию проверки. Такие схемы можно агрегировать в единый валидатор формы или DTO.
const userSchema = [
new ExtendedRuleBuilder('email')
.isEmail()
.build(),
new ExtendedRuleBuilder('password')
.minLength(8)
.build()
];
Использование массива схем упрощает применение валидатора к структурам данных произвольной сложности.
Билдер правил переносит акцент с императивной проверки на декларативное описание. Логика валидации становится частью конфигурации, а не процедурного кода.
Такое разделение уменьшает связность между слоями приложения и упрощает тестирование. Каждое правило становится изолированным объектом, который можно проверять независимо.
При увеличении количества правил возникает необходимость структурирования:
Билдер позволяет организовать иерархию через наследование или композицию, создавая доменные наборы валидаторов.
class UserRules {
static email() {
return new ExtendedRuleBuilder('email').isEmail();
}
static password() {
return new ExtendedRuleBuilder('password').minLength(8);
}
}
Результатом работы билдера часто становится не только булево значение, но и структура ошибок, содержащая информацию о проваленных проверках.
class ResultRuleBuilder extends RuleBuilder {
constructor(field) {
super(field);
this.errors = [];
}
validate(value) {
this.errors = [];
for (const rule of this.rules) {
if (!rule(value)) {
this.errors.push(`Validation failed for ${this.field}`);
}
}
return this.errors.length === 0;
}
}
Такой подход позволяет формировать детализированные отчёты для интерфейса или API-ответов.
В серверных приложениях билдер часто интегрируется как middleware-слой. Валидаторы применяются до выполнения бизнес-логики, обеспечивая чистоту входных данных.
В экосистеме Express подобные конструкции становятся промежуточным звеном между запросом и контроллером, формируя единый стандарт обработки входных данных.
Билдер может поддерживать систему плагинов, где новые правила добавляются без изменения ядра.
function pluginIsUUID(builder) {
builder.isUUID = function() {
this.rules.push(v => /^[0-9a-f]{8}-/.test(v));
return this;
};
}
Механизм расширения делает систему гибкой и адаптируемой под специфические доменные задачи.
При большом количестве проверок важна оптимизация исполнения. Часто применяется ленивое выполнение или ранний выход при первой ошибке, что снижает нагрузку.
validate(value) {
for (const rule of this.rules) {
if (!rule(value)) return false;
}
return true;
}
Такой подход особенно эффективен в высоконагруженных API.