ФронтендБэкендPythonHTML / CSS

FastAPI + htmx: интерактивный список дел без React

Делаем интерактивный todo list на FastAPI и htmx: серверные HTML-фрагменты, добавление без перезагрузки, валидация формы и удаление.

Кодик

Автор

6 мин чтения

FastAPI + htmx можно освоить на одном небольшом проекте, если сразу видеть вход, действие и результат. htmx отправляет обычные HTTP-запросы из HTML-атрибутов и подменяет фрагмент страницы ответом FastAPI. Ниже вы соберёте работающий пример и проверите обычный и граничный сценарии.

Для кнопки «добавить задачу без перезагрузки» новичок часто подключает крупный фронтенд-стек раньше, чем понимает форму, запрос и ответ. Поэтому отделим настройку, входные данные, основное действие и проверку. В этой статье вы соберёте todo list, где FastAPI хранит задачи в памяти, а htmx добавляет и удаляет HTML-элементы без полной перезагрузки. Проверка: форма добавляет непустую задачу, сервер возвращает только

  • , а кнопка удаления убирает выбранную строку.

    1Соберите минимум

    отдать базовый HTML с формой и пустым списком. Не расширяйте проект до первого успешного запуска.

    2Увидьте данные

    ответом приходит один новый li. Проверяйте значение, а не отсутствие ошибки.

    3Измените правило

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

    Как устроен учебный проект FastAPI + htmx

    Браузер отправляет форму, FastAPI проверяет данные и рендерит HTML-фрагмент, htmx вставляет его в выбранную часть DOM. Держите ввод, логику и отображение раздельно: тогда каждый этап можно проверить отдельно.

    ЭтапДействиеПроверяемый результат
    Страницаотдать базовый HTML с формой и пустым спискомGET / возвращает документ 200
    Добавлениенастроить hx-post и endpoint POST /tasksответом приходит один новый li
    Вставкауказать hx-target и hx-swapфрагмент добавляется в конец списка
    Удалениевернуть пустой ответ для DELETE /tasks/:idвыбранная строка исчезает без перезагрузки

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

    Путь данных в проекте FastAPI + htmx
    От входных данных до видимого результата. Сначала проверяется вход, затем действие и только потом интерфейс.

    Зафиксируйте первый успех Откройте вкладку Network и добавьте задачу. Должен появиться POST, а в ответе только один фрагмент <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 управляет доставкой и заменой. Найдите в примере вход, проверку и место формирования результата.

    Хрупкий и проверяемый вариант FastAPI + htmx
    Что отличает устойчивый проект от случайного запуска. Явные границы делают ошибку локальной и понятной.

    Что происходит внутри и почему это работает

    hx-post сериализует форму и отправляет её на сервер. hx-target выбирает узел для обновления, а hx-swap определяет способ вставки ответа. Логику держите отдельно от вывода: функция получает аргумент и возвращает значение, а интерфейс показывает его. Если сервер возвращает ошибку 422, htmx по умолчанию не вставляет тело как успешный фрагмент. Для пользовательской ошибки удобно вернуть контролируемый HTML и подходящий статус или обработать событие ошибки.

    ЧастьЗа что отвечаетЧто проверить
    HTML-атрибутыописывают запрос, цель и способ заменыNetwork показывает ожидаемый метод и URL
    FastAPI endpointпроверяет форму и меняет данныепустое значение не создаёт запись
    HTML-фрагментявляется контрактом ответа для конкретного местакорневой элемент совпадает с hx-target

    Не собирайте HTML через f-строку для пользовательского текста в рабочем приложении: это создаёт риск внедрения разметки. В полном примере используйте шаблонизатор с экранированием. Здесь функция показывает форму ответа, но границу безопасности нужно понимать явно. Не прячьте поток в одном обработчике: сохраните вход, проверьте его, вызовите операцию и только затем обновите интерфейс.

    Проверьте границу Отправьте строку из пробелов и убедитесь, что задача не появилась. Затем проверьте ввод с символами < в шаблонизаторе. Меняйте одно условие за прогон, чтобы видеть причину.

    Проверяем проект и добавляем свою функцию

    Добавьте переключение done через PATCH и класс завершённой задачи. Сервер должен вернуть обновлённый li, а не отдельный JSON. До правки запишите ожидаемое поведение одним предложением. После граничного теста повторите обычный сценарий.

    Проверка своими руками
    1. Сохраните ожидаемый результат: форма добавляет непустую задачу, сервер возвращает только
    2. , а кнопка удаления убирает выбранную строку.
    3. Измените правило: добавьте отметку выполнения и счётчик оставшихся задач в отдельном фрагменте. Сначала предскажите итог.
    4. Передайте граничное значение и убедитесь, что приложение не падает.
    5. Верните корректные данные и объясните роли трёх основных частей.
    Готово, если выполняются все пункты
    • GET / возвращает документ 200
    • ответом приходит один новый li
    • фрагмент добавляется в конец списка
    • выбранная строка исчезает без перезагрузки

    htmx не отменяет HTTP и серверные шаблоны. Он делает их видимыми прямо в разметке. Полезная правка проходит от нового ввода до видимого результата.

    Четыре контрольные точки FastAPI + htmx
    Четыре проверки перед следующим шагом. Каждый следующий этап начинается после наблюдаемого результата.

    Что запомнить и куда двигаться дальше

    Перед постоянным хранением вынесите данные в репозиторий, замените 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.

    Возврат JSON вместо фрагмента

    hx-target получает текст JSON, а не элемент списка. Верните ожидаемый HTML.

    Неверный hx-target

    Ответ вставляется не туда или заменяет весь список. Проверяйте CSS-селектор и swap.

    HTML из f-строки в проде

    Пользовательский текст может стать разметкой. Используйте шаблонизатор с автоэкранированием.

    Состояние только в памяти

    После перезапуска список исчезает. Для продакшена нужен постоянный storage.

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

    Что получится в итоге

    Готовый результат проекта: FastAPI + htmx: интерактивный список дел без React
    Так выглядит законченный учебный проект: есть понятный вход, видимый результат, защита от неверных данных и место для следующего улучшения.

    Короткие ответы
    Что возвращает FastAPI в этом примере?

    Готовый HTML-фрагмент, который htmx вставляет в указанную область страницы.

    За что отвечает hx-swap?

    Он задаёт, как именно ответ заменяет цель: содержимое, внешний элемент, начало или конец.

    Что проверять первым, если FastAPI + htmx не запускается?

    Версию среды, текущую папку, команду запуска и первую строку ошибки.

    Как понять, что проект FastAPI + htmx работает?

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

    Можно ли начать с готового кода?

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

    Сделайте один осознанный шаг дальше

    Выберите одно улучшение и проведите его от ввода до результата.

    Если не хватает основы, перейдите курс Python, затем восстановите ключевую функцию без подсказки.