С помощью Cloudflare Workers, Turnstile и D1 можно добавить к небольшому сайту форму, которая проверяет отправку и сохраняет ответ. Разберём эту связку на опросе читателей: от готовой страницы до проверки записи в базе.

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

Документация проверена 29 сентября 2026 года. Обработчик проверен локально с SQLite и имитацией ответов Turnstile; сборка Wrangler прошла в режиме без размещения. Реальное размещение в Cloudflare, работа виджета в браузере и доступность из сетей российских операторов в эту проверку не входили.

Что получится

Посетитель открывает страницу, выбирает вариант и проходит проверку Turnstile. Worker проверяет запрос, записывает ответ в D1 и только после подтверждения записи сообщает об успехе.

Часть проектаЗа что отвечает
Страница с формойПоказывает вопрос и варианты, отправляет ответ
Workers Static AssetsРаздаёт HTML и браузерный JavaScript
WorkerПроверяет данные и обращается к базе
TurnstileПроверяет отправку на признаки автоматизированного злоупотребления
D1Хранит принятые ответы

Путь одного ответа:

Посетитель выбирает вариант
  → Turnstile выдаёт токен проверки
  → форма отправляет ответ в Worker
  → Worker проверяет токен через Siteverify
  → D1 сохраняет запись
  → посетитель видит подтверждение
💡
Токен — короткоживущее подтверждение прохождения проверки. Его наличие в браузере ещё не означает, что сервер может принять ответ: Worker обязан проверить токен через API Turnstile.

Весь проект работает на одном адресе. Форма обращается к /api/vote на том же сайте, поэтому отдельно настраивать доступ из браузера другого домена в этом примере не потребуется.

От выбора варианта до подтверждения сохранения. Защита от повторной записи запроса не означает один голос на человека.
От выбора варианта до подтверждения сохранения. Защита от повторной записи запроса не означает один голос на человека.

Полезные сценарии

Выбор следующей темы

Автор предлагает несколько тем и собирает предпочтения читателей. В нашем примере это автоматизация повседневных задач, создание небольших сайтов и прикладные ИИ-инструменты.

Результат можно проверить в D1: у каждого принятого запроса появляется запись, а SQL-запрос показывает количество ответов по вариантам.

Оценка полезности материала

Тот же подход можно использовать для кнопок «Полезно», «Слишком сложно» и «Нужен пример». Если голосование размещается под разными статьями, каждую нужно связать с отдельным идентификатором опроса.

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

Выбор формата мероприятия

Посетителям можно предложить выбрать разбор, практикум или сессию вопросов. Для первого этапа достаточно вариантов без контактов и свободного текста.

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

Что этот опрос умеет и чего не гарантирует

Turnstile помогает фильтровать автоматизированные отправки. Проверка вариантов на сервере не позволяет записать произвольное значение вместо предусмотренного ответа. Уникальный идентификатор запроса защищает от повторной записи при повторной отправке одного и того же запроса.

Но здесь нет учётных записей и проверки личности. Человек может открыть новую страницу и проголосовать ещё раз. Поэтому результат следует называть количеством принятых ответов, а не количеством уникальных участников.

⚠️
Этот пример подходит для обратной связи и неформального опроса. Для выборов победителя, выдачи приза или голосования с финансовыми последствиями нужна другая модель допуска участников.

Форма не запрашивает контактные данные и не записывает IP-адрес в таблицу ответов. Это не означает полной анонимности: при работе сайта и Turnstile инфраструктура обрабатывает технические данные соединения.

Что проверить пользователю из России

В этой связке должны работать и страница, и внешний виджет, и запрос отправки ответа. Успешная загрузка HTML не подтверждает, что посетитель сможет пройти весь сценарий.

Cloudflare описывает перебои и ограничение трафика у российских провайдеров. До использования в рабочем проекте проверьте опрос из сетей своей аудитории, включая домашний и мобильный интернет.

Если виджет или отправка недоступны, страница должна сообщать об ошибке. Показывать «Ответ принят» без записи в базе нельзя. В примере кнопка остаётся недоступной до получения токена, а при ошибке сохранения появляется предложение повторить отправку.

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

Шаг 1. Подготовьте проект

Понадобятся аккаунт Cloudflare с доступом к Workers, D1 и Turnstile, установленный Node.js и терминал. Учебный обработчик проверялся на Node.js 24. Для первого запуска достаточно готовых файлов; для изменения серверной логики пригодятся основы JavaScript.

Скачайте архив cloudflare-reader-poll.zip, распакуйте его и откройте терминал в папке проекта. Внутри:

Учебный опрос: исходники, схема базы и настройки. Node.js 24, Wrangler 4.143.0; локальные проверки пройдены, облачный запуск не проверялся.
public/index.html   — страница опроса
public/app.js       — отправка формы и сообщения об ошибках
src/worker.js       — серверная проверка и запись ответа
schema.sql          — структура таблицы
wrangler.jsonc      — настройки Cloudflare
package.json        — команды проекта
package-lock.json   — зафиксированные зависимости
README.md           — краткая последовательность запуска

Установите зависимости:

npm ci

Wrangler — инструмент управления проектами Cloudflare из терминала. Он установлен как зависимость примера. Авторизуйтесь в своём аккаунте:

npx wrangler login

Команда откроет браузер. Проверьте, что выбираете нужный аккаунт, особенно если у вас есть доступ к нескольким рабочим пространствам.

⚠️
Следующие команды создают облачные ресурсы и размещают проект. Используйте отдельный учебный Worker и отдельную базу, чтобы не затронуть работающий сайт.

Шаг 2. Создайте отдельную базу D1

Выполните:

npx wrangler d1 create reader-poll-demo-db

Если такая база у вас уже есть, выберите другое имя. Измените его и в командах ниже, и в поле database_name файла wrangler.jsonc.

После создания Wrangler покажет идентификатор базы. Вставьте его вместо REPLACE_WITH_DATABASE_ID. Если инструмент предлагает автоматически добавить привязку в конфигурацию, откажитесь: нужная секция уже есть в примере. Привязка с именем DB должна остаться одна.

"d1_databases": [{
  "binding": "DB", // Под этим именем Worker обращается к базе.
  "database_name": "reader-poll-demo-db",
  "database_id": "REPLACE_WITH_DATABASE_ID" // ID вашей новой базы.
}]

Это фрагмент уже существующего файла, а не отдельная конфигурация. Менять нужно значение внутри него.

Создайте таблицу в облачной базе:

npx wrangler d1 execute reader-poll-demo-db --remote --file=./schema.sql

Флаг --remote означает обращение к базе в Cloudflare. Локальная база разработки и облачная база различаются: таблица, созданная с --local, не появится в облаке автоматически.

Таблица хранит четыре поля:

ПолеСодержание
poll_idИдентификатор конкретного опроса
request_idСлучайный идентификатор отправки
option_idВыбранный вариант
created_atВремя создания записи

Пара poll_id + request_id уникальна. Две записи с такой же парой не появятся даже при одновременных повторных запросах.

Шаг 3. Разместите страницу с закрытым приёмом ответов

В wrangler.jsonc задано имя Worker reader-poll-demo. Убедитесь, что в вашем аккаунте нет рабочего проекта с таким именем. При необходимости выберите другое.

Для начала оставьте:

"vars": {
  "TURNSTILE_SITE_KEY": "",
  "POLL_OPEN": "false"
}

Разместите проект:

npx wrangler deploy

Если аккаунт ещё не настроен для workers.dev, Wrangler может предложить выбрать поддомен. Следуйте подсказкам и запишите итоговый HTTPS-адрес.

На странице должно появиться сообщение «Приём ответов пока закрыт». Это ожидаемо: сначала получаем адрес проекта, затем подключаем к нему проверку.

В конфигурации папка public указана как источник статических файлов, а путь /api/* направляется в Worker. Благодаря этому HTML загружается как обычная страница, а отправка формы выполняется серверным кодом.

Шаг 4. Подключите Turnstile

В панели Cloudflare откройте Turnstile и создайте виджет. Для учебного проекта выберите режим Managed, в котором сервис сам определяет необходимое взаимодействие с посетителем.

В разрешённые доменные имена добавьте точное имя хоста (hostname) вашего проекта. Например, если Wrangler выдал адрес:

https://reader-poll-demo.your-account.workers.dev

в настройке виджета нужно указать:

reader-poll-demo.your-account.workers.dev

Без https://, пути страницы и параметров. Пример выше учебный: копируйте имя из своего адреса.

После создания виджета вы получите два ключа:

КлючГде используется
SitekeyПубличный ключ для отображения виджета
Secret keyСекрет для проверки токена на сервере

Sitekey вставьте в поле TURNSTILE_SITE_KEY файла wrangler.jsonc.

Secret key сохраните отдельной командой:

npx wrangler secret put TURNSTILE_SECRET

Вставьте секрет в интерактивный запрос терминала. Не добавляйте его в HTML, браузерный JavaScript или конфигурационный файл. Команда сохранения секрета создаёт и размещает новую версию Worker.

После этого измените POLL_OPEN на строку "true" и снова выполните:

npx wrangler deploy

Откройте страницу. Теперь должны появиться варианты ответа и проверка Turnstile. Кнопка отправки становится доступной после получения токена.

Тестовые ключи Turnstile пригодны для разработки, но не защищают публичный опрос. В этом сценарии используйте реальные ключи своего виджета.

Как проверяется отправка

Форма передаёт в Worker выбранный вариант, идентификатор запроса и токен Turnstile. Секретный ключ остаётся на сервере.

Обработчик проверяет:

  • что приём ответов открыт;
  • что запрос отправлен методом POST и содержит JSON допустимого размера;
  • что браузер отправляет его со страницы того же сайта;
  • что выбран один из разрешённых вариантов;
  • что результат проверки Turnstile успешен, hostname совпадает с сайтом, а действие обозначено как poll.

Проверка заголовка Origin помогает ограничить отправку из браузеров посторонних сайтов. Она не является защитой от произвольного программного клиента: такой клиент может сам задать заголовок.

Токены Turnstile действуют пять минут и рассчитаны на однократную проверку. Если посетитель долго держал страницу открытой или отправка завершилась ошибкой, требуется новая проверка. Код формы обновляет виджет, сохраняя идентификатор попытки.

Если сервис проверки недоступен, новый голос не записывается. Форма показывает ошибку, чтобы посетитель мог повторить действие позже.

Почему повторная отправка не должна удваивать голос

Представьте: база уже сохранила ответ, но соединение оборвалось до появления подтверждения. Посетитель нажимает кнопку ещё раз.

Для такого случая при открытии страницы создаётся requestId. Повтор в том же окне отправляет тот же идентификатор и тот же выбранный вариант. Worker сначала проверяет, есть ли эта запись в базе. Если ответ уже сохранён, возвращает подтверждение без добавления второго голоса.

Запись в D1 дополнительно защищена уникальным ключом:

PRIMARY KEY (poll_id, request_id)

А при добавлении используется:

INSERT INTO votes (poll_id, request_id, option_id)
VALUES (?, ?, ?)
ON CONFLICT(poll_id, request_id) DO NOTHING;

Знаки ? обозначают параметры. Код передаёт значения через привязку параметров, а не собирает SQL из пользовательского текста.

После первой попытки выбранный вариант в форме фиксируется. Это позволяет безопасно повторить ту же отправку при неопределённом результате.

Перезагрузка страницы создаёт новый идентификатор. Поэтому при сообщении «Нет подтверждения от сервера» повторяйте отправку в том же окне. Этот механизм защищает от технических дублей, но не ограничивает человека одним голосом.

Шаг 5. Посмотрите результаты в D1

После тестовой отправки выполните:

npx wrangler d1 execute reader-poll-demo-db --remote --command="SELECT option_id, COUNT(*) AS votes FROM votes WHERE poll_id = 'topics-2026-09' GROUP BY option_id;"

Команда покажет количество записей по вариантам. В пустой базе после одного успешно принятого ответа должна появиться одна строка с количеством 1 для выбранного варианта.

В этом примере результаты просматривает владелец через Wrangler. Публичного адреса для чтения всех записей и административной панели нет.

Чтобы посмотреть последние ответы, используйте:

npx wrangler d1 execute reader-poll-demo-db --remote --command="SELECT option_id, created_at FROM votes WHERE poll_id = 'topics-2026-09' ORDER BY created_at DESC LIMIT 10;"

Само сообщение «Спасибо» на странице не заменяет эту проверку. Для первого запуска убедитесь, что запись действительно появилась в нужной облачной базе.

Как изменить опрос под свою задачу

В src/worker.js находятся вопрос, варианты и идентификатор опроса. Страница получает их через /api/config, поэтому переписывать варианты отдельно в HTML не нужно.

Если меняется только формулировка варианта, можно сохранить его технический идентификатор. Если меняется смысл вопроса, создайте новый POLL_ID, чтобы не смешивать старые и новые ответы. При таком переключении сначала закройте приём ответов и предупредите посетителей обновить страницу: уже открытая форма может показывать прежний вопрос.

Для новых технических идентификаторов вариантов потребуется согласованное изменение ограничения CHECK в схеме базы. Редактирование schema.sql само по себе не меняет уже созданную таблицу: для существующей базы нужна миграция. Для первого эксперимента проще создать отдельную учебную базу с новой схемой.

После изменения кода повторно разместите Worker и пройдите проверку отправки. Если меняете адрес сайта, добавьте новое доменное имя в настройки Turnstile до открытия опроса для посетителей.

Проверка перед использованием

ПроверкаОжидаемый результат
Обычная отправкаПодтверждение на странице и одна запись в D1
Запрос без токена для нового requestIdОшибка, запись не создаётся
Несуществующий вариантОшибка, запись не создаётся
Повтор того же запросаПодтверждение без второго голоса
Тот же идентификатор с другим вариантомОшибка конфликта
Недоступная проверка TurnstileОшибка без подтверждения нового голоса
Ошибка базыНет сообщения об успешном сохранении
Закрытый опросНовые ответы не принимаются

Не проверяйте эти ошибки отключением ресурсов рабочего сайта. Используйте отдельный учебный проект. В приложенном примере основные серверные ветки проверены локально с имитацией внешнего сервиса.

Дополнительно пройдите весь сценарий в обычном браузере и с телефона: выберите вариант, дождитесь проверки, отправьте ответ и проверьте запись. Проверьте доступность из сетей предполагаемой аудитории.

Если пример не работает

СимптомЧто проверить
«Приём ответов пока закрыт»POLL_OPEN, публичный sitekey и факт последнего размещения
Виджет не загружаетсяДоступность Turnstile, разрешённое доменное имя, ошибки браузера
Проверка проходит, сервер её отклоняетСоответствие sitekey и secret key, hostname и действия poll
Ошибка сохраненияПривязку DB, идентификатор базы и создание таблицы с --remote
Записи не видныПравильность аккаунта, имени базы и идентификатора опроса
Голосов больше ожидаемогоНовые окна и перезагрузки позволяют человеку отвечать повторно

Не выводите секретный ключ и полные токены в журналы ради диагностики. Для поиска ошибки обычно достаточно этапа обработки, HTTP-кода и сообщения, которое вернул сервис.

Расходы и ограничения

Для небольшого учебного проекта можно рассмотреть бесплатные планы, но перед запуском проверьте действующие условия в своём аккаунте. Возможность открыть сервис и условия оплаты также нужно оценивать отдельно.

У Workers учитываются запросы и вычисления, у D1 — операции с данными и хранение. Приложенная проверка повторной отправки уменьшает риск дублей, но не делает обращения к сервисам бесплатными или неограниченными.

Не обновляйте сводку результатов каждую секунду без необходимости. Не открывайте публичный адрес API с полным содержимым таблицы. Для первого запуска достаточно чтения результатов владельцем через Wrangler.

Для публичного проекта также понадобится оценить ограничение частоты запросов, наблюдение за ошибками и расходами. В учебном примере отдельный ограничитель частоты не реализован, поэтому его нельзя считать готовым решением против целевой атаки.

Как закрыть опрос

Измените POLL_OPEN на "false" и повторно разместите проект:

npx wrangler deploy

Новая загрузка страницы покажет, что приём закрыт. Уже открытая форма может остаться на экране, но сервер отклонит новую отправку. Сохранённые ответы останутся в D1.

Для завершения опроса не требуется удалять Worker или базу. Сначала получите нужную сводку, затем отдельно решите, какие данные и как долго хранить.

Ограничения российского законодательства

Форма не запрашивает имя, телефон и электронную почту, но это не гарантирует анонимность. По 152-ФЗ персональными данными могут быть сведения, по которым человека можно определить прямо или косвенно. Turnstile обрабатывает IP-адрес и технические данные браузера, но не получает ответ формы. Cloudflare сообщает, что хранит данные преимущественно в США и ЕЭЗ, а передавать их и предоставлять к ним доступ может из других стран. Если эти сведения позволяют связать запрос с человеком, возможна трансграничная передача персональных данных. [1] [2] [3] [4]

При сборе персональных данных граждан РФ, в том числе через интернет, закон ограничивает использование баз за пределами России для записи, хранения и других перечисленных операций, кроме установленных исключений. В этом примере D1 не закреплён за российским регионом, а документация Cloudflare не гарантирует размещение базы в РФ. Перед рабочим запуском проверьте, какие данные получают Worker и Turnstile, где хранятся записи и журналы и можно ли связать их с человеком. [5] [6] [7]

Если 152-ФЗ применим, обычно нужно уведомить Роскомнадзор об обработке до её начала. Для передачи данных за границу требуется отдельное предварительное уведомление. Порядок зависит от страны-получателя; Роскомнадзор может ограничить или запретить передачу. Уведомление не отменяет требование локализации. [8] [9] [10]

Нормы и документация проверены 29 сентября 2026 года.

Основные источники

Следующий шаг

Если рядом с опросом нужен раздел с файлами для читателей, откройте руководство Cloudflare на практике: небольшой сайт, файлы и автоматизация с Workers и R2.

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

Если захотите обсудить, как это применить у себя или в команде — пишите в Telegram @pimenov