Introduction: Understanding the Landscape
Modern Python web development presents developers with a fascinating dilemma: choose a framework that aligns perfectly with your project's unique requirements. Flet, Django, and FastAPI represent three fundamentally different philosophies in the Python ecosystem. Flet brings Flutter's UI capabilities to Python for desktop and web apps. Django offers a batteries-included, full-stack web framework with decades of maturity. FastAPI delivers high-performance APIs with automatic OpenAPI documentation. This tutorial dissects each framework, provides hands-on code examples, and guides you toward making an informed architectural decision.
What is Flet?
đ Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Flet is a relatively new Python framework that enables developers to build interactive, multi-platform applicationsâincluding web, desktop, and mobileâusing a Flutter-inspired widget model. You write Python code, and Flet compiles it into Flutter widgets rendered in the browser or as a native desktop app. There's no need to write a single line of JavaScript, HTML, or CSS. Flet excels at data-heavy internal tools, dashboards, and real-time collaborative applications where the UI is generated server-side and streamed to the client over WebSockets.
Core Concepts
- Controls (Widgets): Everything is a controlâText, Button, TextField, DataTable, Chart, etc. Controls are composed declaratively into a tree.
- Page: The root container. You add controls to the page and update them dynamically.
- Event Handlers: Functions that respond to user interactions like button clicks or text changes.
- WebSocket Communication: The Python backend communicates UI changes to the frontend via a persistent WebSocket connection, enabling real-time updates without REST endpoints.
Flet Code Example: Interactive Counter App
import flet as ft
def main(page: ft.Page):
page.title = "Flet Counter Demo"
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
# Counter state
counter = ft.Text("0", size=50, weight=ft.FontWeight.BOLD)
def increment_click(e):
current = int(counter.value)
counter.value = str(current + 1)
page.update()
def decrement_click(e):
current = int(counter.value)
counter.value = str(current - 1)
page.update()
def reset_click(e):
counter.value = "0"
page.update()
# UI Layout
page.add(
ft.Row(
[
ft.IconButton(ft.icons.REMOVE, on_click=decrement_click),
counter,
ft.IconButton(ft.icons.ADD, on_click=increment_click),
],
alignment=ft.MainAxisAlignment.CENTER,
),
ft.ElevatedButton("Reset", on_click=reset_click),
)
ft.app(target=main)
This complete example demonstrates Flet's core workflow: define controls, attach event handlers, mutate state, and call page.update() to push changes to the UI. The same code runs as a web app, desktop window, or PWA with zero modifications.
What is Django?
Django is a high-level, full-stack Python web framework that follows the "batteries-included" philosophy. It provides an ORM, authentication system, admin interface, form handling, template engine, and moreâall out of the box. Django powers some of the largest websites on the internet, including Instagram, Pinterest, and Mozilla. It's built around the MVT (Model-View-Template) architectural pattern and emphasizes rapid development, clean design, and security by default.
Core Concepts
- Models: Python classes that define your database schema. Django's ORM translates these into SQL tables automatically.
- Views: Functions or class-based handlers that process HTTP requests and return responses.
- Templates: Django Template Language (DTL) files that render HTML with dynamic data, template inheritance, and filters.
- URL Dispatcher: A URLconf that maps URL patterns to views using a clean, regex-capable routing system.
- Admin Interface: An automatically generated, production-ready administrative dashboard for managing site content.
Django Code Example: Complete Task Manager API
# models.py
from django.db import models
class Task(models.Model):
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
completed = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
# admin.py
from django.contrib import admin
from .models import Task
@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
list_display = ['title', 'completed', 'created_at']
list_filter = ['completed']
# views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_http_methods
from .models import Task
@require_http_methods(["GET", "POST"])
def task_list(request):
if request.method == "POST":
title = request.POST.get("title")
if title:
Task.objects.create(title=title)
return redirect('task_list')
tasks = Task.objects.all().order_by('-created_at')
return render(request, 'tasks/task_list.html', {'tasks': tasks})
@require_http_methods(["POST"])
def toggle_task(request, pk):
task = get_object_or_404(Task, pk=pk)
task.completed = not task.completed
task.save()
return redirect('task_list')
@require_http_methods(["POST"])
def delete_task(request, pk):
task = get_object_or_404(Task, pk=pk)
task.delete()
return redirect('task_list')
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.task_list, name='task_list'),
path('toggle//', views.toggle_task, name='toggle_task'),
path('delete//', views.delete_task, name='delete_task'),
]
And the accompanying template file:
<!-- templates/tasks/task_list.html -->
<!DOCTYPE html>
<html>
<head>
<title>Task Manager</title>
<style>
.completed { text-decoration: line-through; color: #888; }
</style>
</head>
<body>
<h1>Task Manager</h1>
<form method="post">
{% csrf_token %}
<input type="text" name="title" placeholder="New task...">
<button type="submit">Add</button>
</form>
<ul>
{% for task in tasks %}
<li class="{% if task.completed %}completed{% endif %}">
{{ task.title }}
<form method="post" action="{% url 'toggle_task' task.pk %}" style="display:inline">
{% csrf_token %}
<button>Toggle</button>
</form>
<form method="post" action="{% url 'delete_task' task.pk %}" style="display:inline">
{% csrf_token %}
<button>Delete</button>
</form>
</li>
{% empty %}
<li>No tasks yet!</li>
{% endfor %}
</ul>
</body>
</html>
This example showcases Django's integrated stack: models define the schema, views handle business logic, the admin provides instant CRUD management, and templates render HTML with CSRF protection built-in. The entire application works with just a few files and zero external dependencies beyond Django itself.
What is FastAPI?
FastAPI is a modern, high-performance web framework for building REST APIs and microservices with Python 3.7+. It leverages Python's type hints to provide automatic request validation, serialization, and interactive OpenAPI documentation. Built on Starlette (for web handling) and Pydantic (for data validation), FastAPI achieves near-Node.js performance levels while maintaining Python's developer-friendly syntax. It's ideal for backend APIs consumed by React/Vue frontends, mobile apps, or third-party integrations.
Core Concepts
- Path Operations: Decorator-based route definitions (
@app.get,@app.post) that map HTTP methods to Python functions. - Pydantic Models: Type-hinted data classes used for request bodies, query parameters, and response schemasâautomatically validated and documented.
- Dependency Injection: A powerful system for injecting reusable logic (database sessions, authentication checks, rate limiters) into path operations.
- Async Support: Native
async/awaitcompatibility for high-concurrency I/O operations without thread pools. - Auto-Generated Docs: Swagger UI and ReDoc endpoints generated automatically at
/docsand/redoc.
FastAPI Code Example: Full REST API with Database
# main.py
from fastapi import FastAPI, HTTPException, Depends, status
from sqlalchemy import create_engine, Column, Integer, String, Boolean, DateTime
from sqlalchemy.orm import sessionmaker, declarative_base, Session
from pydantic import BaseModel, Field
from datetime import datetime
from typing import Optional, List
# Database setup
DATABASE_URL = "sqlite:///./tasks.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# SQLAlchemy Model
class TaskModel(Base):
__tablename__ = "tasks"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(200), nullable=False)
description = Column(String, default="")
completed = Column(Boolean, default=False)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
Base.metadata.create_all(bind=engine)
# Pydantic Schemas
class TaskCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=200)
description: Optional[str] = Field("", max_length=1000)
completed: bool = False
class TaskResponse(BaseModel):
id: int
title: str
description: str
completed: bool
created_at: datetime
updated_at: datetime
class Config:
orm_mode = True # Enable ORM-to-Pydantic conversion
class TaskUpdate(BaseModel):
title: Optional[str] = Field(None, min_length=1, max_length=200)
description: Optional[str] = None
completed: Optional[bool] = None
# FastAPI App
app = FastAPI(title="Task Manager API", version="1.0.0")
# Dependency: get a database session per request
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/tasks", response_model=TaskResponse, status_code=status.HTTP_201_CREATED)
async def create_task(task: TaskCreate, db: Session = Depends(get_db)):
"""Create a new task with title and optional description."""
db_task = TaskModel(**task.dict())
db.add(db_task)
db.commit()
db.refresh(db_task)
return db_task
@app.get("/tasks", response_model=List[TaskResponse])
async def list_tasks(
completed: Optional[bool] = None,
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db)
):
"""Retrieve tasks with optional filtering and pagination."""
query = db.query(TaskModel)
if completed is not None:
query = query.filter(TaskModel.completed == completed)
tasks = query.order_by(TaskModel.created_at.desc()).offset(skip).limit(limit).all()
return tasks
@app.get("/tasks/{task_id}", response_model=TaskResponse)
async def get_task(task_id: int, db: Session = Depends(get_db)):
"""Fetch a single task by its ID."""
task = db.query(TaskModel).filter(TaskModel.id == task_id).first()
if task is None:
raise HTTPException(status_code=404, detail="Task not found")
return task
@app.patch("/tasks/{task_id}", response_model=TaskResponse)
async def update_task(task_id: int, updates: TaskUpdate, db: Session = Depends(get_db)):
"""Partially update a task's fields."""
task = db.query(TaskModel).filter(TaskModel.id == task_id).first()
if task is None:
raise HTTPException(status_code=404, detail="Task not found")
update_data = updates.dict(exclude_unset=True)
for key, value in update_data.items():
setattr(task, key, value)
task.updated_at = datetime.utcnow()
db.commit()
db.refresh(task)
return task
@app.delete("/tasks/{task_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_task(task_id: int, db: Session = Depends(get_db)):
"""Permanently remove a task."""
task = db.query(TaskModel).filter(TaskModel.id == task_id).first()
if task is None:
raise HTTPException(status_code=404, detail="Task not found")
db.delete(task)
db.commit()
return None
This comprehensive FastAPI example demonstrates automatic request validation via Pydantic, dependency injection for database sessions, async path operations, and full CRUD functionality. Visiting /docs after running this code reveals an interactive Swagger UI where every endpoint can be tested directly in the browserâno Postman required.
Why the Comparison Matters
Choosing the wrong framework for your project can multiply development time, cripple performance, or force painful architectural rewrites mid-project. Each framework in this comparison targets a fundamentally different use case:
- Flet replaces traditional web frontend/backend separation with a unified Python codebase for UI-heavy applications. It's ideal when you need a desktop-quality interface delivered through the browser without hiring a Flutter developer.
- Django provides a complete web application ecosystem. Choose Django when you need server-rendered HTML pages, an admin dashboard, authentication, sessions, and a mature ORMâall from a single package with consistent conventions.
- FastAPI excels at high-performance, async-first REST and GraphQL APIs consumed by separate frontends, mobile apps, or microservice architectures. It's the go-to choice when API speed, auto-documentation, and type-safety matter more than server-rendered HTML.
Understanding these distinctions prevents the common mistake of forcing a framework into a role it wasn't designed forâlike using Django for a real-time WebSocket dashboard (possible but cumbersome) or FastAPI for a content-heavy CMS with admin panels (possible but requires extensive custom tooling).
Detailed Comparison: Side by Side
Architecture & Paradigm
- Flet: Declarative UI framework. You build a control tree, and Flet handles rendering, layout, and state synchronization via WebSockets. The mental model is closer to Flutter or React than to traditional request-response web frameworks.
- Django: MVT (Model-View-Template) with an explicit request-response cycle. Views receive HTTP requests and return HTTP responses. Templates render HTML on the server. The ORM abstracts database operations into Python method calls.
- FastAPI: API-first architecture focused on path operations and dependency injection. Each endpoint is a Python function decorated with HTTP method and path. Pydantic models enforce data contracts at the boundary between client and server.
Learning Curve
- Flet: Moderate. Requires understanding Flutter's widget model and reactive state management. Python developers familiar with imperative UI programming may need to adjust to the declarative, update-based rendering model.
- Django: Moderate to steep. The framework is vastâmodels, migrations, class-based views, template tags, middleware, signals, forms, and the admin. However, Django's excellent documentation and consistent conventions reduce the practical learning burden significantly.
- FastAPI: Gentle to moderate. The core API surface is small and intuitive. Type-hint-based validation feels natural to modern Python developers. The steepness comes when integrating databases, authentication, and production deployment patterns, but these are well-documented.
Performance & Concurrency
- Flet: Performance depends on the WebSocket communication layer. For UI-heavy apps with frequent updates, it handles efficiently by sending only changed controls. Not designed for high-throughput REST API scenarios.
- Django: Synchronous by default (WSGI). Handles moderate loads well with proper caching and database optimization. Async view support (ASGI) was added in Django 3.1+ but remains optional and less battle-tested than the sync path.
- FastAPI: Built on Starlette/ASGI with native async support. Achieves excellent throughput for I/O-bound workloads. Benchmarks regularly show FastAPI handling thousands of requests per second on modest hardware, outperforming synchronous Python frameworks by significant margins.
Database & ORM
- Flet: No built-in ORM. Data persistence is entirely up to the developer. You can integrate SQLAlchemy, SQLite, or any Python database library directly, but there's no Flet-specific data layer.
- Django: Ships with a powerful, migration-capable ORM that supports PostgreSQL, MySQL, SQLite, Oracle, and more. The ORM includes querysets, lazy evaluation, prefetching, annotations, and aggregations. Migrations are auto-detected and version-controlled.
- FastAPI: Framework-agnostic regarding databases. SQLAlchemy is the most common choice, but you can use async ORMs like Tortoise ORM, databases like MongoDB via Motor, or raw async database drivers. FastAPI provides no ORM of its own.
Authentication & Security
- Flet: No built-in authentication system. You implement auth logic in Python and control UI visibility based on user state. For web deployments, you'd typically integrate with an external identity provider or session-based auth at the application level.
- Django: Comprehensive, battle-tested authentication system with user models, permissions, groups, session management, password hashing, CSRF protection, and XSS mitigation. The admin interface has granular permission controls. Django's security middleware is among the most robust in any web framework.
- FastAPI: Provides security utilities (OAuth2 password flow, API key handling, HTTPBearer) and excellent documentation patterns. Actual authentication logic is implemented by the developer using libraries like Passlib, PyJWT, or python-jose. FastAPI's dependency injection makes auth clean and reusable across endpoints.
Frontend Integration
- Flet: The frontend IS Flet. You don't integrate with a separate frontend framework because Flet generates the entire UI from Python. This is its primary value propositionâeliminating the frontend/backend divide entirely.
- Django: Serves server-rendered HTML by default. Can integrate with modern frontends (React, Vue, Svelte) either as a REST API backend (using Django REST Framework) or via hybrid approaches like django-webpack-loader, django-vite, or Inertia.js.
- FastAPI: Designed specifically to be consumed by separate frontends. Pairs perfectly with React, Vue, Svelte, mobile apps, or any HTTP client. The auto-generated OpenAPI schema enables automatic client code generation in TypeScript, Swift, Kotlin, and other languages.
When to Use Each Framework
Choose Flet When:
- You need a desktop-quality UI delivered through the browser without writing JavaScript
- Your application is data-heavy with real-time updates (dashboards, monitoring tools, internal admin panels)
- You want a single Python codebase for both logic and UI across web, desktop, and mobile
- Your team lacks frontend developers but needs sophisticated interactive interfaces
- Rapid prototyping of Flutter-like UIs with Python's ecosystem is your priority
Choose Django When:
- You're building a traditional web application with server-rendered HTML pages
- You need an admin interface for content management out of the box
- Your application requires robust authentication, sessions, and permissions
- You value convention over configuration and want a well-documented "Django way" for every task
- The project is a content-driven website, e-commerce platform, or CMS
- Long-term maintainability with a large developer community matters more than raw API throughput
Choose FastAPI When:
- You're building a REST or GraphQL API consumed by a separate frontend, mobile app, or third-party clients
- High concurrency and async I/O performance are critical requirements
- Automatic interactive API documentation (Swagger UI) is a must-have for your team or consumers
- You want to leverage Python type hints for automatic request validation and editor autocompletion
- Your architecture follows a microservices pattern with many small, independent API services
- You need WebSocket support for real-time features alongside traditional REST endpoints
Best Practices
Flet Best Practices
- Minimize page.update() calls: Batch multiple control mutations into a single
page.update()call to reduce network overhead and prevent UI flickering. - Separate UI from business logic: Keep control definitions in one module and business logic in another. This makes testing possible and prevents the UI code from becoming a monolithic tangle.
- Use refs for dynamic controls: When you need to modify controls created inside loops or conditionally, use
Refobjects to maintain references without tightly coupling to the control tree structure. - Handle session state carefully: In web deployments, each user session gets its own page instance. Store per-user state on the page object or in a server-side dictionary keyed by session ID.
Django Best Practices
- Keep views thin, models fat: Push business logic into model methods and managers. Views should orchestrateâfetching data, calling model methods, and rendering templatesânot implement domain rules directly.
- Use class-based views judiciously: CBVs reduce boilerplate for standard CRUD patterns but can become opaque. For complex logic, function-based views with explicit decorators often prove more maintainable.
- Leverage Django's testing framework: Write tests using Django's
TestCaseandClientfor integration-level tests. Test models, views, and templates in isolation where possible. - Optimize queries with select_related and prefetch_related: Profile your views with Django Debug Toolbar and eliminate N+1 query problems before they reach production.
- Structure projects with apps: Break functionality into reusable Django apps with clear responsibilities. A well-structured project has apps for core domain entities, not a single monolithic app containing everything.
FastAPI Best Practices
- Use dependency injection for shared logic: Extract database sessions, authentication checks, permission validations, and rate limiting into dependency functions. This keeps path operation functions focused on business logic.
- Structure with routers: Break your API into modular routers organized by domain concern. Include the router in
app.include_router()with prefixes and tags for automatic documentation grouping. - Version your APIs explicitly: Use URL versioning (
/api/v1/) or header-based versioning from the start. FastAPI's router prefix system makes this straightforward. - Use async database drivers: For true async performance, pair FastAPI with async-compatible database libraries like
asyncpgfor PostgreSQL,databaseslibrary, or Tortoise ORM. Synchronous drivers in async endpoints can bottleneck your entire event loop. - Validate environment configuration with Pydantic: Use Pydantic's
BaseSettingsto load and validate environment variables at startup. This provides type-safe configuration with automatic parsing.
Conclusion
Flet, Django, and FastAPI occupy distinct niches in the Python web ecosystem, and the "best" framework depends entirely on what you're building. Flet revolutionizes internal tool and dashboard development by collapsing the frontend-backend boundary into a single Python codebase with Flutter-quality rendering. Django remains the gold standard for full-stack web applications where server-rendered HTML, content management, authentication, and an admin interface are non-negotiable requirements. FastAPI dominates the API-first landscape with its type-driven development model, exceptional async performance, and automatic documentation that doubles as a live API playground.
The practical code examples in this tutorialâfrom Flet's reactive counter to Django's task manager with admin, to FastAPI's fully documented REST APIâdemonstrate that each framework excels in its intended domain. Your decision should flow from your project's core requirements: UI generation model, rendering strategy, performance profile, and team composition. There is no universal winner, only the right tool for the job at hand. Understanding all three frameworks expands your architectural vocabulary and equips you to choose confidently when the next project begins.