|$ curl https://forge-ai.dev/api/markdown?path=docs/python/web-frameworks
$cat docs/python-—-web-frameworks.md
updated Last week·25 min read·published

Python — Web Frameworks

PythonIntermediate🎯Free Tools
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.

settings.py
Python
1# settings.py — Django project configuration
2INSTALLED_APPS = [
3 "django.contrib.admin",
4 "django.contrib.auth",
5 "django.contrib.contenttypes",
6 "django.contrib.sessions",
7 "myapp",
8]
9
10DATABASES = {
11 "default": {
12 "ENGINE": "django.db.backends.postgresql",
13 "NAME": "mydb",
14 "USER": "postgres",
15 "HOST": "localhost",
16 }
17}
models.py
Python
1# models.py — Django ORM
2from django.db import models
3
4class 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
19articles = Article.objects.filter(published=True)
20recent = Article.objects.filter(author=user)[:5]
21count = Article.objects.count()
views.py
Python
1# views.py — Django class-based and function views
2from django.shortcuts import render, get_object_or_404
3from django.http import JsonResponse
4from django.views.decorators.http import require_http_methods
5from .models import Article
6
7# Function-based view
8def 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
13from django.views.generic import ListView
14
15class 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"])
25def 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 })
urls.py
Python
1# urls.py — Django URL routing
2from django.urls import path
3from . import views
4
5urlpatterns = [
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.

app.py
Python
1# app.py — Flask application
2from flask import Flask, request, jsonify, abort, render_template
3from flask_sqlalchemy import SQLAlchemy
4
5app = Flask(__name__)
6app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db"
7db = SQLAlchemy(app)
8
9# Define model
10class 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("/")
19def index():
20 articles = Article.query.filter_by(published=True).all()
21 return render_template("index.html", articles=articles)
22
23@app.route("/article/<slug>")
24def 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"])
30def 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"])
39def 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
50if __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.
blueprints.py
Python
1# Blueprints — modular Flask applications
2from flask import Blueprint
3
4api = Blueprint("api", __name__, url_prefix="/api")
5
6@api.route("/articles")
7def list_articles():
8 return jsonify([])
9
10@api.route("/articles/<int:id>")
11def get_article(id):
12 return jsonify({"id": id})
13
14# Register in main app
15app.register_blueprint(api)
16
17# Error handlers
18@app.errorhandler(404)
19def not_found(e):
20 return jsonify({"error": "Not found"}), 404
21
22@app.errorhandler(500)
23def 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.

main.py
Python
1# main.py — FastAPI application
2from fastapi import FastAPI, HTTPException, Query
3from pydantic import BaseModel, Field
4from typing import Optional
5from datetime import datetime
6
7app = FastAPI(title="Article API", version="1.0.0")
8
9# Pydantic models for validation
10class 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
16class 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
25articles_db: dict[int, dict] = {}
26next_id = 0
27
28# Dependency injection
29def get_db():
30 return articles_db
31
32# Routes with type hints and auto-validation
33@app.get("/articles", response_model=list[ArticleResponse])
34def 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)
45def 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)
53def 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)
59def 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
Python
1# Async endpoints and middleware
2from fastapi import Request
3from fastapi.middleware.cors import CORSMiddleware
4import time
5
6# Middleware
7@app.middleware("http")
8async 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
16app.add_middleware(
17 CORSMiddleware,
18 allow_origins=["http://localhost:3000"],
19 allow_methods=["*"],
20 allow_headers=["*"],
21)
22
23# Async database operations
24import httpx
25
26@app.get("/proxy/{url:path}")
27async 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
Python
1# Django — URL patterns in urls.py
2from django.urls import path
3urlpatterns = [
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>")
10def user_detail(pk):
11 ...
12
13@app.route("/posts/<slug:slug>")
14def post_detail(slug):
15 ...
16
17# FastAPI — type hints in function signature
18@app.get("/users/{user_id}")
19def user_detail(user_id: int):
20 ...
21
22@app.get("/posts/{slug}")
23def 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
templates.py
Python
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:
15from fastapi.templating import Jinja2Templates
16
17templates = Jinja2Templates(directory="templates")
18
19@app.get("/")
20def 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
middleware.py
Python
1# Django middleware — class-based
2class 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
14MIDDLEWARE = [
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
22def start_timer():
23 import time
24 request.start_time = time.perf_counter()
25
26@app.after_request
27def 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")
34async 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.

comparison.py
Python
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