Web Frameworks Compared
When a Spring Boot engineer evaluates a Python web framework for the first time, the experience is disorienting. Spring is monolithic by default — you get an embedded Tomcat, a DI container, JPA, Spring Security, and a plugin ecosystem without making any choices. Python's web frameworks sit on a spectrum from microframework (Flask) to full-stack batteries-included (Django) to async-first API-focused (FastAPI). None of them is Spring, but together they cover the same problem space.
Framework Positioning
Pick Flask when you want control. Pick Django when you want decisions made for you. Pick FastAPI when you are building a JSON API and care about performance and automatic documentation.
Flask — Minimal, Explicit
Flask is a WSGI framework (synchronous by default) with no built-in ORM, no authentication, no form handling. It is a router with a request context.
from flask import Flask, jsonify, request, abort
app = Flask(__name__)
users: dict[int, dict] = {
1: {"id": 1, "name": "Alice"},
}
@app.route("/users/<int:user_id>", methods=["GET"])
def get_user(user_id: int):
user = users.get(user_id)
if not user:
abort(404)
return jsonify(user)
@app.route("/users", methods=["POST"])
def create_user():
data = request.get_json()
new_id = max(users) + 1
users[new_id] = {"id": new_id, **data}
return jsonify(users[new_id]), 201Java equivalent using Javalin:
app.get("/users/{id}", ctx -> {
var id = ctx.pathParamAsClass("id", Integer.class).get();
var user = users.get(id);
if (user == null) throw new NotFoundResponse();
ctx.json(user);
});Flask has no DI container. Shared dependencies (database connections, config) are typically globals or thread-local proxies (flask.g, current_app). For larger apps this becomes unwieldy — which is why teams either migrate to Django or adopt a DI library like injector.
Django — The Full Stack
Django is opinionated: project layout, ORM, migrations, admin interface, authentication, middleware — all built in and integrated. The closest Spring analogue is Spring Boot with Spring Data JPA and Spring Security pre-configured.
# models.py
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created_at"]
# views.py (Django REST Framework)
from rest_framework import viewsets, serializers
from rest_framework.permissions import IsAuthenticated
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "name", "email", "created_at"]
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [IsAuthenticated]Django's ORM is closer to Spring Data JPA than raw JDBC — it abstracts SQL into a queryable Python API:
# Equivalent of: SELECT * FROM users WHERE name LIKE 'Al%' ORDER BY created_at
users = User.objects.filter(name__startswith="Al").order_by("created_at")
# Equivalent of: SELECT u.*, p.* FROM users u JOIN profiles p ON ...
users = User.objects.select_related("profile").prefetch_related("orders")Django migrations (python manage.py makemigrations / migrate) are the equivalent of Liquibase or Flyway changesets, but generated automatically from model diffs.
FastAPI — Async, Typed, Documented
FastAPI is an ASGI framework (async) built on Starlette and Pydantic. It generates OpenAPI documentation automatically from type hints and validates requests/responses using Pydantic models.
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel, EmailStr
app = FastAPI(title="User API", version="1.0.0")
class UserCreate(BaseModel):
name: str
email: EmailStr
class UserResponse(BaseModel):
id: int
name: str
email: str
users: dict[int, UserResponse] = {}
@app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int) -> UserResponse:
if user_id not in users:
raise HTTPException(status_code=404, detail="User not found")
return users[user_id]
@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(payload: UserCreate) -> UserResponse:
new_id = len(users) + 1
user = UserResponse(id=new_id, **payload.model_dump())
users[new_id] = user
return userFastAPI's dependency injection uses Depends() — similar in concept to Spring's @Autowired but explicit and composable:
from fastapi import Depends
from typing import Annotated
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
DbDep = Annotated[Session, Depends(get_db)]
@app.get("/users/{user_id}")
async def get_user(user_id: int, db: DbDep) -> UserResponse:
...Deployment Model
Flask and Django deploy behind a WSGI server (Gunicorn, uWSGI) — multiple processes handle concurrent requests. FastAPI deploys behind an ASGI server (Uvicorn, Hypercorn) — a single process handles thousands of concurrent connections via the asyncio event loop, similar to Netty's threading model.
Comparison Summary
| Concern | Flask | Django | FastAPI | Spring Boot |
|---|---|---|---|---|
| Routing | @app.route |
urls.py |
@app.get/post |
@GetMapping |
| DI | Manual / globals | Manual | Depends() |
@Autowired |
| ORM | SQLAlchemy (add) | Built-in | SQLAlchemy / SQLModel | Spring Data JPA |
| Validation | Manual | Forms + DRF serializers | Pydantic (built-in) | Bean Validation |
| API docs | Flask-Smorest | drf-spectacular | Built-in (Swagger UI) | springdoc-openapi |
| Auth | Flask-Login/JWT | django-allauth | FastAPI-Users | Spring Security |
| Migrations | Alembic | Built-in | Alembic | Liquibase/Flyway |
Key Takeaways
- Flask is Javalin/Spark Java — a thin router; you compose the rest from libraries.
- Django is Spring Boot with JPA and Security included — pick it when you want decisions made for you.
- FastAPI is Spring WebFlux + Springdoc + Bean Validation in one package — ideal for typed, documented async APIs.
- All three deploy behind a process manager (Gunicorn for WSGI, Uvicorn for ASGI) rather than an embedded servlet container.
- FastAPI's
Depends()DI is explicit and function-scoped; it lacks Spring's full application-context lifecycle but covers 90% of real use cases cleanly. - For new microservices where you control the design, FastAPI is the default choice in 2025 — the type safety, auto-docs, and async model give the most value per line of code.