Django and FastAPI: What Comes After Flask?
Three frameworks, and how to pick one
In this lesson
You built with Flask because it is small and gets out of the way. It is not the only option, and for some projects it is not the best one. This lesson is a map, not a tutorial: what Django and FastAPI each bring, what an ORM and an admin site actually save you, and how to choose without agonising.
Explain it like I’m 5
Frameworks are toolboxes. Flask is a small one you fill yourself, Django is a fitted workshop, and FastAPI is a light bench built for serving data quickly.
Why more than one framework exists
Every framework trades how much it decides for you against how much you can decide yourself. Nothing here is better or worse in the abstract; they sit at different points on that trade-off, which is why all three are widely used.
- Flask is minimal. Routes and templates, and you choose everything else. Perfect for small apps and for learning, because nothing is hidden.
- Django is batteries included. Database layer, admin site, authentication, forms, and security defaults arrive together. More to learn up front, far less to assemble.
- FastAPI is API-focused. Built for JSON services, uses type hints to validate requests automatically, generates interactive documentation, and supports
asyncnaturally.
Two ideas worth knowing: the ORM and the admin
Django’s two biggest labor-savers are worth understanding even before you use them.
An ORM (object-relational mapper) lets you work with database rows as Python objects instead of writing SQL. You define a class; the framework creates the table and turns method calls into queries. Unit 8 covers what it is doing underneath and when writing SQL yourself is the better call.
The admin interface is a complete web UI for viewing and editing your data, generated from those same class definitions. For anything with content someone has to maintain, it removes an entire build.
# Django: describe the data as a class...
class Book(models.Model):
title = models.CharField(max_length=200)
year = models.IntegerField()
# ...then query it as Python, no SQL written by you.
recent = Book.objects.filter(year__gte=2000).order_by("title")
Choosing, without agonising
A rough guide that will serve you well:
- Small tool, few pages, you want to understand every moving part → Flask.
- Real database, users and logins, someone needs to edit content → Django.
- A JSON API with no pages, especially a fast or async one → FastAPI.
And the most useful thing to know: the concepts transfer. Routes, requests, responses, templates, and status codes mean the same in all three. Learning a second framework is mostly learning where it keeps things.
Encode the guidance as a function. Return "Django" when the project needs an admin and a database, "FastAPI" when it is API-only, and "Flask" otherwise.
def choose(project):
return "Flask" # TODO: handle the Django and FastAPI cases first
projects = [
{"name": "Blog with admin", "needs_admin": True, "has_database": True, "api_only": False},
{"name": "Weather JSON service", "needs_admin": False, "has_database": False, "api_only": True},
{"name": "Tiny personal tool", "needs_admin": False, "has_database": False, "api_only": False},
]
for project in projects:
print(project["name"], "->", choose(project))
Check the most specific case first. Django needs both needs_admin and has_database, so test that with and before the others.
def choose(project):
if project["needs_admin"] and project["has_database"]:
return "Django"
if project["api_only"]:
return "FastAPI"
return "Flask"
projects = [
{"name": "Blog with admin", "needs_admin": True, "has_database": True, "api_only": False},
{"name": "Weather JSON service", "needs_admin": False, "has_database": False, "api_only": True},
{"name": "Tiny personal tool", "needs_admin": False, "has_database": False, "api_only": False},
]
for project in projects:
print(project["name"], "->", choose(project))
Blog with admin -> Django
Weather JSON service -> FastAPI
Tiny personal tool -> Flask
Common mistake: Choosing the biggest framework for the smallest job
More features sounds like more capability, so Django looks like the safe default.
Match the tool to the job. A two-route script does not need migrations, an admin site, and a settings module. Flask will be finished before Django is configured.
Common mistake: Assuming an ORM means you never need SQL
It hides queries so completely that they stop feeling real.
Learn what it generates. When something is slow or a query gets complicated, you need to see the SQL underneath, which is exactly what Unit 8 covers.
Common mistake: Rewriting a working app to switch framework
A new framework looks like it would have made things tidier.
Switch when you hit a real limitation, not out of curiosity. A working Flask app is worth more than a half-finished Django rewrite.
What does Django give you that Flask does not, out of the box?
Both handle routes and templates. Django adds the database layer, admin site, and auth by default.
Which framework is aimed squarely at building JSON APIs?
FastAPI is built for APIs: type-hint validation, generated docs, and native async support.
What does an ORM do?
An object-relational mapper maps classes to tables and turns Python calls into queries.
Mini exercise (medium)
Write recommend(project) taking a dict with api_only, needs_admin, has_database, and page_count. Return a (framework, reason) tuple: FastAPI for API-only projects, Django when it needs an admin or has a database with more than five pages, and Flask otherwise, each with a one-line reason.
Practice here. Fill in the missing piece and click Run to try your answer in place.
def recommend(project):
return ("Flask", "small and simple") # TODO: handle FastAPI and Django first
print(recommend({"api_only": True, "needs_admin": False, "has_database": False, "page_count": 0}))
print(recommend({"api_only": False, "needs_admin": True, "has_database": True, "page_count": 12}))
print(recommend({"api_only": False, "needs_admin": False, "has_database": False, "page_count": 2}))
Order the checks from most specific to least. Return both values as a tuple so the reason travels with the choice.
def recommend(project):
if project["api_only"]:
return ("FastAPI", "an API with no pages to render")
if project["needs_admin"] or (project["has_database"] and project["page_count"] > 5):
return ("Django", "content to manage and data to store")
return ("Flask", "small enough to assemble yourself")
print(recommend({"api_only": True, "needs_admin": False, "has_database": False, "page_count": 0}))
print(recommend({"api_only": False, "needs_admin": True, "has_database": True, "page_count": 12}))
print(recommend({"api_only": False, "needs_admin": False, "has_database": False, "page_count": 2}))
('FastAPI', 'an API with no pages to render')
('Django', 'content to manage and data to store')
('Flask', 'small enough to assemble yourself')
assert recommend({"api_only": False, "needs_admin": False, "has_database": True, "page_count": 20})[0] == "Django", "a big database-backed site is Django"
assert recommend({"api_only": True, "needs_admin": True, "has_database": True, "page_count": 9})[0] == "FastAPI", "api_only wins, check it first"
print("✓ Looks good!")