Что такое Joi и зачем он нужен

Joi представляет собой библиотеку для описания схем данных и их последующей валидации в JavaScript-приложениях. Основная задача инструмента заключается в формализации структуры входящих данных и обеспечении их соответствия заранее заданным правилам. Подход основан на декларативном описании ограничений, при котором данные проверяются на соответствие типам, диапазонам, форматам и логическим условиям.

Использование Joi особенно распространено в серверных приложениях на Node.js, где требуется контроль входящих запросов: параметры HTTP, тела запросов, данные форм, конфигурации и внешние интеграции. Отсутствие валидации приводит к неконтролируемым ошибкам, уязвимостям и нестабильному поведению системы.

Основная концепция схем

В основе работы Joi лежит понятие схемы. Схема описывает структуру данных и набор правил, которым эти данные должны соответствовать. Проверка осуществляется путем сравнения входного значения с описанием схемы.

Простейшая схема для строки:

const Joi = require('joi');

const schema = Joi.string();

Такая схема определяет, что допустимым значением является любая строка.

Валидация выполняется через метод validate:

const result = schema.validate('тестовая строка');

Результат содержит информацию об ошибке и преобразованное значение.

Установка и подключение

Библиотека устанавливается через менеджер пакетов npm:

npm install joi

Подключение осуществляется стандартным способом:

const Joi = require('joi');

В проектах с ES Modules используется:

import Joi from 'joi';

Базовые типы данных

Joi предоставляет набор базовых типов, соответствующих примитивам JavaScript.

Строки

const schema = Joi.string();

Добавление ограничений:

const schema = Joi.string().min(3).max(30);

Проверка форматов:

const schema = Joi.string().email();

Регулярные выражения:

const schema = Joi.string().pattern(/^[a-zA-Z0-9]+$/);

Числа

const schema = Joi.number();

Ограничения диапазона:

const schema = Joi.number().min(0).max(100);

Проверка целых чисел:

const schema = Joi.number().integer();

Булевы значения

const schema = Joi.boolean();

Допустимые значения: true и false.

Обязательные и необязательные значения

По умолчанию поля считаются необязательными. Для изменения поведения используется метод required:

const schema = Joi.string().required();

Обратное поведение:

const schema = Joi.string().optional();

Значения по умолчанию:

const schema = Joi.string().default('значение по умолчанию');

При отсутствии данных будет использовано заданное значение.

Объекты и вложенные структуры

Одним из ключевых сценариев применения Joi является валидация объектов.

const schema = Joi.object({
  username: Joi.string().min(3).required(),
  age: Joi.number().min(18),
  email: Joi.string().email()
});

Проверка:

const data = {
  username: 'user1',
  age: 25,
  email: 'test@mail.com'
};

const result = schema.validate(data);

Вложенные объекты

const schema = Joi.object({
  user: Joi.object({
    name: Joi.string().required(),
    profile: Joi.object({
      bio: Joi.string(),
      website: Joi.string().uri()
    })
  })
});

Массивы

Joi поддерживает строгую типизацию массивов:

const schema = Joi.array().items(Joi.string());

Ограничение длины:

const schema = Joi.array().min(1).max(5);

Массив объектов:

const schema = Joi.array().items(
  Joi.object({
    id: Joi.number(),
    name: Joi.string()
  })
);

Альтернативные типы и выбор вариантов

Для случаев, когда данные могут соответствовать нескольким схемам, используется alternatives:

const schema = Joi.alternatives().try(
  Joi.string(),
  Joi.number()
);

Это позволяет принимать значения разных типов в рамках одного поля.

Настройки валидации

Метод validate принимает дополнительные параметры, влияющие на поведение проверки.

abortEarly

Останавливает проверку при первой ошибке:

const result = schema.validate(data, { abortEarly: false });

allowUnknown

Разрешает наличие неизвестных полей в объектах:

const schema = Joi.object({
  name: Joi.string()
}).unknown(true);

stripUnknown

Удаляет лишние поля из результата:

const result = schema.validate(data, { stripUnknown: true });

convert

Включает автоматическое приведение типов:

const result = schema.validate('123', { convert: true });

Сообщения об ошибках

Joi позволяет настраивать текст ошибок:

const schema = Joi.string().min(3).messages({
  'string.min': 'Строка слишком короткая'
});

Ошибки структурированы по типам, что упрощает обработку на уровне приложения.

Кастомная валидация

Для более сложных сценариев используется метод custom:

const schema = Joi.string().custom((value, helpers) => {
  if (value === 'admin') {
    return helpers.error('any.invalid');
  }
  return value;
});

Позволяет реализовать произвольную бизнес-логику проверки.

Объединение схем

Схемы могут комбинироваться через concat:

const base = Joi.string().min(3);
const extended = base.concat(Joi.string().max(10));

Это полезно при переиспользовании правил.

Расширение функциональности

Joi поддерживает расширения через создание собственных правил:

const customJoi = Joi.extend((joi) => ({
  type: 'positive',
  base: joi.number(),
  validate(value, helpers) {
    if (value < 0) {
      return { value, errors: helpers.error('number.positive') };
    }
  }
}));

Позволяет добавлять новые типы и правила поверх существующей системы.

Поведение при преобразовании данных

Joi способен не только проверять, но и преобразовывать данные:

  • строки в числа
  • строки в даты
  • нормализация значений

Пример:

const schema = Joi.number();

const result = schema.validate('42');

Результатом будет число 42, если включено преобразование.

Работа с датами

const schema = Joi.date();

Ограничения:

const schema = Joi.date().greater('now');

Поддерживаются ISO-форматы и JavaScript Date.

Практическая роль в архитектуре приложений

Система валидации на основе Joi часто используется на границах приложения:

  • проверка входящих HTTP-запросов
  • валидация конфигурационных файлов
  • контроль данных от внешних API
  • проверка пользовательских форм

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