Два проекта в одном году. В одном API переписали на FastAPI, прод стал вдвое быстрее. В другом через неделю вернули Django. Оба выбора были сознательными.
Лучшего фреймворка нет. Есть задача, размер команды и цена, которую платят за админку или за честный async.
Django 5.2 LTS вышел 2 апреля 2025, поддержка до апреля 2028. FastAPI к декабрю стоит на 0.124.4. У первого двадцать лет банков и госпорталов. У второго семь лет и await без sync_to_async.
Я восемь месяцев таскал код туда и обратно. Ниже то, что осталось после грабель.
Что умеет каждый
Django 5.2 умеет async views, половину ORM (aget(), acreate(), asave()), админку на пятьдесят таблиц за вечер и четыре с лишним тысячи пакетов. Не умеет async-транзакции. Async admin не умеет и не планирует.
Включил async views: половина middleware осталась sync, получил штраф. Написал транзакцию: завернул в sync_to_async. Захотел кастомизировать админку: через три часа проще писать свою.
Спасает, когда MVP нужен вчера, джун сломал миграцию, а в три ночи в логах ORM видно, куда упало.
FastAPI 0.124 умеет async из коробки, OpenAPI, Pydantic V2, dependency injection, нативные WebSocket и SSE. Админки нет. Миграций нет, Alembic руками.
Нужна админка: два дня на FastAPI-Admin, она кривая, ещё день на костыли. Забыл миграцию: схема прода собирается по логам. Джун спрашивает «как сделать вот это»: показываешь пять способов.
Спасает, когда три горячих эндпоинта жрут железо, партнёрам нужна документация, а чат на WebSocket должен быть за пятьдесят строк.
Производительность
Пятница, 23:45. Трафик вырос в десять раз. Django начал отдавать 503. Восемь инстансов справились. После переписывания на FastAPI хватило трёх.
На одинаковом железе (4 vCPU, 8 GB RAM, uvicorn/gunicorn) простой JSON:
# FastAPI (async endpoint)
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id, "name": "Item"}
# Результат: ~25000 req/s (wrk benchmark)# Django 5 (async view)
from django.http import JsonResponse
async def read_item(request, item_id):
return JsonResponse({"item_id": item_id, "name": "Item"})
# Результат: ~18000 req/s (wrk benchmark)FastAPI быстрее на 30-40%. В деньгах это два-три сервера на каждые десять.
Django async показывает 18k req/s только если нет sync middleware (прощай, сессии), нет админки и транзакции завернуты в sync_to_async. В обычном проде выходит около 12k. Разница с FastAPI уже вдвое.
Я написал красивый async view. В проде получил 12k вместо обещанных 18k.
async def my_view(request):
users = [u async for u in User.objects.all()]
return JsonResponse({"users": users})В MIDDLEWARE сидел SessionMiddleware. Django молча уходил в sync на каждый запрос. Убрал все sync middleware. Сессий нет. Админка не работает.
На простых SELECT разница незаметна: SQLAlchemy async около 15 ms на сто записей, Django async ORM около 18 ms.
# FastAPI + SQLAlchemy 2.0 async
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
async def get_users(db: AsyncSession):
result = await db.execute(select(User).limit(100))
return result.scalars().all()# Django 5 async ORM
from django.contrib.auth.models import User
async def get_users():
users = [user async for user in User.objects.all()[:100]]
return usersТранзакции в Django async не работают.
# Это не сработает
async with transaction.atomic():
await User.objects.acreate(...)
await Profile.objects.acreate(...)
# RuntimeError: atomic() не поддерживает async
# Приходится так
@sync_to_async
def create_user_with_profile(data):
with transaction.atomic():
user = User.objects.create(...)
Profile.objects.create(user=user, ...)
# Снова sync. Async где?В SQLAlchemy async-транзакции работают без этого танца.
Контракт API
Понедельник, десять утра. Пятнадцать полей, валидация в двадцати местах.
С Django два часа в serializers.py, методы clean_*, документация руками. Забыл поле: баг в проде.
С FastAPI меняешь Pydantic-модель. OpenAPI обновляется сам. Фронт смотрит /docs.
from pydantic import BaseModel, Field, validator
from typing import Annotated
class UserCreate(BaseModel):
email: str = Field(..., pattern=r"^[\w\.-]+@[\w\.-]+\.\w+$")
age: Annotated[int, Field(ge=18, le=120)]
@validator("email")
def email_must_be_lowercase(cls, v):
return v.lower()
@app.post("/users/")
async def create_user(user: UserCreate):
return userfrom django import forms
from rest_framework import serializers
class UserCreateForm(forms.Form):
email = forms.EmailField()
age = forms.IntegerField(min_value=18, max_value=120)
def clean_email(self):
return self.cleaned_data['email'].lower()
class UserSerializer(serializers.Serializer):
email = serializers.EmailField()
age = serializers.IntegerField(min_value=18, max_value=120)Время на смену контракта: DRF 30-60 минут, FastAPI 5-10. Документация в FastAPI живёт в коде.
@app.post("/items/",
summary="Create item",
response_description="Created item details",
tags=["items"])
async def create_item(
item: ItemCreate,
x_token: Annotated[str, Header(description="API token")]
):
"""
Create item with metadata:
- **name**: item name (required)
- **price**: item price in USD
"""
return itemSwagger на /docs, ReDoc на /redoc, схема на /openapi.json.
В DRF то же самое через drf-spectacular и декоратор на каждый эндпоинт:
from rest_framework.decorators import api_view
from drf_spectacular.utils import extend_schema
@extend_schema(
summary="Create item",
tags=["items"],
request=ItemSerializer,
responses={201: ItemSerializer}
)
@api_view(['POST'])
def create_item(request):
serializer = ItemSerializer(data=request.data)
if serializer.is_valid():
return Response(serializer.data, status=201)
return Response(serializer.errors, status=400)Что лежит в коробке
| Функционал | Django 5 | FastAPI |
|---|---|---|
| Admin панель | Встроенная, мощная | Нет (сторонние: FastAPI-Admin, SQLAdmin) |
| ORM | Django ORM (async) | SQLAlchemy, Tortoise ORM, SQLModel |
| Миграции | django-admin migrate | Alembic (вручную) |
| Аутентификация | django.contrib.auth | OAuth2/JWT (вручную или библиотеки) |
| CORS/CSRF | Middleware + декораторы | CORSMiddleware (Starlette) |
| Фоновые задачи | Celery, Django-Q | ARQ, Celery, BackgroundTasks |
| WebSockets | Django Channels | Встроенная поддержка |
| GraphQL | Graphene-Django | Strawberry, Ariadne |
| Тестирование | Django TestCase | pytest + httpx |
Django: батареи в комплекте, пять тысяч страниц документации, единый стиль. FastAPI: меньше магии, отдельно учить Pydantic, SQLAlchemy, Alembic.
Три случая
EdTech, инвестору нужно демо за 72 часа: модерация контента и API для мобилки. Django admin, четыре модели за полчаса. DRF, десять эндпоинтов за два часа. git push на Heroku. Через 48 часов демо было готово. FastAPI здесь съел бы два дня на админку.
Fintech, 100k запросов в минуту, Django на сорока c5.2xlarge, счёт $6k в месяц. Переписали пятнадцать горячих эндпоинтов на FastAPI, async SQLAlchemy, пул соединений. Осталось шестнадцать инстансов, $2.4k в месяц. P99: 250 ms → 95 ms. Здесь решил нативный async без штрафа за sync middleware.
SaaS, трое человек, CTO переписал всё на FastAPI «чтобы быть современными». Через месяц FastAPI-Admin тормозил, джун ломал Alembic, аутентификация у каждого сервиса своя. Откатились на Django и DRF. Команда из трёх человек не тянет зоопарк микросервисов.
Гибрид
Часто хватает обоих.
Frontend (Next.js)
↓
FastAPI (публичный API)
↓
Django (админка)
↓
Общая PostgreSQL
Публичный контур держит нагрузку. Редакторы живут в знакомой админке. Одна база.
Временный мост, пока тащишь эндпоинты:
from asgiref.sync import sync_to_async
from django.contrib.auth.models import User
@app.get("/users/{user_id}")
async def get_user(user_id: int):
user = await sync_to_async(User.objects.get)(id=user_id)
return {"id": user.id, "email": user.email}Обратно, с FastAPI на Django, тоже бывает: Pydantic-модели в DRF, бизнес-логика как есть, async оборачивается в sync views. Реже нужно.
Цифры из прода
JSON API, около 10k req/s, 4 vCPU, 8 GB, Postgres, Redis:
| Метрика | Django 5 + Gunicorn | FastAPI + Uvicorn |
|---|---|---|
| P50 latency | 45ms | 32ms |
| P99 latency | 180ms | 95ms |
| Max RPS | 8500 | 12000 |
| Memory (idle) | 250MB | 180MB |
CRUD с транзакцией, около 1k req/s:
| Метрика | Django 5 async | FastAPI + SQLAlchemy |
|---|---|---|
| Avg latency | 85ms | 78ms |
| DB pool usage | 60% | 55% |
| Error rate | 0.01% | 0.01% |
На умеренной нагрузке разница копеечная. На высокой выигрывает FastAPI.
Django async без рекламы
Админка навсегда sync.
class MyAdmin(admin.ModelAdmin):
async def get_queryset(self, request):
return await MyModel.objects.all()
# 500, SynchronousOnlyOperation, ticket: won't fixКлиент требовал админку. Я написал async API. Django держал sync middleware. Получил сложность async и скорость sync. Дальше обычно два сервиса: FastAPI снаружи, Django только для админки.
Транзакции:
from django.db import transaction
async def create_user(data):
async with transaction.atomic():
user = await User.objects.acreate(**data)
await Profile.objects.acreate(user=user)Так нельзя. Обход:
from asgiref.sync import sync_to_async
@sync_to_async
def create_user_with_transaction(data):
with transaction.atomic():
user = User.objects.create(**data)
Profile.objects.create(user=user)
await create_user_with_transaction(data)Один sync middleware, и async почти бесплатно теряется: переход sync → async → sync около 1 ms, дальше thread per request.
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
]ORM помечен как async-unsafe. Глобальное состояние не про корутины:
async def concurrent_updates():
tasks = [
User.objects.filter(id=1).aupdate(score=F('score') + 1)
for _ in range(100)
]
await asyncio.gather(*tasks)Результат может быть любым.
Работает: async views, aget / acreate / asave / adelete / aupdate, async for, async middleware если они все async, WebSocket через Channels. Не работает: admin, atomic(), часть сторонних пакетов, рендер шаблонов в sync middleware.
Django async имеет смысл, когда во view много внешних HTTP-запросов, sync middleware можно выкинуть, транзакций мало, админка не центр мира.
import httpx
async def aggregate_data(request):
async with httpx.AsyncClient() as client:
responses = await asyncio.gather(
client.get("https://api1.example.com/data"),
client.get("https://api2.example.com/data"),
client.get("https://api3.example.com/data"),
)
return JsonResponse({"data": [r.json() for r in responses]})Заказ с транзакцией лучше писать в FastAPI:
async def create_order(data):
async with async_session() as session:
async with session.begin():
order = Order(**data)
session.add(order)
await session.flush()
for item in data['items']:
order_item = OrderItem(order_id=order.id, **item)
session.add(order_item)
await session.commit()
return orderЧто нового в 2025
Django 5.2 LTS: составные первичные ключи, модели сами импортируются в python manage.py shell, MySQL по умолчанию utf8mb4, async-методы у User и permissions, Python 3.10-3.14. Async admin по-прежнему нет.
FastAPI 0.124.4: Pydantic v1 и v2 рядом, 401 вместо 403 если нет credentials, fastapi run --entrypoint module:app, починили иерархию security scopes, кеш зависимостей без scopes.
Как я выбираю
Нужна админка сегодня, команда из одного-трёх человек, клиент не понимает слово API: Django. Переплата за железо $500-1000 в месяц часто дешевле недели на самодельную админку.
Трафик больше 10k req/min, бюджет на серверы жёсткий, команда живёт в async, админка не нужна: FastAPI. Цена: одна-две недели на Alembic, аутентификацию и всё, что в Django лежит в contrib.
Нужно оба: FastAPI снаружи, Django внутри, одна PostgreSQL.
После восьми месяцев я больше не выбираю по хайпу. Django пережил сотню JS-фреймворков. FastAPI через три года тоже перестанет быть новым. Смотрю на админку, нагрузку и размер команды.
Полезное:

