раздел 05 · шаг 4/7

Шаг 4. Связка фронт-бэк

Базовая интеграция работает, но у нас два косяка: TS-типы написаны руками (расходятся с бэком), и нет состояний loading/error/empty. Чиним.

Что делаем

  • Генерим TypeScript-типы из FastAPI OpenAPI-схемы
  • Подключаем сгенерированные типы в фронт, удаляем ручные
  • Добавляем loading/error/empty состояния в UI
  • Проверяем что всё типизировано end-to-end

Шаг 4.1. Генерация типов

FastAPI отдаёт OpenAPI-схему по адресу http://localhost:8000/openapi.json. По ней можно сгенерировать готовые TS-типы через openapi-typescript.

Промпт для Claude

Настрой генерацию TypeScript-типов из FastAPI OpenAPI-схемы:

1. Установи в frontend/ devDependency: openapi-typescript
2. Добавь в frontend/package.json скрипт:
   "types:gen": "openapi-typescript http://localhost:8000/openapi.json -o src/types/api.ts"
3. Сгенерируй типы (бэк должен быть запущен)
4. Удали ручной src/types.ts
5. Замени все импорты Task на сгенерированные типы из src/types/api.ts
   (там будет components["schemas"]["TaskRead"] и т.п.)
6. Сделай удобные алиасы в src/types/index.ts:
   export type Task = components["schemas"]["TaskRead"]
   export type TaskCreate = components["schemas"]["TaskCreate"]
   export type TaskUpdate = components["schemas"]["TaskUpdate"]
7. Обнови fetchApi в src/lib/api.ts: типизируй на основе сгенерированных типов

Запуск

Бэк должен быть запущен. В отдельном терминале:

cd frontend
pnpm install
pnpm types:gen

В frontend/src/types/api.ts появится сгенерированный файл - его в git коммитим, переписывать руками не нужно.

Шаг 4.2. Состояния UI

Промпт для Claude

Добавь loading, error и empty состояния в TaskList:

- loading: пока useQuery в isPending, показывай 3 skeleton-строки
  (фон zinc-900, скругление rounded-lg, animate-pulse)
- error: если isError, показывай красную плашку с текстом ошибки и кнопкой "Повторить",
  которая вызывает refetch
- empty: если data.length === 0, показывай центрированный текст
  "Задач пока нет. Добавьте первую сверху." с приглушённым цветом text-zinc-500

Аналогично для мутаций:
- POST /api/tasks: пока в isPending - инпут disabled, плейсхолдер "Добавляем..."
- PATCH: пока isPending - чекбокс показывает спиннер вместо галочки
- DELETE: пока isPending - строка с opacity-50

Используй типы из src/types вместо any.

Что должно получиться

После этого шага UI ведёт себя адекватно:

  • Открыли страницу - сразу видно скелетон, потом список
  • Бэк упал - красная плашка, можно перезапросить
  • Нет задач - понятный текст приглашения
  • Создание/удаление/обновление - визуальная обратная связь

Проверка типизации

cd frontend
pnpm tsc --noEmit

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

Тест end-to-end

  1. Бэк запущен (uvicorn)
  2. Фронт запущен (pnpm dev)
  3. Открываете http://localhost:5173
  4. Видите skeleton, потом список (или empty)
  5. Добавляете задачу - появляется
  6. Чекаете - сохраняется (PATCH улетел)
  7. Удаляете - исчезает (DELETE улетел)
  8. Останавливаете бэк, перезагружаете фронт - видите error-плашку
  9. Запускаете бэк обратно, нажимаете "Повторить" - данные подтягиваются

Возможные проблемы

  • openapi-typescript не видит бэк - проверьте что uvicorn запущен, curl http://localhost:8000/openapi.json должен вернуть JSON
  • TS-ошибки про null в типах - в Pydantic v2 Optional[X] превращается в X | null, а не X | undefined. Поправьте в TS обработку соответственно.
  • TanStack Query не обновляет список после POST - проверьте что в мутации вызывается queryClient.invalidateQueries({ queryKey: ['tasks'] })

Точка сохранения

cd ..
git add .
git commit -m "step 4: openapi-typescript types, loading/error/empty states"

Полезные ссылки