FastAPI остаётся одним из самых удобных способов собрать HTTP API на Python: асинхронный ASGI-сервер, валидация через Pydantic, автоматическая документация OpenAPI и минимум шаблонного кода. В 2026 году он хорошо сочетается с uv, Pydantic v2 и современными драйверами баз данных. Разберём рабочий каркас сервиса, типичные ошибки производительности и проверку API тестами.
Окружение и установка
Не смешивайте зависимости проекта с системным Python. Виртуальное окружение или uv изолируют версии и делают сборку воспроизводимой.
python -m venv .venv
source .venv/bin/activate
pip install "fastapi[standard]" uvicorn
fastapi --version
Пакет fastapi[standard] приносит CLI, сервер разработки и клиентские зависимости. Для продакшена достаточно fastapi и uvicorn, но команда fastapi dev удобна локально: она следит за файлами и перезапускает процесс.
Минимальное приложение
Модели Pydantic описывают вход и выход, а response_model гарантирует, что наружу не утекут лишние поля. Это не только документация, но и защита от случайной выдачи внутренних атрибутов.
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field
app = FastAPI(title="Catalog API", version="1.0")
class ItemIn(BaseModel):
name: str = Field(min_length=1, max_length=120)
price: float = Field(gt=0)
class Item(ItemIn):
id: int
DB: dict[int, Item] = {}
@app.post("/items", response_model=Item, status_code=status.HTTP_201_CREATED)
async def create_item(payload: ItemIn) -> Item:
item = Item(id=len(DB) + 1, **payload.model_dump())
DB[item.id] = item
return item
Обработчик объявлен async, потому что внутри нет блокирующих вызовов. Если бы там был синхронный драйвер базы или тяжёлый расчёт, быстрее было бы объявить обычную функцию: FastAPI выполнит её в пуле потоков и не заблокирует цикл событий.
Маршруты и ошибки
HTTP-коды — часть контракта. Не возвращайте 200 на ошибку и не смешивайте «не найдено» с «неверный запрос».
@app.get("/items/{item_id}", response_model=Item)
async def get_item(item_id: int) -> Item:
if item_id not in DB:
raise HTTPException(status_code=404, detail="Item not found")
return DB[item_id]
Удаление обычно возвращает 204 No Content без тела ответа, а повторное удаление того же объекта — тоже 204, если вы хотите сделать операцию идемпотентной. Для ошибок валидации FastAPI сам формирует 422 и подробный список полей.
Валидация типов выполняется до вызова функции: строка "abc" в пути с типом int даст 422 без вашего кода. Это удобно, но не заменяет проверку бизнес-правил: уникальность имени, права пользователя и состояние объекта проверяются отдельно.
Зависимости и база данных
Depends выносит общую логику: сессию базы, текущего пользователя, настройки. Зависимость с yield закрывает ресурс после ответа, даже если обработчик упал с исключением.
async def get_session() -> AsyncSession:
async with SessionLocal() as session:
yield session
Фабрику SessionLocal создают один раз при старте через async_sessionmaker, привязанный к асинхронному движку. Для PostgreSQL берите драйвер asyncpg или psycopg в асинхронном режиме. Синхронный psycopg2 внутри async-функции блокирует весь воркер: под нагрузкой это выглядит как «сервер завис» при низком CPU.
Аутентификация
Защита собственного API строится на короткоживущих токенах и проверке подписи. Пароли храните только в виде хеша с солью, например через pwdlib с Argon2. Секрет подписи держите в переменной окружения, а не в репозитории.
from fastapi.security import OAuth2PasswordBearer
import jwt
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
async def current_user(token: str = Depends(oauth2_scheme)) -> str:
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")
return payload["sub"]
Проверяйте срок действия и аудиторию токена, а не только подпись. Для внутренних сервисов вместо самописных токенов иногда разумнее mTLS или подписанные заголовки от доверенного прокси. Не храните токены в localStorage без необходимости и не логируйте их.
Запуск и производительность
Режим разработки не предназначен для нагрузки. В продакшене запускайте несколько воркеров под управлением systemd или контейнера, а TLS и сжатие отдайте обратному прокси.
fastapi dev app/main.py
uvicorn app.main:app --host 127.0.0.1 --port 8000 --workers 4
curl -s localhost:8000/items -H "Content-Type: application/json" \
-d '{"name": "Cable", "price": 5}'
curl -s localhost:8000/openapi.json | head -c 200
Число воркеров подбирайте по ядрам и типу нагрузки, а не копируйте «4» из статьи. Для ввода-вывода хватает одного процесса с async-кодом, для CPU-задач нужны процессы или отдельная очередь. Проверяйте задержку p95 и p99, а не только среднее значение.
CORS, тесты и схема
Если API вызывается из браузера с другого домена, добавьте CORSMiddleware и перечислите разрешённые источники явно. Не ставьте allow_origins=["*"] вместе с учётными данными: браузер такую комбинацию отклонит, а широкое разрешение откроет лишнее. Проверяйте заголовки в браузере и в тестах.
Тесты через TestClient не требуют поднятого сервера и проверяют сериализацию, коды и валидацию.
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_create_item():
r = client.post("/items", json={"name": "Cable", "price": 5})
assert r.status_code == 201
assert r.json()["id"] == 1
def test_not_found():
assert client.get("/items/999").status_code == 404
Для асинхронных зависимостей используйте httpx.AsyncClient с транспортом ASGI. Подменяйте базу на тестовую через dependency_overrides, чтобы тесты не зависели от сети и порядка запуска.
Структура проекта
app/main.py— создание приложения и подключение роутеров.app/api/— маршруты, разбитые по доменам.app/models.pyиapp/schemas.py— ORM и Pydantic-модели раздельно.app/deps.py— зависимости и настройки.tests/— тесты API и бизнес-логики.
Чек-лист
- Версии зафиксированы, окружение изолировано.
- Для каждого маршрута задан
response_modelи корректный код. - Блокирующие вызовы не выполняются внутри
async-функций. - Секреты в переменных окружения, токены короткоживущие.
- Есть тесты на 401, 404, 422 и успешный сценарий.
- Документация
/docsзакрыта или ограничена в продакшене.
Итог
FastAPI даёт компактный и предсказуемый каркас API: Pydantic проверяет данные, зависимости управляют ресурсами, OpenAPI описывает контракт. Основные риски связаны не с фреймворком, а с блокирующими вызовами в async-коде, слабой аутентификацией и запуском dev-сервера в продакшене. Начните с явных моделей и тестов, затем добавляйте базу, кеш и воркеры — по измеренной нагрузке.