Python โ Web Frameworks
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 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.
| 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 | } |
| 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() |
| 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 | }) |
| 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 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.
| 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
| 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 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.
| 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] |
| 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() |
| 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. |
| 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
| 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 |
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.
| 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 |
Community
Get help on Slack, Discord or VIP
Stuck on a guide? Join the community and ask.