Прямой ответ
Сила FastAPI — не в скорости (она следствие), а в двух механиках, которые переносят рутину из кода в объявления. Валидация: pydantic-модель тела и аннотации параметров описывают контракт — невалидный запрос получает 422 с точным списком проблем до входа в вашу логику. Зависимости (Depends): сессия БД, текущий пользователь, пагинация, настройки — объявляются функциями и внедряются в эндпоинт по сигнатуре, с вложенностью и переиспользованием. Правильная архитектура FastAPI-приложения — это почти полностью «тонкие эндпоинты + дерево зависимостей + сервисный слой», и понимание Depends — главный порог входа.
Валидация: контракт в сигнатуре
Тело запроса — pydantic-модель (выбор моделей — в статье про pydantic vs dataclasses): типы, диапазоны (Field(gt=0, max_length=100)), обязательность — всё проверяется автоматически, ошибка приходит структурированной (detail с путём к полю). Query/path-параметры валидируются аннотациями (limit: int = Query(10, le=100)). Ответ — тоже моделью (response_model=): лишние поля отсекаются, внутренности ORM-объектов не утекают в JSON (частая дыра — user.password в ответе). Автоматическая OpenAPI-схема из этих объявлений — основа контрактной работы с фронтендом и генерации клиентов (подход — в статье про контрактную разработку API).
Depends: внедрение вместо копипасты
Зависимость — любая вызываемая функция (sync или async), результат которой приходит в параметр эндпоинта: def read_items(db: Session = Depends(get_db), user: User = Depends(get_current_user)). Три свойства, которые делают систему архитектурной. Вложенность: get_current_user может сама зависеть от get_db — дерево строится по сигнатурам. Переиспользование: одна реализация авторизации на все роуты (и dependencies=[Depends(...)] на роутере — применение без параметра). Override в тестах: app.dependency_overrides[get_db] = fake_db — подмена БД/внешних сервисов без моков импортов. Жизненный цикл: зависимость с yield даёт cleanup после ответа (классика — сессия БД: open → yield → close; нюансы сессий — в статье про SQLAlchemy).
Типовые зависимости из практики
Стандартный набор, который собирается в каждом проекте: сессия БД (yield-зависимость на запрос); текущий пользователь (чтение токена из заголовка — через HTTPBearer/OAuth2PasswordBearer, проверка, 401 при провале); пагинация (общая зависимость Page с offset/limit, переиспользуемая во всех списках); настройки (pydantic-settings объект, кеш через lru_cache); проверка прав (зависимость «требует роль», параметризованная через класс с __call__). Антипаттерн — толстая зависимость, делающая «всё» (БД + кеш + метрики в одной функции): зависимости должны быть маленькими и композируемыми, как функции.
Sync/async и фоновые задачи
FastAPI позволяет смешивать async def и def-эндпоинты, и правило простое: эндпоинт с блокирующими вызовами (синхронная БД, requests) объявляется обычным def — FastAPI выполнит его в пуле потоков; async def — только когда внутри всё асинхронное (httpx/asyncpg), иначе блокировка event loop (механика — в статье про asyncio). Быстрые «после-ответа» действия — BackgroundTasks (выполняются в том же процессе после отправки ответа); тяжёлое и требующее ретраев — внешняя очередь (Celery — в статье про фоновые задачи). Lifespan-контекст (asynccontextmanager на lifespan=) — место создания пулов/клиентов на старте приложения, заменяет устаревшие on_event-хуки.
Ошибки, логирование и эксплуатация
Единый формат ошибок — через exception handlers (RequestValidationError уже отдаёт 422; свои доменные исключения маппятся в 4xx централизованно — каждый роутер не изобретает свой JSON). Request ID и структурированные логи — middleware (подход к логам — в статье про структурированное логирование). CORS — CORSMiddleware с явными origin (не «*» на проде с credentials). Запуск — uvicorn (локально --reload), за reverse-proxy с proxy-headers; тесты — TestClient/httpx.AsyncClient с override-зависимостями.
Практический сценарий: сборка эндпоинта на зависимостях
Соберём типовой «защищённый список» целиком, чтобы увидеть систему в сборе. Зависимость get_settings возвращает объект настроек из переменных окружения, закешированный lru_cache — создаётся один раз на процесс. Зависимость get_db с yield открывает сессию SQLAlchemy и закрывает её после ответа — независимо от того, успешно прошёл запрос или с исключением. Зависимость get_current_user принимает Authorization: HTTPAuthorizationCredentials (схема HTTPBearer объявляет заголовок для OpenAPI), декодирует JWT, проверяет подпись и срок, поднимает пользователя из БД — и бросает 401 с внятной деталью при любом провале; она сама зависит от get_db — дерево строится автоматически. Зависимость require_admin принимает пользователя из get_current_user и проверяет роль — 403 при отказе. Эндпоинт GET /orders в сигнатуре перечисляет db, user = Depends(require_admin) и зависимость пагинации page: Page = Depends() — тело функции занимает пять строк: запрос, сериализация через response_model=list[OrderOut], возврат. В тестах app.dependency_overrides подменяет get_current_user на заглушку с нужной ролью — и тот же эндпоинт проверяется и для админа, и для гостя без единого реального токена. Каждая зависимость при этом тестируется отдельно как обычная функция — вот что означает «архитектура из объявлений».
FAQ
Depends или middleware? Middleware — сквозное для всех запросов (request id, тайминги, логи); Depends — для логики конкретных роутов с доступом к параметрам и DI-дереву. «Доставать пользователя» в middleware на всех путях, включая healthcheck, — антипаттерн.
Почему 422, а не 400? 422 Unprocessable Entity — «запрос синтаксически корректен, но семантически невалиден»; FastAPI следует этой конвенции для ошибок валидации. Свои ошибки бизнес-логики — 400/409/422 по смыслу, главное — единообразие, зафиксированное в OpenAPI.
Можно ли без pydantic? Технически да (dict-тела), но теряется всё: валидация, документация, типизация. FastAPI без pydantic — как ORM без моделей; если не нужна валидация — возможно, нужен более лёгкий фреймворк.
Как версионировать API?
Практичнее всего — префикс пути (/api/v1/..., роутеры по версиям) с общими pydantic-моделями и явными ломающими изменениями только в новой версии; заголовочное версионирование — реже и сложнее для клиентов.
Источники: документация FastAPI (dependencies, body), pydantic — ссылки в шапке.