Backend Frameworks
Building APIs with FastAPI and content-heavy apps with Django -- dependency injection, middleware, and when to choose which
Backend Frameworks
Python is a serious backend language, and two frameworks dominate. FastAPI is the modern, type-hint-driven choice for APIs; Django is the batteries-included framework for content-heavy applications and admin interfaces. This page covers both, and the judgement of when each is the right tool.
Backend Frameworks
FastAPI
FastAPI is the modern choice for Python APIs. It is built on type hints and delivers automatic validation, serialization, and OpenAPI documentation:
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel, EmailStr
from datetime import datetime
app = FastAPI()
class UserCreate(BaseModel):
email: EmailStr
name: str
age: int
class UserResponse(BaseModel):
id: str
email: str
name: str
created_at: datetime
@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate, db=Depends(get_db)):
if await db.users.find_one({"email": user.email}):
raise HTTPException(status_code=409, detail="Email already registered")
result = await db.users.insert_one(user.model_dump())
return UserResponse(
id=str(result.inserted_id),
email=user.email,
name=user.name,
created_at=datetime.utcnow(),
)Dependency injection is central to FastAPI. Dependencies are declared as function parameters and resolved automatically:
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession
async def get_db() -> AsyncGenerator[AsyncSession, None]:
async with async_session_factory() as session:
yield session
async def get_current_user(
token: str = Depends(oauth2_scheme),
db: AsyncSession = Depends(get_db),
) -> User:
payload = decode_jwt(token)
user = await db.get(User, payload["sub"])
if not user:
raise HTTPException(status_code=401)
return user
@app.get("/me")
async def read_profile(user: User = Depends(get_current_user)):
return userDjango
Django remains the right choice for content-heavy applications, admin interfaces, and teams that want batteries-included:
# models.py
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
content = models.TextField()
published_at = models.DateTimeField(null=True, blank=True)
author = models.ForeignKey("auth.User", on_delete=models.CASCADE)
class Meta:
ordering = ["-published_at"]
def is_published(self) -> bool:
return self.published_at is not NoneDjango middleware intercepts every request and response. It is the right place for cross-cutting concerns:
class RequestTimingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
start = time.perf_counter()
response = self.get_response(request)
duration = time.perf_counter() - start
response["X-Request-Duration"] = f"{duration:.4f}s"
return response