Переход с Flask на FastAPI: что учесть и где споткнётесь
Flask-приложение выросло, и вы решили переехать на FastAPI ради асинхронности и автодокументации. Разбираем реальные отличия в роутинге, DI, валидации и работе с базой — и показываем, чем за это придётся заплатить.

Почему очевидный «перенос маршрутов» не работает
Flask и FastAPI оба раздают HTTP, оба используют декораторы для маршрутов, оба запускаются одной командой. На этом сходства заканчиваются. FastAPI построен поверх Starlette и работает в ASGI-цикле, а Flask — WSGI. Разница не в буквах аббревиатуры, а в том, что весь I/O-код, который вы годами писали синхронно, в FastAPI нужно либо переписать на async, либо явно обернуть — иначе один медленный запрос к внешнему API заблокирует весь event loop.
Ниже — четыре области, где перенос ломается чаще всего, с конкретными приёмами и ценой каждого решения.
Роутинг и декораторы: похоже, но не то же
В Flask маршрут объявляется через @app.route, а тип HTTP-метода передаётся аргументом methods=["GET"]. В FastAPI каждый метод — отдельный декоратор: @app.get, @app.post и т.д. Выглядит косметикой, но влечёт за собой другую организацию файлов: если вы привыкли к Flask-блюпринтам, в FastAPI их роль выполняет APIRouter.
Параметры пути и query
Flask извлекает параметры пути через конвертеры (<int:user_id>), а query-параметры — через request.args. FastAPI берёт и то, и другое прямо из сигнатуры функции и валидирует типы ещё до входа в тело обработчика. Это удобно, но означает, что любой нетипизированный маршрут из Flask придётся переписать с явной аннотацией.
# FastAPI 0.110+
from fastapi import APIRouter, Query
router = APIRouter(prefix="/users")
@router.get("/{user_id}")
async def get_user(
user_id: int,
include_orders: bool = Query(default=False, description="Подтянуть заказы"),
):
# user_id уже int, include_orders уже bool — валидация прошла до этой строки
return {"user_id": user_id, "include_orders": include_orders}
Обратите внимание: функция async. Если оставить её синхронной (без async), FastAPI запустит её в отдельном потоке через run_in_executor. Это работает, но добавляет накладные расходы на переключение контекста.
Валидация и Pydantic вместо ручных проверок
В Flask-проектах валидация входных данных обычно реализована одним из трёх способов: вручную через if, через Marshmallow или через WTForms. В FastAPI валидация встроена через Pydantic (v2 в актуальных версиях). Это и плюс, и источник боли при миграции.
Плюс: схема запроса описывается один раз и сразу становится частью OpenAPI-документации — Swagger UI доступен из коробки на /docs.
Боль: все ваши Marshmallow-схемы придётся переписать на Pydantic-модели. Автоматической конвертации нет.
# Pydantic v2
from pydantic import BaseModel, Field, EmailStr
class CreateUserRequest(BaseModel):
email: EmailStr
name: str = Field(min_length=1, max_length=200)
age: int = Field(ge=0, le=150)
class CreateUserResponse(BaseModel):
id: int
email: EmailStr
Не пытайтесь конвертировать Marshmallow-схемы одной регуляркой. Различий в поведении дефолтов, вложенных объектов и кастомных валидаторов достаточно, чтобы получить молча работающий, но неправильно валидирующий код.
Для проектов с десятками Marshmallow-схем разумная стратегия — переносить схемы по мере миграции эндпоинтов, а не все разом.
Синхронный код в асинхронном мире
Это главный источник багов при переезде. Flask работает в синхронном WSGI-режиме: каждый запрос обрабатывается в своём потоке. FastAPI работает в ASGI — один event loop, и блокирующий вызов в async-функции замораживает обработку всех остальных запросов.
Два пути для «старого» синхронного кода
| Подход | Как сделать | Цена |
|---|---|---|
Оставить функцию синхронной (без async) | FastAPI автоматически запустит её в thread pool | Ограничение на количество потоков; ORM-сессии должны быть thread-safe |
Переписать на async + асинхронный драйвер | Использовать asyncpg, aiohttp, httpx.AsyncClient вместо синхронных аналогов | Переписывание слоя доступа к данным; новые зависимости |
Если ваш проект использует SQLAlchemy, обратите внимание на AsyncSession, доступный в SQLAlchemy 2.x. Он позволяет работать с базой без блокировки event loop, но требует асинхронного драйвера (например, asyncpg для PostgreSQL). Детали API и совместимость версий стоит сверять в официальной документации SQLAlchemy, потому что интерфейс async-части менялся между релизами 1.4 и 2.x.
Dependency Injection вместо глобальных объектов
Flask поощряет глобальные объекты: g, current_app, расширения, привязанные к экземпляру приложения. FastAPI заменяет это системой Dependency Injection через Depends.
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession
async def get_db_session() -> AsyncSession:
async with async_session_factory() as session:
yield session # сессия закроется после завершения запроса
@router.post("/users", response_model=CreateUserResponse)
async def create_user(
body: CreateUserRequest,
db: AsyncSession = Depends(get_db_session),
):
# db — готовая к работе сессия, время жизни управляется фреймворком
...
Плюс: зависимости легко подменять в тестах, не трогая глобальное состояние. Минус: если у вас десятки мест, где код обращается к flask.g или current_app.config, каждое нужно заменить на явную зависимость. Это рутина, но она делает скрытые связи явными — и часто вскрывает проблемы, которые тихо жили во Flask-версии.
Middleware и обработка ошибок
Flask использует @app.errorhandler и before_request/after_request. В FastAPI есть два механизма: ASGI-middleware (на уровне Starlette) и exception handlers.
- Exception handlers — регистрируются через
@app.exception_handler(SomeException). Прямой аналог Flask-обработчиков, только функция должна бытьasyncи приниматьRequest+ исключение. - Middleware — класс или функция, оборачивающая весь цикл запрос-ответ. Если вы использовали
before_requestдля проверки токенов — в FastAPI это удобнее делать черезDepends, а не middleware. - CORS — в Flask это Flask-CORS, в FastAPI — встроенный
CORSMiddlewareиз Starlette. Не забудьте подключить, иначе фронтенд, привыкший к старому бэкенду, перестанет работать.
Чек-лист миграции
- Составить список всех маршрутов Flask-приложения (удобно через
app.url_map). Проверка: список получен, количество совпадает с ожидаемым. - Перенести маршруты на
APIRouter, начав с тех, у которых нет сложных зависимостей. Проверка:/docsпоказывает эндпоинты. - Переписать Marshmallow-схемы / ручную валидацию на Pydantic-модели. Проверка: некорректный запрос возвращает 422 с описанием ошибки.
- Заменить обращения к
flask.g,current_appи расширениям наDepends. Проверка: тесты проходят без запуска Flask-контекста. - Определить синхронные I/O-вызовы (БД, HTTP, файлы) и решить для каждого: оставить синхронным или переписать на async. Проверка: под нагрузкой event loop не блокируется (метрика: время ответа не деградирует при росте конкурентных запросов).
- Перенести middleware и обработчики ошибок. Проверка: кастомные коды ответа и CORS работают как раньше.
- Настроить запуск через Uvicorn (или Gunicorn с Uvicorn-воркерами для продакшена). Проверка: приложение отвечает на
healthcheck.
Сноска: ASGI (Asynchronous Server Gateway Interface) — спецификация, определяющая взаимодействие между асинхронным Python-веб-приложением и сервером. Пришла на смену WSGI, которая поддерживает только синхронную обработку.
Что делать дальше
После переноса маршрутов и валидации проект формально работает, но настоящая отдача от FastAPI начинается, когда вы переводите I/O на асинхронные драйверы и начинаете использовать фоновые задачи (BackgroundTasks) вместо Celery для лёгких операций вроде отправки писем. Отдельная тема — WebSocket-эндпоинты, которые в Flask требовали flask-socketio и отдельного сервера, а в FastAPI поддерживаются нативно. Но это уже следующий шаг: сначала убедитесь, что синхронный слой не блокирует event loop под реальной нагрузкой. Измеряйте P95 латентности при конкурентности, характерной для вашего проекта, — именно эта метрика покажет, действительно ли миграция дала результат.
Проектная работа: От проблемы до защиты: продукт, этапы, вопросы комиссии
Спросить за 15 ₽Источники: FastAPI — официальная документация, SQLAlchemy — Asyncio Extension
Частые вопросы
Можно ли запускать Flask и FastAPI параллельно во время миграции?
Обязательно ли переписывать все функции на async?
Что делать, если библиотека не поддерживает async?
Swagger UI в FastAPI полностью заменяет Postman-коллекции?
Как перенести тесты, написанные на pytest + Flask test client?
А если после миграции производительность не выросла?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.