Python (Django/FastAPI) Backend Development for Mobile Applications
Python is the second most popular language for mobile backends after Node.js. Two dominant options: Django REST Framework — batteries-included, ORM, admin, auth out of the box; FastAPI — async, type-safe, rapid startup. Choice between them depends on speed-to-market, performance, and team size.
FastAPI: When You Need Speed and Type Safety
FastAPI builds on Pydantic and Starlette. Each endpoint automatically validates input data via type annotations and generates OpenAPI documentation:
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel, UUID4
from sqlalchemy.ext.asyncio import AsyncSession
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
class CreatePostRequest(BaseModel):
title: str
content: str
author_id: UUID4
class PostResponse(BaseModel):
id: UUID4
title: str
content: str
created_at: datetime
class Config:
from_attributes = True
@app.post("/posts", response_model=PostResponse, status_code=201)
async def create_post(
body: CreatePostRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
if body.author_id != current_user.id:
raise HTTPException(status_code=403, detail="Forbidden")
post = await post_service.create(db, body)
return post
Pydantic v2 (Rust-based) validates data faster than most alternatives. response_model auto-serializes ORM objects and hides fields not in schema (password hash won't appear in response).
SQLAlchemy 2.x + AsyncSession for async PostgreSQL work via asyncpg:
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
engine = create_async_engine(settings.DATABASE_URL) # postgresql+asyncpg://...
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async def get_db():
async with AsyncSessionLocal() as session:
yield session
Alembic for database schema migrations — generates diffs between models and current DB.
JWT Authentication
from jose import JWTError, jwt
from datetime import datetime, timedelta
def create_access_token(user_id: str) -> str:
expires = datetime.utcnow() + timedelta(minutes=15)
return jwt.encode(
{"sub": user_id, "exp": expires, "type": "access"},
settings.JWT_SECRET,
algorithm="HS256",
)
async def get_current_user(
token: str = Depends(oauth2_scheme),
db: AsyncSession = Depends(get_db),
) -> User:
try:
payload = jwt.decode(token, settings.JWT_SECRET, algorithms=["HS256"])
user_id: str = payload.get("sub")
except JWTError:
raise HTTPException(status_code=401, detail="Could not validate token")
user = await user_repo.get(db, user_id)
if not user:
raise HTTPException(status_code=401, detail="User not found")
return user
Django REST Framework: When You Need the Full Stack
DRF wins when you need quick admin panel, powerful ORM with select_related/prefetch_related, built-in permissions and throttling:
# serializers.py
class PostSerializer(serializers.ModelSerializer):
author_name = serializers.SerializerMethodField()
class Meta:
model = Post
fields = ['id', 'title', 'content', 'author_name', 'created_at']
read_only_fields = ['id', 'created_at']
def get_author_name(self, obj):
return obj.author.get_full_name()
# views.py
class PostViewSet(ModelViewSet):
serializer_class = PostSerializer
permission_classes = [IsAuthenticated]
throttle_classes = [UserRateThrottle]
def get_queryset(self):
return Post.objects.filter(author=self.request.user)\
.select_related('author')\
.order_by('-created_at')
select_related solves N+1: one SQL JOIN instead of one query per author. prefetch_related for many-to-many.
django-channels for WebSocket (chat, realtime). Celery + Redis for background tasks (email, push notifications, heavy computation).
# tasks.py
from celery import shared_task
import firebase_admin.messaging as fcm
@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def send_push_notification(self, token: str, title: str, body: str):
try:
message = fcm.Message(
token=token,
notification=fcm.Notification(title=title, body=body),
android=fcm.AndroidConfig(priority='high'),
apns=fcm.APNSConfig(payload=fcm.APNSPayload(aps=fcm.Aps(sound='default'))),
)
fcm.send(message)
except Exception as exc:
raise self.retry(exc=exc)
Approach Comparison
| Criterion | FastAPI | Django REST Framework |
|---|---|---|
| Async out of box | Yes (asyncio) | Partial (Django 4.1+) |
| Startup speed | High | Medium |
| Admin panel | No (third-party) | Built-in |
| ORM | SQLAlchemy / Tortoise | Django ORM |
| API documentation | Auto-generation (Swagger) | drf-spectacular |
| Suits | New projects, API-only | Fast MVP with admin |
Common Pitfall: Sync Code in Async FastAPI
# Bad: blocks event loop
@app.get("/data")
async def get_data():
return requests.get("https://external-api.com/data").json() # sync!
# Good
@app.get("/data")
async def get_data():
async with httpx.AsyncClient() as client:
response = await client.get("https://external-api.com/data")
return response.json()
requests in async def blocks entire event loop: all concurrent requests queue up. httpx.AsyncClient is the async alternative.
Deployment
FastAPI/Django runs via Uvicorn + Gunicorn (multiple workers × async). Docker with multi-stage builds for minimal image. Nginx as reverse proxy. PostgreSQL + Redis in docker-compose locally, RDS + ElastiCache on AWS.
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
CMD ["gunicorn", "app.main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000"]
What's Included in Development
API design for mobile client requirements. PostgreSQL + Alembic migration setup. Auth (JWT). CRUD modules. Push notifications (Firebase Admin SDK). Background tasks (Celery). Docker + CI/CD. OpenAPI documentation.
Timeline
MVP (Auth + 3–5 resources): 2–3 weeks. Full backend: 1–3 months. Pricing after requirements analysis.







