← Все статьиКейсРазбор
EVER AI · Блог · Строю с ИИ · практика

Собрал форму записи для клиники через Claude Code: от пустого файла до продакшена

Практика8 минКухня EVER AI
Форма записи для клиники, собранная через Claude Code

Задача простая на словах: форма записи для клиники — имя, телефон, услуга. Заполнил — заявка падает в Telegram администратору, форма живёт на своём домене и работает ночью. Я не программист, я бывший руководитель продаж. Собрал это через Claude Code, без классического кодинга. Показываю весь путь — вместе с местами, где я споткнулся.

0шагов до продакшена
3поля: имя · телефон · услуга
24/7заявка сразу в Telegram
01

Задачу надо ставить как ТЗ, а не как желание

Главная ошибка новичка — написать Claude Code «сделай форму записи» и ждать магии. Он что-нибудь сделает, но не то, что у тебя в голове. Claude Code силён ровно настолько, насколько точно ты назвал результат. Формулируй как инженер, который принимает работу: какие поля, куда уходит заявка, что должно произойти после отправки.

размытая задача

✕ «сделай форму записи для клиники»

✕ ИИ угадывает поля, дизайн, логику

✕ переделываешь три раза

задача как ТЗ

✓ «форма: имя, телефон, услуга (выпадающий список)»

✓ «POST на /api/booking, тело — JSON»

✓ «после отправки — заявка в Telegram + запись в базу»

Разница не в вежливости, а в количестве переделок. На размытой формулировке ты потратишь вечер на споры с ИИ. На точной — он соберёт скелет с первого раза, а ты будешь только докручивать. Я держу в голове три вопроса перед каждой задачей: что на входе, куда уходит, что происходит после. Ответил на все три — можешь формулировать.

02

Фронт формы: три поля, валидация телефона и ловушка для ботов

Начинаем с того, что видит пациент. Обычная HTML-форма: имя, телефон, услуга. Две вещи, которые прошу у Claude Code сразу, — проверку телефона (чтобы не приходили заявки с пустым номером) и honeypot: скрытое поле, которое человек не видит и не заполнит, а бот заполнит. Заполнено — значит бот, заявку молча выкидываем.

<form id="booking">
 <input name="name" placeholder="Имя" required>
 <input name="phone" placeholder="+7 ___ ___ __ __"
 pattern="\+?[0-9\s\-]{10,}" required>
 <select name="service" required>
 <option value="">Услуга</option>
 <option>Консультация</option>
 <option>УЗИ</option>
 </select>
 <!-- ловушка для ботов: человек её не видит -->
 <input name="website" tabindex="-1" autocomplete="off"
 style="position:absolute;left:-9999px">
 <button>Записаться</button>
</form>

<script>
document.getElementById('booking').onsubmit = async (e) => {
 e.preventDefault();
 const data = Object.fromEntries(new FormData(e.target));
 const res = await fetch('/api/booking', {
 method: 'POST',
 headers: { 'Content-Type': 'application/json' },
 body: JSON.stringify(data)
 });
 alert(res.ok ? 'Заявка принята' : 'Ошибка, позвоните нам');
};
</script>
// фронт формы: атрибут pattern проверяет телефон, honeypot ловит ботов

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

03

Бэкенд-эндпоинт: куда падает заявка

Фронт красивый, но сам по себе он никуда ничего не отправит. Нужен серверный роут — адрес, куда прилетает POST, и который делает две вещи: шлёт заявку в Telegram администратору и пишет её в базу, чтобы ничего не потерялось. Токены и ключи — только на сервере, в переменных окружения. На фронт их выносить нельзя: любой откроет код страницы и заберёт.

// /api/booking — серверный роут (Next.js-стиль)
export async function POST(req) {
 const { name, phone, service, website } = await req.json();

 // honeypot заполнен → это бот, тихо выходим
 if (website) return Response.json({ ok: true });

 // валидация на сервере, не только в браузере
 if (!name || !/[0-9]{10,}/.test((phone||'').replace(/\D/g,'')))
 return Response.json({ ok: false }, { status: 400 });

 // 1. сигнал в Telegram администратору
 await fetch(`https://api.telegram.org/bot${process.env.TG_TOKEN}/sendMessage`, {
 method: 'POST',
 headers: { 'Content-Type': 'application/json' },
 body: JSON.stringify({
 chat_id: process.env.TG_CHAT,
 text: `Новая запись\nИмя: ${name}\nТелефон: ${phone}\nУслуга: ${service}`
 })
 });

 // 2. пишем в базу сервисной ролью (см. грабли ниже)
 await supabaseAdmin.from('bookings').insert({ name, phone, service });

 return Response.json({ ok: true });
}
// TG_TOKEN, TG_CHAT — плейсхолдеры, реальные ключи живут в переменных окружения сервера

Три защиты, которые прошу заложить сразу: honeypot проверяем и на сервере (не только в браузере — бот шлёт запрос напрямую, минуя форму); валидацию дублируем на сервере; и хорошо бы поставить простой лимит — не больше N заявок с одного адреса в минуту, чтобы форму не завалили тысячей отправок. Последнее можно добавить позже, но заложить в голову — сейчас.

04

Git: фиксируем работу, чтобы не потерять

Пока всё лежит только на твоём ноутбуке — этого не существует. Git — это точки сохранения. Работаю в отдельной ветке, чтобы не трогать рабочую версию сайта, фиксирую изменения коммитом и отправляю на сервер. Три команды, которые прошу Claude Code выполнить и которые ты быстро запомнишь сам:

git checkout -b booking-form # новая ветка под форму
git add . # собрать изменения
git commit -m "форма записи + эндпоинт /api/booking"
git push origin booking-form # отправить на GitHub
// ветка изолирует работу; коммит — точка сохранения; push уводит код в облако

Отдельная ветка — это страховка. Если что-то сломается, рабочий сайт стоит нетронутым, а ты спокойно доводишь форму в своей ветке. Claude Code сам подскажет команды, но понимать, что делает каждая, стоит — это твои точки отката.

05

Vercel: push в Git → форма в проде

Дальше самое приятное. Vercel привязан к репозиторию: как только код уходит в Git, он сам собирает и публикует сайт. Никаких серверов руками. После деплоя первым делом проверяю, что эндпоинт живой — бью в него из терминала curl, не открывая форму:

# проверяем, что эндпоинт принимает заявку
curl -X POST https://klinika.kz/api/booking \
 -H "Content-Type: application/json" \
 -d '{"name":"Тест","phone":"+77001234567","service":"УЗИ"}'

# ждём в ответ: {"ok":true} и сообщение в Telegram
// curl шлёт тестовую заявку прямо в прод — если пришло в Telegram, эндпоинт жив

Пришёл {"ok":true} и сообщение в Telegram — бэкенд работает. Осталось подключить домен. В Vercel добавляешь свой адрес, он выдаёт, что прописать у регистратора: либо A-запись на IP Vercel, либо сменить nameservers на серверы Vercel. Второе проще — Vercel сам рулит всеми записями. DNS обновляется от нескольких минут до пары часов, тут остаётся только подождать.

01
Push в Git
Отправил ветку — Vercel увидел изменения и сам запустил сборку. Ничего не нажимаешь.
02
Автодеплой
Через минуту-две форма и эндпоинт уже в вебе по временному адресу Vercel.
03
Проверка curl
Тестовая заявка прямо в прод. Пришла в Telegram — значит цепочка форма → сервер → сигнал жива.
04
Домен
A-запись или nameservers у регистратора. Подождал, пока DNS обновится — форма на своём адресе.
06

Грабли: где я реально споткнулся

Теперь честная часть, ради которой стоит читать чужой опыт. Каждый из этих трёх пунктов стоил мне времени, и на каждом я сначала грешил на ИИ, а виноват был не он.

База молча не писала заявки. Форма отправлялась, Telegram приходил, а в базе пусто. Причина — защита доступа на уровне строк (RLS): по умолчанию она запрещает запись анонимному клиенту. Это правильно, что она включена, но эндпоинт должен писать сервисной ролью — отдельным серверным ключом, который эти ограничения обходит легально. Как только эндпоинт пошёл в базу сервисным ключом (а не публичным), заявки легли на место. Публичный ключ на фронт, сервисный — только на сервер.

CORS резал запрос. Пока форма и эндпоинт на одном домене — всё гладко. Стоит вынести форму на отдельный адрес — браузер начинает блокировать запрос с чужого источника. Лечится заголовками на эндпоинте, разрешающими твой домен. Знать про это заранее — сэкономить полдня гадания, почему «в одном месте работает, в другом нет».

Сессии и переменные окружения. Локально всё работало, в проде эндпоинт падал. Оказалось — забыл добавить TG_TOKEN и ключи базы в переменные окружения на самом Vercel. На ноутбуке они лежали в локальном файле, а в облако не уехали (и правильно — секреты в Git не коммитят). Правило: каждый ключ, который есть локально, продублируй в настройках Vercel.

Задачу ставь как ТЗ. Секреты — только на сервере. В базу пиши сервисной ролью. Проверяй эндпоинт через curl до того, как открыл форму. И тогда «собрать форму записи» — это вечер, а не проект на месяц.

Это ровно тот же принцип, на котором держатся мои большие системы: маленькие проверяемые кирпичики, каждый из которых можно потрогать отдельно. Если хочешь увидеть, как из таких кирпичиков собирается связка ИИ с живой базой бизнеса — я разбирал это в отдельной статье про подключение Claude к CRM за вечер.

Частые вопросы
Как сделать форму записи на сайт с помощью ИИ?
Описать задачу словами, ИИ собирает форму и подключает её к базе и уведомлениям. Заявка сразу падает в CRM и в Telegram — без ручного переноса.

Посмотреть, как это устроено

Покажу систему целиком — 20–30 минут на моём экране. Доступы не нужны, решать ничего не надо. Слово ПОКАЗ в WhatsApp: +7 777 580 60 14. Или посчитай свою воронку сам — две минуты, без контактов.

Хочешь сам научиться собирать такие вещи через Claude Code, без классического программирования, — напиши ОБУЧЕНИЕ в директ, беру 2–3 человека на реальных проектах.

Написать в директ → @la_konstant