π§ͺ Flask Framework Architecture
Flask is the "micro-framework" that has quietly powered API layers at Pinterest, Netflix, and countless startups. This lesson takes it apart so you can see the two libraries it is built on, how a request travels through it, and why its deliberately minimal core is a feature rather than a limitation.
π― Learning Objectives
By the end of this lesson, you will be able to:
- Explain what a micro-framework is and how Flask's "batteries optional" philosophy differs from Django's "batteries included"
- Identify Flask's two foundational dependencies β Werkzeug (WSGI/HTTP) and Jinja2 (templating) β and the job each does
- Trace an HTTP request through Flask's architecture from WSGI server to view function to response
- Create a minimal Flask 3 application with the application object, a route, and a run guard
- Choose Flask (versus Django, FastAPI, or Pyramid) for an appropriate project
Estimated Time: 30β40 minutes β’ Difficulty: BeginnerβIntermediate
Hands-on: Build and run a two-route Flask app, then add a dynamic parameterized route.
In This Lesson
What Is Flask?
Flask is a lightweight, flexible web framework for Python, first released by Armin Ronacher in 2010. It is often called a micro-framework β not because it is only for tiny apps, but because its core is intentionally small. Flask gives you routing, request/response handling, and templating, then gets out of your way and lets you choose everything else: which database library, which authentication system, which project structure.
Unlike a comprehensive framework such as Django β which ships with an ORM, an admin panel, a forms library, and a user model out of the box β Flask ships with almost none of that. This "batteries optional" approach is what makes it a favourite for APIs, microservices, prototypes, and any project whose shape does not fit an off-the-shelf mould.
π‘ A useful analogy: Django is a fully furnished apartment β move in and everything is already there, arranged the way the builder decided. Flask is a well-built empty loft with excellent plumbing and wiring β you decide where the walls go and what furniture to bring. More freedom, and more responsibility.
Flask remains one of the most popular Python web frameworks today, and the concepts you learn here β routing, the request object, view functions, templating β carry over directly to Django, FastAPI, and even frameworks in other languages.
The Micro-Framework Philosophy
Flask's design is guided by a handful of principles that explain why its code looks the way it does:
- Simplicity: the core stays small so it is easy to understand top to bottom.
- Flexibility: you decide how to structure the app; Flask imposes no directory layout.
- Explicitness: Flask favours explicit code over hidden "magic," so behaviour is easier to reason about.
- Extensibility: functionality you don't need isn't loaded; you add extensions only when a project calls for them.
π Key Term
"Batteries optional": a play on Python's "batteries included" slogan. Flask deliberately leaves out the batteries (ORM, forms, auth) so you can pick the ones that suit your project β and skip the ones you don't need.
Picture Flask as a LEGO baseplate with a small bag of essential bricks. It gives you a solid foundation and the connectors you need, but you decide which additional sets β a database ORM, a login system, a REST toolkit β to snap on. You can start small and grow only as the project demands, never carrying weight you aren't using.
Flask's Core Architecture
Flask itself is fairly thin glue code. Almost all of its heavy lifting is delegated to two mature libraries that ship as dependencies:
- Werkzeug β a WSGI utility library that handles the low-level HTTP layer: parsing requests, building responses, URL routing, and the development server.
- Jinja2 β a fast, secure templating engine that turns template files plus data into finished HTML.
π Key Term
WSGI (Web Server Gateway Interface): the Python standard that defines how a web server talks to a Python application. Because Flask speaks WSGI, it can run behind any WSGI server β Gunicorn, uWSGI, or Werkzeug's built-in dev server β without changing your code.
Here is the path a single request takes as it moves through the stack and back:
Notice that Flask sits in the middle as the coordinator: Werkzeug hands it a matched URL, Flask calls the right view function, the view (optionally) asks Jinja2 to render a template, and the finished response travels back out through the same layers. This clean separation is exactly what keeps Flask lightweight while still being complete.
Key Components
The Application Object
Every Flask app begins with a single central object β an instance of the Flask class. It holds your configuration, registers your routes, and is the WSGI callable the server ultimately runs.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return "Hello, World!"
if __name__ == "__main__":
app.run(debug=True)
The Flask(__name__) constructor passes the current module's name so Flask can locate resources β templates and static files β relative to your code. The if __name__ == "__main__": guard means the development server only starts when you run this file directly, not when a production WSGI server imports it.
β οΈ The dev server is for development only
app.run() starts Werkzeug's built-in server, which is perfect for local work but not built to handle production traffic. In production you run Flask behind a real WSGI server such as Gunicorn (gunicorn "app:app"). Never expose debug=True to the public internet β its interactive debugger can execute arbitrary code.
The Routing System
Flask maps URLs to Python functions using the @app.route() decorator. Dynamic segments in angle brackets are passed as arguments, and a converter such as <int:...> validates and casts them:
@app.route("/users/<int:user_id>")
def show_user(user_id):
return f"Showing profile for user {user_id}"
Visiting /users/42 calls show_user(42) with user_id already an integer. Visiting /users/abc returns a 404 automatically, because abc is not a valid integer. (The next lesson covers routing in depth.)
The Request and Response Objects
Flask hands your view a request object holding everything the client sent β form fields, JSON body, query string, headers β and gives you helpers like jsonify to build responses:
from flask import request, jsonify
@app.route("/api/echo", methods=["POST"])
def echo():
data = request.get_json() # parse the JSON body
return jsonify(status="success", received=data)
π‘ The application context
How can request be imported globally yet always refer to this request? Flask uses thread-local "context" objects: during each request it binds the correct request and g (a per-request scratchpad) so your view sees only its own data, even under concurrent traffic. You will meet the app context and request context again when configuring databases and testing.
The Extension Ecosystem
Because the core is minimal, Flask relies on a rich ecosystem of extensions to add capabilities without bloating every project. You install only the ones a given app needs:
| Extension | Adds | Replaces (in Django terms) |
|---|---|---|
| Flask-SQLAlchemy | Database ORM integration | Django ORM |
| Flask-Migrate | Database schema migrations (via Alembic) | Django migrations |
| Flask-WTF | Forms, validation, CSRF protection | Django Forms |
| Flask-Login | User session & authentication management | Django auth |
| Flask-RESTful / Flask-Smorest | Structured REST API building | Django REST Framework |
Installing an extension is just pip install plus a couple of lines to wire it to your app object. For example, adding a database:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db"
db = SQLAlchemy(app) # the extension attaches itself to the app
β Why this matters
A JSON-only microservice can stay tiny β just Flask. A full web app can pull in SQLAlchemy, Login, and WTF. Same framework, different weight. You never pay for features you don't use, which keeps deployments lean and startup fast.
Flask vs Other Frameworks
Flask is one of several strong Python web frameworks. Knowing where each shines helps you pick the right tool:
| Framework | Type | Best for | Learning curve |
|---|---|---|---|
| Flask | Micro-framework | APIs, microservices, smallβmedium apps, learning | Low |
| Django | Batteries-included | Large apps, content sites, admin-heavy projects | MediumβHigh |
| FastAPI | Async, API-first | High-performance typed APIs, async workloads | LowβMedium |
| Pyramid | Flexible / scalable | Projects that start small but must grow | Medium |
π‘ Flask in production
Companies including Pinterest, Netflix, LinkedIn, and Twilio have run Flask for API services and internal tools. Its lightness is an advantage at scale when each service does one focused job β the microservice pattern Flask fits so well.
Hands-on Exercise
ποΈ Build Your First Flask App
Objective: Stand up a minimal Flask application with a home page, an about page, and one dynamic route.
Instructions:
- Create and activate a virtual environment, then install Flask:
python -m venv venv source venv/bin/activate # Windows: venv\Scripts\activate pip install Flask - Create
app.pywith an app object and a/route returning a greeting. - Add an
/aboutroute. - Add a dynamic route
/greet/<name>that greets the visitor by name. - Run it with
flask --app app run --debugand visit each URL in your browser.
π‘ Hint
Each route is a function decorated with @app.route("..."). A dynamic segment goes in angle brackets, e.g. <name>, and becomes an argument of the function. Return a plain string and Flask wraps it in a proper HTTP response for you.
β Solution
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "<h1>Welcome to my first Flask app!</h1>"
@app.route("/about")
def about():
return "<h1>About</h1><p>A tiny app to learn Flask's architecture.</p>"
@app.route("/greet/<name>")
def greet(name):
return f"<h1>Hello, {name}!</h1>"
if __name__ == "__main__":
app.run(debug=True)
Run with python app.py (or flask --app app run --debug), then visit /, /about, and /greet/Ray. Notice the dev server auto-reloads when you save a change β that is Werkzeug's reloader at work.
π― Quick Quiz
Question 1: Which two libraries form the foundation of Flask?
Question 2: What does calling Flask a "micro-framework" mean?
Question 3: Why should you avoid running the development server with debug=True in production?
Best Practices
β Do
- Use a virtual environment per project and pin dependencies in
requirements.txt. - Grow into an application factory (a
create_app()function) and Blueprints as the app gets larger. - Keep configuration out of code β load it from environment variables or a config object.
- Deploy behind a production WSGI server (Gunicorn/uWSGI) with a reverse proxy such as Nginx.
β οΈ Don't
- Don't run
debug=Trueor the built-in dev server in production. - Don't hard-code secrets (API keys,
SECRET_KEY) in your source files. - Don't cram an entire large app into one
app.pyβ split it once routes multiply.
Summary & Quiz
π Key Takeaways
- Flask is a micro-framework: a small, explicit core with a "batteries optional" philosophy.
- It is built on Werkzeug (WSGI/HTTP) and Jinja2 (templating); Flask is the coordinating glue.
- Every app centres on a Flask application object that holds config and registers routes.
- A rich extension ecosystem adds databases, forms, auth, and APIs only when you need them.
- Choose Flask for APIs, microservices, prototypes, and projects that don't fit an off-the-shelf mould.
π Further Reading
π What's Next?
Now that you can see how a request flows into Flask, the next lesson zooms in on the routing system and view functions β URL patterns, type converters, HTTP methods, and url_for() β the heart of how Flask decides which code runs for each URL.
π Nice work!
You've got Flask's architecture in your head and a running app on your machine. Time to master its routing.