FastAPI + htmx можно освоить на одном небольшом проекте, если сразу видеть вход, действие и результат. htmx отправляет обычные HTTP-запросы из HTML-атрибутов и подменяет фрагмент страницы ответом FastAPI. Ниже вы соберёте работающий пример и проверите обычный и граничный сценарии.
Для кнопки «добавить задачу без перезагрузки» новичок часто подключает крупный фронтенд-стек раньше, чем понимает форму, запрос и ответ. Поэтому отделим настройку, входные данные, основное действие и проверку. В этой статье вы соберёте todo list, где FastAPI хранит задачи в памяти, а htmx добавляет и удаляет HTML-элементы без полной перезагрузки. Проверка: форма добавляет непустую задачу, сервер возвращает только
отдать базовый HTML с формой и пустым списком. Не расширяйте проект до первого успешного запуска.
ответом приходит один новый li. Проверяйте значение, а не отсутствие ошибки.
После успеха добавьте отметку выполнения и счётчик оставшихся задач в отдельном фрагменте. Так копирование станет практикой.
Как устроен учебный проект FastAPI + htmx
Браузер отправляет форму, FastAPI проверяет данные и рендерит HTML-фрагмент, htmx вставляет его в выбранную часть DOM. Держите ввод, логику и отображение раздельно: тогда каждый этап можно проверить отдельно.
| Этап | Действие | Проверяемый результат |
|---|---|---|
| Страница | отдать базовый HTML с формой и пустым списком | GET / возвращает документ 200 |
| Добавление | настроить hx-post и endpoint POST /tasks | ответом приходит один новый li |
| Вставка | указать hx-target и hx-swap | фрагмент добавляется в конец списка |
| Удаление | вернуть пустой ответ для DELETE /tasks/:id | выбранная строка исчезает без перезагрузки |
Контрольные точки: страница, добавление, вставка и удаление. Следующий шаг начинается после совпадения с ожидаемым результатом.

От входных данных до видимого результата. Сначала проверяется вход, затем действие и только потом интерфейс.
<li>, не вся страница. Сохраните команду запуска и ожидаемый вывод.Собираем рабочий пример шаг за шагом
Пример сверялся с актуальной документацией официальной документации htmx на 19 октября 2026 года. Создайте проект через uv init и выполните uv add fastapi uvicorn python-multipart jinja2. htmx подключите официальным скриптом в шаблоне. Проверьте текущую папку через pwd или Get-Location. Вставьте пример целиком, выполните один раз, затем разбирайте по частям.
from fastapi import FastAPI, Form, HTTPException
from fastapi.responses import HTMLResponse, Response
app = FastAPI()
tasks: dict[int, str] = {}
def task_html(task_id: int, title: str) -> str:
return f'''<li id="task-{task_id}">
<span>{title}</span>
<button hx-delete="/tasks/{task_id}" hx-target="#task-{task_id}" hx-swap="outerHTML">Удалить</button>
</li>'''
@app.post("/tasks", response_class=HTMLResponse)
def add_task(title: str = Form(min_length=1, max_length=80)):
clean = title.strip()
if not clean:
raise HTTPException(422, "Введите задачу")
task_id = max(tasks, default=0) + 1
tasks[task_id] = clean
return task_html(task_id, clean)
@app.delete("/tasks/{task_id}", status_code=200)
def delete_task(task_id: int):
tasks.pop(task_id, None)
return Response()- POST возвращает небольшой HTML-фрагмент новой задачи, а не JSON и не полный документ.
- DELETE отвечает пустым телом, после чего htmx удаляет целевой
li. - Повторный запуск даёт тот же предсказуемый результат.
- Пустой ввод получает понятное сообщение вместо падения.
Контракт интерфейса здесь является HTML. Сервер возвращает готовое представление, а htmx управляет доставкой и заменой. Найдите в примере вход, проверку и место формирования результата.

Что отличает устойчивый проект от случайного запуска. Явные границы делают ошибку локальной и понятной.
Что происходит внутри и почему это работает
hx-post сериализует форму и отправляет её на сервер. hx-target выбирает узел для обновления, а hx-swap определяет способ вставки ответа. Логику держите отдельно от вывода: функция получает аргумент и возвращает значение, а интерфейс показывает его. Если сервер возвращает ошибку 422, htmx по умолчанию не вставляет тело как успешный фрагмент. Для пользовательской ошибки удобно вернуть контролируемый HTML и подходящий статус или обработать событие ошибки.
| Часть | За что отвечает | Что проверить |
|---|---|---|
| HTML-атрибуты | описывают запрос, цель и способ замены | Network показывает ожидаемый метод и URL |
| FastAPI endpoint | проверяет форму и меняет данные | пустое значение не создаёт запись |
| HTML-фрагмент | является контрактом ответа для конкретного места | корневой элемент совпадает с hx-target |
Не собирайте HTML через f-строку для пользовательского текста в рабочем приложении: это создаёт риск внедрения разметки. В полном примере используйте шаблонизатор с экранированием. Здесь функция показывает форму ответа, но границу безопасности нужно понимать явно. Не прячьте поток в одном обработчике: сохраните вход, проверьте его, вызовите операцию и только затем обновите интерфейс.
< в шаблонизаторе. Меняйте одно условие за прогон, чтобы видеть причину.Проверяем проект и добавляем свою функцию
Добавьте переключение done через PATCH и класс завершённой задачи. Сервер должен вернуть обновлённый li, а не отдельный JSON. До правки запишите ожидаемое поведение одним предложением. После граничного теста повторите обычный сценарий.
- Сохраните ожидаемый результат: форма добавляет непустую задачу, сервер возвращает только
- , а кнопка удаления убирает выбранную строку.
- Измените правило: добавьте отметку выполнения и счётчик оставшихся задач в отдельном фрагменте. Сначала предскажите итог.
- Передайте граничное значение и убедитесь, что приложение не падает.
- Верните корректные данные и объясните роли трёх основных частей.
- GET / возвращает документ 200
- ответом приходит один новый li
- фрагмент добавляется в конец списка
- выбранная строка исчезает без перезагрузки
htmx не отменяет HTTP и серверные шаблоны. Он делает их видимыми прямо в разметке. Полезная правка проходит от нового ввода до видимого результата.

Четыре проверки перед следующим шагом. Каждый следующий этап начинается после наблюдаемого результата.
Что запомнить и куда двигаться дальше
Перед постоянным хранением вынесите данные в репозиторий, замените f-строку на Jinja2 и добавьте тесты endpoints. Интерфейс htmx можно оставить. Для закрепления откройте в первом API FastAPI, в разборе Fetch API и в REST API на Node.js.
Как изучить тему в Кодике
Сначала повторите формы, методы HTTP и HTML-шаблоны, затем добавляйте hx-атрибуты по одному. Сначала восстановите главную функцию и проверьте её на трёх значениях.
| Шаг | Что разобрать | Мини-проверка |
|---|---|---|
| 1. Основа | Формы, семантический список и HTTP-методы | Объясните вход и выход функции. |
| 2. Инструмент | FastAPI Form, HTMLResponse и атрибуты htmx | Повторите команду по памяти. |
| 3. Проект | Добавление, удаление и обработку пустого ввода | Проверьте два сценария. |
| 4. Рефакторинг | Шаблонизатор, базу и тестирование фрагментов | Разделите ввод, логику и вывод. |
Отключите JavaScript и убедитесь, что понимаете базовый HTTP-контракт, даже если интерфейс без htmx станет менее удобным. Базовый синтаксис закрепите через курс Python.
hx-target получает текст JSON, а не элемент списка. Верните ожидаемый HTML.
Ответ вставляется не туда или заменяет весь список. Проверяйте CSS-селектор и swap.
Пользовательский текст может стать разметкой. Используйте шаблонизатор с автоэкранированием.
После перезапуска список исчезает. Для продакшена нужен постоянный storage.
Что получится в итоге

Так выглядит законченный учебный проект: есть понятный вход, видимый результат, защита от неверных данных и место для следующего улучшения.
Что возвращает FastAPI в этом примере?
Готовый HTML-фрагмент, который htmx вставляет в указанную область страницы.
За что отвечает hx-swap?
Он задаёт, как именно ответ заменяет цель: содержимое, внешний элемент, начало или конец.
Что проверять первым, если FastAPI + htmx не запускается?
Версию среды, текущую папку, команду запуска и первую строку ошибки.
Как понять, что проект FastAPI + htmx работает?
Проверьте основной сценарий, неверный ввод, повторный запуск и один граничный случай.
Можно ли начать с готового кода?
Да. Добейтесь результата, измените одно правило и восстановите ключевую функцию без копирования.
Выберите одно улучшение и проведите его от ввода до результата.
Если не хватает основы, перейдите курс Python, затем восстановите ключевую функцию без подсказки.