Python Backend Cheatsheet: Django, Django REST Framework, FastAPI, and Flask

📅 December 4, 2025
⏱️ 20 min read

Python powers some of the busiest web backends in the world. The ecosystem offers four dominant choices — Django, Django REST Framework, FastAPI, and Flask — each with a distinct philosophy. This article covers architecture, core patterns, async/sync tradeoffs, authentication, and deployment, then distils the essentials into a practical cheatsheet and a side-by-side comparison to help you pick the right tool.

For the surrounding infrastructure — reverse proxies, process managers, and containerisation — see the DevOps Ready Reckoner.

Choosing a Python Web Framework

FrameworkBatteries-included?Built-in adminAsyncAPI-firstTypical use
DjangoYesYesPartialNo (adds DRF)Monoliths, CMS, admin panels
Django REST FrameworkOn top of DjangoYesPartialYesREST APIs on Django
FastAPINoNoYesYesModern APIs, microservices, ML services
FlaskNoNoLimitedNo (extensions)Small APIs, prototypes, glue
Short version: Django for full-featured web apps with an admin. Django REST Framework for REST APIs backed by Django. FastAPI for performance-sensitive or async-heavy APIs. Flask for small services where you want to pick your own libraries.

Architecture: Sync, Async, and ASGI/WSGI

All four frameworks can be served over WSGI. FastAPI and modern Django also support ASGI, which enables async handlers and WebSocket endpoints.

WSGI  -> requests processed synchronously (one thread per request)
ASGI  -> requests can run asynchronously (event loop)
           also supports WebSockets and background tasks

The request flow is conceptually:

Client
  |
HTTP request
  |
Server (gunicorn / uvicorn / daphne)
  |
Router -> View/Handler -> Business logic -> Model -> Database
  |
Response body
  |
Client

Django

Django convention: one app per bounded context, each with its own models.py, views.py, urls.py, and tests.py. The manage.py CLI drives almost everything.

Project layout

myblog/
  manage.py
  myblog/
    __init__.py
    settings.py
    urls.py
    wsgi.py
    asgi.py
  blog/
    __init__.py
    admin.py
    apps.py
    models.py
    tests.py
    views.py

Settings excerpt

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    ...
    'blog',
]
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydb',
        'USER': 'myuser',
        'PASSWORD': '***',
        'HOST': 'db',
        'PORT': '5432',
    }
}

Models and migrations

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    body = models.TextField()
    published_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title
python manage.py makemigrations
python manage.py migrate

Views, templates, and URLs

from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from .models import Post

def post_list(request):
    posts = Post.objects.all()
    return render(request, 'blog/post_list.html', {'posts': posts})

def post_detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    return render(request, 'blog/post_detail.html', {'post': post})
# blog/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.post_list, name='post_list'),
    path('post//', views.post_detail, name='post_detail'),
]
# blog/templates/blog/post_list.html
{% extends 'base.html' %}
{% block content %}
  {% for post in posts %}
    
  {% endfor %}
{% endblock %}

Django REST Framework

DRF layers a RESTful API and automatic OpenAPI schema generation on top of a Django project.

# serializers.py
from rest_framework import serializers
from .models import Post

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = '__all__'
# views.py
from rest_framework import viewsets
from rest_framework.response import Response
from .models import Post
from .serializers import PostSerializer

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
DRF convention: ModelViewSet gives you list, create, retrieve, update, and destroy for free. Override individual actions when business rules diverge.

FastAPI

FastAPI uses Python type hints to define routes, validate requests, and generate documentation automatically.

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List

app = FastAPI(title="My API")

class PostIn(BaseModel):
    title: str
    body: str

class Post(PostIn):
    id: int

DB: List[Post] = []

@app.get("/posts", response_model=List[Post])
def list_posts():
    return DB

@app.get("/posts/{post_id}", response_model=Post)
def read_post(post_id: int):
    for post in DB:
        if post.id == post_id:
            return post
    raise HTTPException(status_code=404, detail="Not found")

@app.post("/posts", response_model=Post, status_code=201)
def create_post(payload: PostIn):
    post = Post(id=len(DB) + 1, **payload.dict())
    DB.append(post)
    return post

Flask

Flask is a micro-framework: a routing layer, Jinja2 templating, and a development server. Everything else is an extension.

from flask import Flask, jsonify, request, abort
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db"
db = SQLAlchemy(app)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200))
    body = db.Column(db.Text)

@app.route("/posts")
def list_posts():
    posts = Post.query.all()
    return jsonify([{"id": p.id, "title": p.title, "body": p.body} for p in posts])

if __name__ == "__main__":
    app.run(debug=True)

Flask keeps you close to the metal. That gives you freedom — and responsibility.

Authentication and Authorization

Authentication answers "who is this?", authorization answers "what may they do?".

FrameworkAuth mechanismNotes
Djangodjango.contrib.authUsers, groups, permissions built in
DRFDRF tokens, JWT, SessionIsAuthenticated, DjangoModelPermissions
FastAPIfastapi-users, JWT, OAuth2Depends-based security utilities
FlaskFlask-Login, Flask-JWTExtensions

Common Patterns

Dependency injection (FastAPI)

from fastapi import Depends

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.get("/posts")
def list_posts(db: Session = Depends(get_db)):
    return db.query(Post).all()

Middleware and request hooks

# FastAPI middleware
@app.middleware("http")
async def add_process_header(request: Request, call_next):
    response = await call_next(request)
    response.headers["X-Process-Time"] = "0.42s"
    return response

Validation and Serialisation

All four frameworks can validate data. Pydantic (FastAPI, and usable standalone) and DRF serializers are the dominant approaches.

ApproachValidationSerialisation
Django formsYesForm rendering
DRF serializersYesTo/from model
Pydantic (FastAPI)Type-drivenModel -> dict
Flask-WTFYesForm rendering

Testing

# Django
from django.test import TestCase, Client
from .models import Post

class PostTests(TestCase):
    def test_list(self):
        client = Client()
        response = client.get("/")
        self.assertEqual(response.status_code, 200)

# FastAPI / Flask (pytest + httpx / flask testing)
from fastapi.testclient import TestClient
client = TestClient(app)

def test_list_posts():
    response = client.get("/posts")
    assert response.status_code == 200

Deployment

Run behind a production WSGI/ASGI server and a reverse proxy (nginx) for TLS termination and static assets.

Python app
  |
WSGI/ASGI server (gunicorn, uvicorn, daphne)
  |
Reverse proxy (nginx)
  |
Load balancer / Internet
CombinationServerNotes
Django / FlaskgunicornWSGI workers
Django asyncdaphne + gunicornASGI
FastAPIuvicorn + gunicornworkers=uvicorn.workers.UvicornWorker
Django (modern)uvicorn + asgirefASGI path
# gunicorn for Django/Flask
gunicorn myproject.wsgi:application \
  --workers 4 --bind 0.0.0.0:8000

# uvicorn for FastAPI
gunicorn myapp.main:app \
  -k uvicorn.workers.UvicornWorker \
  --workers 4 --bind 0.0.0.0:8000

Python Backend Cheatsheet

Bootstrapping a project

# Django
django-admin startproject mysite
cd mysite
python manage.py startapp blog
python manage.py migrate
python manage.py runserver

# Flask
pip install flask
flask --app app run

# FastAPI
pip install "fastapi[all]"
uvicorn app:app --reload

Adding a record

# Django ORM
Post.objects.create(title="Hi", body="World")

# DRF: validated through serializer
PostSerializer(data={...}).save()

# SQLAlchemy (Flask/FastAPI pattern)
db.add(Post(title="Hi")); db.commit()

Running the development server

# Django
python manage.py runserver

# Flask
flask --app app run --debug

# FastAPI
uvicorn app:app --reload

Migrations and schema

# Django
python manage.py makemigrations
python manage.py migrate

# Flask-Migrate
flask db migrate
flask db upgrade
Production rule: never run manage.py runserver, flask run, or uvicorn --reload in production. Use a production server (gunicorn/uvicorn/daphne) behind nginx.

Framework Comparison

Dimension Django DRF FastAPI Flask
Built-in adminYesYesNoNo
Built-in ORMYesYesNoNo
Built-in authYesDjango authNoNo
Async supportPartialPartialYesLimited
Auto docs (OpenAPI)NoYesYesNo
Type validationFormsSerializersPydanticFlask-WTF
Routing styleFunction/classFunction/classDecoratorsDecorators
CLImanage.pymanage.pyCustom (typer/click)flask CLI
Learning curveModerate-HighModerateModerateLow
Ecosystem sizeHugeHugeGrowing fastHuge
Performance (typical)GoodGoodVery goodGood

When to Choose Which

ScenarioPickWhy
Admin panel / CMSDjangoBuilt-in admin and ORM
REST API on existing data modelDRFSerializer + permission layer over Django
Microservices / async I/OFastAPIASGI, type-driven validation, auto docs
Small glue service / prototypeFlaskMinimal, pick your own stack
Machine learning model servingFastAPIAsync, Pydantic, pydantic-settings
Heavy legacy DB, no schema controlDjangoUnmanaged models
Performance-critical APIFastAPIuvicorn + type-driven validation

Conclusion

Python's web ecosystem gives you a tool for almost every job: Django when you want batteries included, Django REST Framework when you are already on Django and need an API layer, FastAPI when performance, async, or type safety matter, and Flask when you want to choose every dependency yourself.

The most important choice is not which framework has the best benchmark — it is which framework your team can operate safely, patch, and reason about in production. Whichever you choose, standardise on one WSGI/ASGI server, one reverse proxy pattern, one secrets store, one test strategy, and one deployment pipeline. The framework is the small part; the operations around it are what keep systems secure and reliable.

This can also be extended into a Part 2: Python Deployment Cheatsheet, covering nginx gunicorn configs, systemd unit files, Let's Encrypt, health checks, and container images for Django, DRF, FastAPI, and Flask.

Topics & Tags
Python Django Django REST Framework FastAPI Flask Backend API Architecture Cheat Sheet Security DevOps Cloud Engineering