Steven's Knowledge

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 user

Django

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 None

Django 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

On this page