$ cat docs/python-—-web-frameworks.md
updated Last week · 25 min read · published
Python — Web Frameworks Introduction
Python has three major web frameworks that dominate the ecosystem: Django (batteries-included), Flask (microframework), and FastAPI (modern async). Each solves the problem of building web applications differently, and choosing the right one depends on your project requirements.
Django provides a full-stack solution with ORM, admin, and authentication built in. Flask gives you minimal core with extensions for everything else. FastAPI leverages Python type hints for automatic validation, documentation, and async support.
Django
Django follows the "batteries-included" philosophy. It ships with an ORM, template engine, authentication system, admin panel, and more. It enforces a structured project layout with apps, settings modules, and URL configuration.
Copy 1 # settings.py — Django project configuration 2 INSTALLED_APPS = [ 3 "django.contrib.admin", 4 "django.contrib.auth", 5 "django.contrib.contenttypes", 6 "django.contrib.sessions", 7 "myapp", 8 ] 9 10 DATABASES = { 11 "default": { 12 "ENGINE": "django.db.backends.postgresql", 13 "NAME": "mydb", 14 "USER": "postgres", 15 "HOST": "localhost", 16 } 17 }
Copy 1 # models.py — Django ORM 2 from django.db import models 3 4 class Article(models.Model): 5 title = models.CharField(max_length=200) 6 slug = models.SlugField(unique=True) 7 body = models.TextField() 8 author = models.ForeignKey("auth.User", on_delete=models.CASCADE) 9 created = models.DateTimeField(auto_now_add=True) 10 published = models.BooleanField(default=False) 11 12 class Meta: 13 ordering = ["-created"] 14 15 def __str__(self): 16 return self.title 17 18 # QuerySet API 19 articles = Article.objects.filter(published=True) 20 recent = Article.objects.filter(author=user)[:5] 21 count = Article.objects.count()
Copy 1 # views.py — Django class-based and function views 2 from django.shortcuts import render, get_object_or_404 3 from django.http import JsonResponse 4 from django.views.decorators.http import require_http_methods 5 from .models import Article 6 7 # Function-based view 8 def article_detail(request, slug): 9 article = get_object_or_404(Article, slug=slug, published=True) 10 return render(request, "article_detail.html", {"article": article}) 11 12 # Class-based view 13 from django.views.generic import ListView 14 15 class ArticleListView(ListView): 16 model = Article 17 template_name = "article_list.html" 18 paginate_by = 20 19 20 def get_queryset(self): 21 return Article.objects.filter(published=True) 22 23 # JSON API view 24 @require_http_methods(["GET"]) 25 def article_api(request, slug): 26 article = get_object_or_404(Article, slug=slug) 27 return JsonResponse({ 28 "title": article.title, 29 "body": article.body, 30 "author": article.author.username, 31 })
Copy 1 # urls.py — Django URL routing 2 from django.urls import path 3 from . import views 4 5 urlpatterns = [ 6 path("", views.ArticleListView.as_view(), name="article-list"), 7 path("article/<slug:slug>/", views.article_detail, name="article-detail"), 8 path("api/article/<slug:slug>/", views.article_api, name="article-api"), 9 ] 10 11 # Path converters: str, int, slug, uuid, path 12 # Named URLs enable reverse() lookups 13 # from django.urls import reverse 14 # url = reverse("article-detail", kwargs={"slug": "my-post"})
Flask
Flask is a microframework that provides routing, request/response handling, and a development server. Everything else — database access, authentication, forms — comes from extensions. It uses decorators for routing and Jinja2 for templates.
Copy 1 # app.py — Flask application 2 from flask import Flask, request, jsonify, abort, render_template 3 from flask_sqlalchemy import SQLAlchemy 4 5 app = Flask(__name__) 6 app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db" 7 db = SQLAlchemy(app) 8 9 # Define model 10 class Article(db.Model): 11 id = db.Column(db.Integer, primary_key=True) 12 title = db.Column(db.String(200), nullable=False) 13 slug = db.Column(db.String(200), unique=True, nullable=False) 14 body = db.Column(db.Text, nullable=False) 15 published = db.Column(db.Boolean, default=False) 16 17 # Routes with decorators 18 @app.route("/") 19 def index(): 20 articles = Article.query.filter_by(published=True).all() 21 return render_template("index.html", articles=articles) 22 23 @app.route("/article/<slug>") 24 def article_detail(slug): 25 article = Article.query.filter_by(slug=slug).first_or_404() 26 return render_template("detail.html", article=article) 27 28 # REST API 29 @app.route("/api/articles", methods=["GET"]) 30 def api_articles(): 31 articles = Article.query.filter_by(published=True).all() 32 return jsonify([{ 33 "id": a.id, 34 "title": a.title, 35 "slug": a.slug, 36 } for a in articles]) 37 38 @app.route("/api/articles", methods=["POST"]) 39 def create_article(): 40 data = request.get_json() 41 article = Article( 42 title=data["title"], 43 slug=data["slug"], 44 body=data["body"], 45 ) 46 db.session.add(article) 47 db.session.commit() 48 return jsonify({"id": article.id}), 201 49 50 if __name__ == "__main__": 51 app.run(debug=True)
ℹ info
Flask's Blueprint lets you split large apps into modules. Each blueprint can have its own routes, templates, and static files — then register them on the main app.
Copy 1 # Blueprints — modular Flask applications 2 from flask import Blueprint 3 4 api = Blueprint("api", __name__, url_prefix="/api") 5 6 @api.route("/articles") 7 def list_articles(): 8 return jsonify([]) 9 10 @api.route("/articles/<int:id>") 11 def get_article(id): 12 return jsonify({"id": id}) 13 14 # Register in main app 15 app.register_blueprint(api) 16 17 # Error handlers 18 @app.errorhandler(404) 19 def not_found(e): 20 return jsonify({"error": "Not found"}), 404 21 22 @app.errorhandler(500) 23 def server_error(e): 24 return jsonify({"error": "Internal server error"}), 500
FastAPI
FastAPI is built on Starlette (ASGI) and Pydantic. It uses Python type hints for automatic request validation, JSON serialization, and OpenAPI documentation. It is natively async and one of the fastest Python web frameworks available.
Copy 1 # main.py — FastAPI application 2 from fastapi import FastAPI, HTTPException, Query 3 from pydantic import BaseModel, Field 4 from typing import Optional 5 from datetime import datetime 6 7 app = FastAPI(title="Article API", version="1.0.0") 8 9 # Pydantic models for validation 10 class ArticleCreate(BaseModel): 11 title: str = Field(..., min_length=1, max_length=200) 12 slug: str = Field(..., pattern=r"^[a-z0-9-]+$") 13 body: str = Field(..., min_length=1) 14 published: bool = False 15 16 class ArticleResponse(BaseModel): 17 id: int 18 title: str 19 slug: str 20 body: str 21 published: bool 22 created: datetime 23 24 # In-memory storage for demo 25 articles_db: dict[int, dict] = {} 26 next_id = 0 27 28 # Dependency injection 29 def get_db(): 30 return articles_db 31 32 # Routes with type hints and auto-validation 33 @app.get("/articles", response_model=list[ArticleResponse]) 34 def list_articles( 35 published: Optional[bool] = Query(None), 36 skip: int = Query(0, ge=0), 37 limit: int = Query(20, ge=1, le=100), 38 ): 39 results = list(articles_db.values()) 40 if published is not None: 41 results = [a for a in results if a["published"] == published] 42 return results[skip : skip + limit] 43 44 @app.post("/articles", response_model=ArticleResponse, status_code=201) 45 def create_article(article: ArticleCreate): 46 global next_id 47 next_id += 1 48 db_article = {**article.model_dump(), "id": next_id, "created": datetime.now()} 49 articles_db[next_id] = db_article 50 return db_article 51 52 @app.get("/articles/{article_id}", response_model=ArticleResponse) 53 def get_article(article_id: int): 54 if article_id not in articles_db: 55 raise HTTPException(status_code=404, detail="Article not found") 56 return articles_db[article_id] 57 58 @app.delete("/articles/{article_id}", status_code=204) 59 def delete_article(article_id: int): 60 if article_id not in articles_db: 61 raise HTTPException(status_code=404, detail="Article not found") 62 del articles_db[article_id]
async_routes.py wrap Python
Copy 1 # Async endpoints and middleware 2 from fastapi import Request 3 from fastapi.middleware.cors import CORSMiddleware 4 import time 5 6 # Middleware 7 @app.middleware("http") 8 async def timing_middleware(request: Request, call_next): 9 start = time.perf_counter() 10 response = await call_next(request) 11 elapsed = time.perf_counter() - start 12 response.headers["X-Process-Time"] = f"{elapsed:.4f}" 13 return response 14 15 # CORS 16 app.add_middleware( 17 CORSMiddleware, 18 allow_origins=["http://localhost:3000"], 19 allow_methods=["*"], 20 allow_headers=["*"], 21 ) 22 23 # Async database operations 24 import httpx 25 26 @app.get("/proxy/{url:path}") 27 async def proxy_url(url: str): 28 async with httpx.AsyncClient() as client: 29 response = await client.get(f"https://{url}") 30 return response.json()
Routing Comparison
routing_compare.py wrap Python
Copy 1 # Django — URL patterns in urls.py 2 from django.urls import path 3 urlpatterns = [ 4 path("users/<int:pk>/", views.user_detail), 5 path("posts/<slug:slug>/", views.post_detail), 6 ] 7 8 # Flask — decorators on functions 9 @app.route("/users/<int:pk>") 10 def user_detail(pk): 11 ... 12 13 @app.route("/posts/<slug:slug>") 14 def post_detail(slug): 15 ... 16 17 # FastAPI — type hints in function signature 18 @app.get("/users/{user_id}") 19 def user_detail(user_id: int): 20 ... 21 22 @app.get("/posts/{slug}") 23 def post_detail(slug: str): 24 ... 25 26 # All three convert path parameters to Python types automatically. 27 # Django uses path converters, Flask uses angle-bracket syntax, 28 # FastAPI uses Python type annotations.
Templates & ORMs
Copy 1 # Django template language (DTL) 2 # {% for article in articles %} 3 # <h2>{{ article.title }}</h2> 4 # <p>{{ article.body|truncatewords:30 }}</p> 5 # {% endfor %} 6 7 # Jinja2 — used by Flask (and optionally Django) 8 # {% for article in articles %} 9 # <h2>{{ article.title }}</h2> 10 # <p>{{ article.body | truncatewords(30) }}</p> 11 # {% endfor %} 12 13 # FastAPI — no built-in template engine 14 # Use Jinja2 directly: 15 from fastapi.templating import Jinja2Templates 16 17 templates = Jinja2Templates(directory="templates") 18 19 @app.get("/") 20 def index(request: Request): 21 return templates.TemplateResponse("index.html", { 22 "request": request, 23 "articles": articles, 24 }) 25 26 # ORM comparison: 27 # Django ORM — mature, feature-rich, complex queries 28 # SQLAlchemy — flexible, supports raw SQL, used with Flask 29 # SQLModel — Pydantic + SQLAlchemy, designed for FastAPI 30 # Tortoise ORM — async-native, Django-like API
ℹ info
For FastAPI projects, SQLModel combines Pydantic models and SQLAlchemy into one, giving you automatic validation and database mapping with zero duplication.
Middleware
Copy 1 # Django middleware — class-based 2 class TimingMiddleware: 3 def __init__(self, get_response): 4 self.get_response = get_response 5 6 def __call__(self, request): 7 start = time.time() 8 response = self.get_response(request) 9 elapsed = time.time() - start 10 response["X-Process-Time"] = f"{elapsed:.4f}" 11 return response 12 13 # Register in settings.py 14 MIDDLEWARE = [ 15 "myapp.middleware.TimingMiddleware", 16 "django.middleware.security.SecurityMiddleware", 17 "django.contrib.sessions.middleware.SessionMiddleware", 18 ] 19 20 # Flask middleware — before_request / after_request 21 @app.before_request 22 def start_timer(): 23 import time 24 request.start_time = time.perf_counter() 25 26 @app.after_request 27 def add_timing(response): 28 elapsed = time.perf_counter() - request.start_time 29 response.headers["X-Process-Time"] = f"{elapsed:.4f}" 30 return response 31 32 # FastAPI middleware — ASGI-based 33 @app.middleware("http") 34 async def timing(request: Request, call_next): 35 start = time.perf_counter() 36 response = await call_next(request) 37 response.headers["X-Process-Time"] = f"{time.perf_counter() - start:.4f}" 38 return response
When to Use Each
Choose Django when you need a full-stack solution with admin, auth, and ORM out of the box. Ideal for content-heavy sites, e-commerce, and teams that want conventions over configuration.
Choose Flask when you want full control over your stack. Great for microservices, APIs with custom requirements, and projects where you pick your own tools. The extension ecosystem covers every need.
Choose FastAPI when you need high-performance APIs with automatic documentation, type-safe validation, and async support. Ideal for ML serving, real-time APIs, and microservices.
Copy 1 # Quick decision guide: 2 # 3 # Feature | Django | Flask | FastAPI 4 # -----------------+------------+------------+------------ 5 # Admin panel | Built-in | Extension | Manual 6 # ORM | Built-in | SQLAlchemy | SQLModel 7 # Auth | Built-in | Extension | Manual 8 # Async | 3.1+ | 2.0+ | Native 9 # Type validation | Manual | Manual | Automatic 10 # Auto docs | No | Swagger ext| Built-in 11 # Performance | Good | Good | Excellent 12 # Learning curve | Moderate | Low | Low-Med 13 # Best for | Full-stack | Micro/API | API/ML
$ Blueprint — Engineering Documentation · Section ID: PYTHON-WEB · Revision: 1.0