Использование в Node.js

Использование в Node.js опирается на идею единых схем валидации, которые применяются как на клиенте, так и на сервере. Это снижает расхождения между фронтенд- и бэкенд-логикой, особенно в проектах, где требуется строгая синхронизация правил проверки данных.

Библиотека Vest реализует декларативный подход к описанию проверок, где набор правил формируется как последовательность тестов, группируемых по логическим блокам. В Node.js она применяется как часть слоя валидации входящих данных, middleware в веб-фреймворках, а также как отдельный модуль доменной логики.


Подключение и структура модулей в Node.js

В среде Node.js библиотека устанавливается как обычная зависимость npm-пакета и может использоваться как в CommonJS, так и в ESM-окружении.

Основные варианты подключения:

// ESM
import { test, enforce, group, skip } from 'vest';

// CommonJS
const { test, enforce, group, skip } = require('vest');

Node.js-проекты часто разделяют валидацию на отдельные модули:

/src
  /validation
    user.validation.js
    auth.validation.js
    product.validation.js

Такой подход позволяет изолировать правила и переиспользовать их в контроллерах, сервисах и тестах.


Базовая модель выполнения валидации

В основе работы лежит функция, возвращающая результат набора тестов. В Node.js это удобно интегрируется в сервисный слой.

import { create, test, enforce } from 'vest';

const validateUser = create((data = {}) => {
  test('username', 'Имя пользователя обязательно', () => {
    enforce(data.username).isNotEmpty();
  });

  test('email', 'Некорректный email', () => {
    enforce(data.email).matches(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
  });
});

export default validateUser;

На сервере результат используется как объект, содержащий информацию о валидности и деталях ошибок.


Интеграция с HTTP-сервером (Express.js)

Node.js чаще всего применяет валидацию на уровне middleware. В связке с Express логика проверки отделяется от бизнес-кода.

import express from 'express';
import validateUser from './validation/user.validation.js';

const app = express();

app.use(express.json());

app.post('/users', (req, res) => {
  const result = validateUser(req.body);

  if (result.hasErrors()) {
    return res.status(400).json({
      errors: result.getErrors(),
    });
  }

  res.status(201).json({ ok: true });
});

Такой подход позволяет централизовать правила и не дублировать проверки в контроллерах.


Состояние выполнения и кэширование результатов

Одной из особенностей Vest является возможность повторного выполнения только изменённых частей логики. В Node.js это критично при высокой нагрузке.

Пример использования кэширования через идентификатор данных:

import { create, test, enforce } from 'vest';

const validateProduct = create((data = {}, changedFields = []) => {
  test.memo('name', () => changedFields.includes('name'), () => {
    enforce(data.name).longerThan(2);
  });

  test.memo('price', () => changedFields.includes('price'), () => {
    enforce(data.price).greaterThan(0);
  });
});

В серверной архитектуре это снижает стоимость повторной валидации при частичных обновлениях ресурсов (PATCH-запросы).


Асинхронная валидация в Node.js

Node.js часто работает с внешними источниками: базами данных, API, очередями. Валидация может включать асинхронные проверки.

import { create, test, enforce } from 'vest';

const validateRegister = create(async (data = {}) => {
  test('email_unique', 'Email уже используется', async () => {
    const exists = await checkEmailInDatabase(data.email);
    enforce(exists).equals(false);
  });
});

Асинхронные тесты выполняются последовательно внутри группы, сохраняя предсказуемость результата.


Использование группировки проверок

Группы позволяют логически разделять правила валидации. В Node.js это особенно полезно для сложных DTO.

import { create, group, test, enforce } from 'vest';

const validateOrder = create((data = {}) => {
  group('customer', () => {
    test('name', () => {
      enforce(data.customer?.name).isNotEmpty();
    });

    test('phone', () => {
      enforce(data.customer?.phone).matches(/^\+?[0-9]{10,15}$/);
    });
  });

  group('delivery', () => {
    test('address', () => {
      enforce(data.delivery?.address).isNotEmpty();
    });
  });
});

Такое разделение удобно при логировании и трассировке ошибок на сервере.


Использование в архитектуре сервисов

В Node.js-приложениях валидация часто размещается между контроллером и сервисом.

Типичная схема:

Request → Controller → Validation Layer → Service → Repository

Валидация с Vest выполняет роль фильтра входных данных до попадания в бизнес-логику.

async function createUserHandler(req, res) {
  const result = validateUser(req.body);

  if (result.hasErrors()) {
    return res.status(400).json(result.getErrors());
  }

  const user = await userService.create(req.body);
  res.json(user);
}

Работа с TypeScript в Node.js

В TypeScript-среде библиотека используется без потери типизации входных данных.

import { create, test, enforce } from 'vest';

type User = {
  email: string;
  username: string;
};

const validateUser = create((data: Partial<User>) => {
  test('email', () => {
    enforce(data.email).isString();
  });

  test('username', () => {
    enforce(data.username).isString();
  });
});

Типизация помогает переносить правила валидации ближе к контракту данных, уменьшая количество runtime-ошибок.


Использование в serverless-окружениях

Node.js-функции в serverless (AWS Lambda, Cloud Functions) требуют минимального времени старта. Валидация должна быть лёгкой и повторно используемой.

export const handler = async (event) => {
  const body = JSON.parse(event.body);

  const result = validateUser(body);

  if (result.hasErrors()) {
    return {
      statusCode: 400,
      body: JSON.stringify(result.getErrors()),
    };
  }

  return {
    statusCode: 200,
    body: JSON.stringify({ ok: true }),
  };
};

Обработка результатов и форматирование ошибок

В Node.js часто требуется преобразование результата в формат API-ответа.

function formatErrors(result) {
  return result.getErrors().map(err => ({
    field: err.fieldName,
    message: err.message,
  }));
}

Структура ошибок может быть адаптирована под JSON API, GraphQL или собственные протоколы.


Логирование и трассировка валидации

На сервере важно фиксировать причины отказа валидации. Vest позволяет извлекать детализированные данные о каждом тесте.

if (result.hasErrors()) {
  console.error('Validation failed', result.getErrors());
}

В распределённых системах такие логи используются для анализа качества входящих данных и мониторинга API.


Производительность в Node.js-среде

Node.js работает в однопоточном режиме, поэтому стоимость валидации напрямую влияет на пропускную способность сервера. Оптимизация достигается за счёт:

  • условного выполнения тестов
  • группировки логики
  • мемоизации результатов
  • пропуска неизменённых полей

Vest спроектирована так, чтобы минимизировать повторные вычисления при неизменных данных, что особенно важно при высокой частоте запросов.


Переиспользование схем между фронтендом и Node.js

Одним из ключевых сценариев является общий пакет валидации:

/packages
  /validation
    user.rules.js

Этот пакет импортируется и в фронтенд, и в Node.js:

import validateUser from '@shared/validation/user.rules';

Такой подход устраняет расхождения между клиентской и серверной логикой, снижая количество ошибок интеграции.