Валидация на сервере

При использовании Tom Select основная логика выбора и ввода значений часто переносится на клиент, однако окончательная проверка данных должна выполняться на сервере. Любые значения, поступающие из компонента, считаются недоверенными: автодополнение, создание новых элементов, мультивыбор и динамическая подгрузка данных формируют поверхность атаки и источник неконсистентности данных.

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


Поток данных между Tom Select и сервером

Типовой сценарий взаимодействия строится вокруг нескольких точек обмена:

  1. Загрузка списка опций (load)
  2. Поиск и автодополнение (search)
  3. Создание новых значений (createItem)
  4. Сохранение формы (submit)

Каждый из этих этапов может включать серверную проверку.

Базовая схема потока

  • Пользователь вводит текст
  • Tom Select инициирует AJAX-запрос
  • Сервер возвращает список кандидатов
  • Пользователь выбирает значение
  • При сохранении отправляется финальный payload
  • Сервер выполняет строгую проверку

Ключевая идея: поиск и отображение — это удобство, валидация — это обязательное правило на сервере.


Валидация при загрузке опций (load)

Метод load часто используется для динамической подгрузки данных:

new TomSelect("#select", {
  load: function(query, callback) {
    fetch(`/api/users?q=${encodeURIComponent(query)}`)
      .then(res => res.json())
      .then(data => callback(data))
      .catch(() => callback());
  }
});

На сервере в этот момент обычно выполняется:

  • фильтрация по запросу
  • ограничение доступа (ACL)
  • пагинация
  • проверка корректности входного query

Серверная логика

Важно не полагаться на клиентский ввод:

  • ограничение длины строки поиска
  • очистка спецсимволов
  • защита от wildcard-инъекций в SQL/NoSQL
  • rate limiting для предотвращения перебора

Проверка выбранных значений при сохранении формы

Наиболее критичный этап — финальная отправка данных.

Tom Select формирует массив значений:

{
  "tags": ["12", "45", "78"]
}

или в режиме создания новых элементов:

{
  "tags": ["react", "vue", "svelte"]
}

Сервер обязан проверить:

  • существует ли ID в базе
  • принадлежит ли объект пользователю
  • не был ли удалён или деактивирован
  • соответствует ли формат данных

Пример серверной валидации (Node.js / Express)

app.post("/submit", async (req, res) => {
  const { tags } = req.body;

  if (!Array.isArray(tags)) {
    return res.status(400).json({ error: "Invalid format" });
  }

  const validTags = await db.tags.findMany({
    where: {
      id: { in: tags }
    }
  });

  if (validTags.length !== tags.length) {
    return res.status(400).json({ error: "Some tags are invalid" });
  }

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

Здесь ключевой принцип — сравнение количества и состава входных данных с результатами из базы.


Валидация новых элементов (createItem)

Tom Select поддерживает создание новых значений через create: true:

new TomSelect("#select", {
  create: true
});

Это означает, что пользователь может отправить значение, которого нет в системе.

Потенциальные проблемы:

  • дублирование записей
  • неконсистентность справочников
  • внедрение мусорных или вредоносных строк
  • нарушение бизнес-правил именования

Серверная обработка create-запросов

Часто применяется разделение логики:

  • существующие ID проходят проверку
  • новые значения создаются отдельно
app.post("/tags", async (req, res) => {
  const { tags } = req.body;

  const result = [];

  for (const tag of tags) {
    if (/^[a-zA-Z0-9_-]{2,30}$/.test(tag)) {
      const existing = await db.tags.findOne({ name: tag });

      if (existing) {
        result.push(existing.id);
      } else {
        const created = await db.tags.create({ name: tag });
        result.push(created.id);
      }
    }
  }

  res.json({ tags: result });
});

Важный аспект

Регулярные выражения и бизнес-валидация на сервере являются обязательными, даже если клиент уже фильтрует ввод.


Защита от подмены значений

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

{
  "user_id": "1",
  "role": "admin"
}

Tom Select может возвращать только UI-слой, но злоумышленник способен отправить произвольный payload.

Меры защиты:

  • игнорировать label и принимать только ID
  • сверять ID с базой
  • использовать whitelist-выборку
  • проверять принадлежность сущности пользователю

Асинхронная валидация выбранного значения

Иногда требуется проверить значение до отправки формы:

async function validateValue(value) {
  const res = await fetch(`/api/validate?id=${value}`);
  const data = await res.json();
  return data.valid;
}

Применение в Tom Select:

onChange: async function(value) {
  const valid = await validateValue(value);

  if (!valid) {
    this.removeItem(value);
  }
}

Риски такого подхода

  • гонки запросов (race conditions)
  • устаревшие ответы
  • несогласованность UI и сервера

При быстром вводе:

  • запрос A: “re”
  • запрос B: “rea”
  • запрос C: “react”

Ответ может прийти в неправильном порядке.

Сервер не решает проблему полностью, но помогает:

  • добавление timestamp
  • использование request id
  • отмена предыдущих запросов на клиенте

Пример обработки:

let lastRequestId = 0;

load: function(query, callback) {
  const requestId = ++lastRequestId;

  fetch(`/api?q=${query}`)
    .then(r => r.json())
    .then(data => {
      if (requestId !== lastRequestId) return;
      callback(data);
    });
}

Синхронизация состояния клиента и сервера

Tom Select хранит состояние локально, но сервер является источником истины.

Типичные проблемы:

  • удалённые на сервере значения остаются в UI
  • изменённые записи не обновляются
  • несоответствие label/value

Решение

При каждом открытии списка или перед сохранением:

  • перепроверять список выбранных ID
  • делать ре-валидацию массива
  • при необходимости очищать невалидные значения

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

На сервере обычно вводится несколько уровней проверки:

1. Синтаксическая

  • тип данных
  • формат
  • длина

2. Семантическая

  • существует ли объект
  • активен ли он
  • доступен ли пользователю

3. Бизнес-логика

  • лимиты выбора
  • уникальность
  • зависимости между полями

Типовые ошибки при интеграции Tom Select и серверной валидации

Доверие к клиенту

Любая логика в браузере считается ненадёжной.

Проверка только на submit

Игнорирование промежуточных состояний приводит к накоплению мусора в UI.

Отсутствие сверки ID

Использование label вместо id приводит к подменам.

Отсутствие очистки данных

Сервер должен нормализовать вход:

  • trim
  • lowercase (если требуется)
  • удаление дубликатов

Массовая валидация списков

При мультивыборе важно не валидировать элементы по одному в цикле без оптимизации.

Оптимальный подход:

const ids = req.body.tags;

const valid = await db.tags.findMany({
  where: { id: { in: ids } }
});

Затем:

  • сравнение множеств
  • выявление недостающих элементов
  • возврат ошибки с детализацией

Итоговая модель безопасности

Tom Select выступает как слой UX, а сервер выполняет:

  • проверку целостности данных
  • защиту от подмены
  • контроль бизнес-ограничений
  • нормализацию входных данных

Любая интеграция считается корректной только при условии, что сервер способен полностью восстановить и переоценить состояние выбора без участия клиента.