Skip to main content

🧪 Flask Framework Overview

Flask is the framework that made a lot of people fall in love with Python on the web. It gives you just enough to start — routing, a request/response object, and templates — and then gets out of your way. This lesson explains what "microframework" really means, what lives at Flask's core, and how it stacks up against Django and FastAPI so you know exactly when to reach for it.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Explain what a microframework is and what Flask includes versus leaves out
  • Describe Flask's two core dependencies — Werkzeug and Jinja2 — and their jobs
  • Trace a request through Flask's request–response cycle
  • Compare Flask with Django and FastAPI and pick the right tool for a project
  • Write and run a minimal Flask app and recognize the application factory pattern

Estimated Time: 30–40 minutes  •  Difficulty: Beginner

Hands-on: Build and run a two-route Flask app, then decide whether Flask fits a project of your own.

In This Lesson

What Is Flask?

Flask is a lightweight, flexible web framework for Python. Armin Ronacher first released it in 2010 — famously as an April Fools' joke that turned out to be genuinely useful — and it has since become one of the most widely used Python web frameworks in the world. Flask gives you the essentials for building web apps and APIs without forcing a rigid structure or a long list of dependencies on you.

💡 Flask's own tagline: "a microframework for Python based on Werkzeug, Jinja, and good intentions." The "good intentions" part is a joke, but the philosophy is real: give developers a small, dependable core and trust them to assemble the rest.

Think of Flask as a well-stocked toolbox rather than a pre-fabricated house. Django hands you a house with the plumbing, wiring, and admin office already installed — great if you want exactly that house. Flask hands you the tools and the blueprint and lets you decide which rooms to build. For small-to-medium apps, APIs, and learning, that freedom is a feature, not a burden.

What "Microframework" Means

The word micro throws people off. It does not mean Flask is only good for tiny apps, and it does not mean your whole application must fit in one file. It means Flask's core is small and deliberately unopinionated. Out of the box, Flask gives you:

  • URL routing — mapping paths like /users to Python functions
  • Request and response objects — reading form data, query strings, JSON; building responses
  • Template rendering — turning HTML templates into finished pages with Jinja2
  • A development server and debugger — for running and troubleshooting locally
  • Built-in support for unit testing — a test client that simulates requests

What Flask deliberately leaves out — and expects you to add only if you need it:

  • A database layer / ORM (add Flask-SQLAlchemy)
  • Form validation and CSRF protection (add Flask-WTF)
  • User authentication and sessions management (add Flask-Login)
  • A ready-made admin interface (add Flask-Admin)

📖 "Batteries included, but removable"

Django's motto is "batteries included" — everything ships in the box. Flask's is "batteries included, but removable." You reach for exactly the components a project needs and skip the rest, which keeps small apps small and gives you fine-grained control over how each piece behaves.

Flask's Core: Werkzeug & Jinja2

Flask itself is thin because it stands on two mature libraries from the same team (the Pallets project):

  1. Werkzeug — a WSGI toolkit. WSGI (Web Server Gateway Interface) is the standard that lets a Python web app talk to a web server. Werkzeug provides the request/response objects, URL routing, and the interactive debugger that Flask exposes.
  2. Jinja2 — a fast, expressive templating engine. It turns templates full of {{ variables }} and {% logic %} into finished HTML.
Flask's core built on Werkzeug and Jinja2, surrounded by optional extensions The Flask core sits on top of Werkzeug and Jinja2. Optional extensions like SQLAlchemy, Flask-Login, and Flask-WTF plug into the core. Werkzeug — WSGI toolkit Jinja2 — template engine Flask core (routing, app) Flask-SQLAlchemy Flask-Login Flask-WTF
Figure 1 — Flask's small core rests on Werkzeug and Jinja2. Extensions (dashed) plug in only when you need them.

The Request–Response Cycle

Every Flask app follows the same journey for each request. Understanding this flow makes debugging far easier, because you can pinpoint where in the chain something went wrong.

flowchart LR A[Browser sends
HTTP request] --> B[WSGI server
Gunicorn / dev server] B --> C[Werkzeug builds
request object] C --> D[Flask matches
URL to route] D --> E[View function
runs your code] E --> F[Jinja2 renders
a template if needed] F --> G[Response sent
back to browser]
  1. The browser sends an HTTP request to your server.
  2. A WSGI server (the built-in dev server locally, or Gunicorn/uWSGI in production) receives it.
  3. Werkzeug parses the raw request into a convenient request object.
  4. Flask's URL dispatcher matches the path to one of your routes.
  5. The matching view function runs — this is your code.
  6. If the view returns a template, Jinja2 renders it into HTML.
  7. The finished response travels back to the browser.

⚠️ The dev server is not for production

Flask's built-in server (started by flask run or app.run()) is single-threaded and unhardened. It is perfect for development but must never face real traffic. In production you run Flask behind a proper WSGI server like Gunicorn or uWSGI, usually behind Nginx.

Flask vs Django vs FastAPI

Flask is one of three Python frameworks you'll hear about constantly. None is "best" in the abstract — each optimizes for something different.

Feature Flask Django FastAPI
Style Microframework Full-stack "batteries included" Modern async microframework
Learning curve Low Moderate to high Low to moderate
Database / ORM Any, via extensions Built-in ORM Any, via extensions
Async support Partial (since 2.0) Partial Native (built on ASGI)
Best for Small–medium apps, APIs, learning Large apps needing structure High-performance / typed APIs
flowchart TD A[What are you building?] --> B{Full website with
admin & auth built in?} B -->|Yes| C[Django] B -->|No| D{High-throughput async API
with typed validation?} D -->|Yes| E[FastAPI] D -->|No| F[Flask is a great fit]

✅ Why Flask is a great place to learn

Because Flask does little "magic," you see the wiring. You write the route, read the request, and build the response yourself — so the concepts you learn (routing, HTTP methods, templates, sessions) transfer cleanly to any framework you meet later, including Django and FastAPI.

The Extension Ecosystem

Flask's power comes from its extensions. Each is a small, focused library that plugs into the core. Here are the ones you'll meet most often:

ExtensionAdds
Flask-SQLAlchemyDatabase access via the SQLAlchemy ORM
Flask-MigrateDatabase schema migrations (built on Alembic)
Flask-WTFForm handling, validation, and CSRF protection
Flask-LoginUser sessions and authentication
Flask-JWT-ExtendedToken-based (JWT) auth for APIs
Flask-MailSending email

The recommended way to wire extensions in is the application factory pattern: create each extension object once at module level, then bind it to the app inside a create_app() function with init_app(). This keeps things testable and lets you spin up differently-configured apps (development, testing, production) from the same code.

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager

# Create extension objects once, unbound to any app yet
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()


def create_app(config_object="config.DevelopmentConfig"):
    app = Flask(__name__)
    app.config.from_object(config_object)

    # Bind the extensions to THIS app instance
    db.init_app(app)
    migrate.init_app(app, db)
    login_manager.init_app(app)
    login_manager.login_view = "auth.login"

    # Register blueprints (route groups)
    from app.main import bp as main_bp
    app.register_blueprint(main_bp)

    return app

💡 Why the factory pattern?

Defining the app inside a function (instead of as a global) means your tests can create a fresh, isolated app with a testing config, and you avoid the circular-import headaches that plague single-file apps as they grow. We build this out fully in the next lesson.

Your First Flask App

Enough theory — here is a complete, runnable Flask application. Save it as app.py:

from flask import Flask

app = Flask(__name__)


@app.route("/")
def home():
    return "Hello, Flask!"


@app.route("/greet/<name>")
def greet(name):
    return f"Hello, {name}!"


if __name__ == "__main__":
    app.run(debug=True)

Then, from a terminal in the same folder (with Flask installed in your virtual environment), run it. Either style works:

# Recommended: the Flask CLI
flask --app app run --debug

# Or run the file directly
python app.py

Visit http://127.0.0.1:5000/ and you'll see:

Hello, Flask!

And http://127.0.0.1:5000/greet/Ray responds with Hello, Ray! — the <name> part of the URL is captured and passed straight into your function.

That's the whole idea in miniature: a route maps a URL to a function, and the function's return value becomes the response. In the lessons ahead we replace those plain strings with rendered templates, JSON, and database-backed data — but the shape stays exactly this simple.

Hands-on Exercise

🏋️ Build a Two-Route "About Me" App

Objective: Get a real Flask app running and prove you understand routing.

Instructions:

  1. Create a virtual environment and install Flask: python -m venv venv, activate it, then pip install flask.
  2. Create app.py with two routes: / that returns a welcome message, and /about that returns a sentence about you.
  3. Add a third, dynamic route /hello/<name> that greets whatever name appears in the URL.
  4. Run it with flask --app app run --debug and visit all three URLs in your browser.
  5. In one sentence, write down why Flask (rather than Django) is a reasonable choice for an app this size.
💡 Hint

Each route is a function directly beneath its @app.route(...) decorator. For the dynamic route, put <name> in the path and add name as the function's parameter — the two must match.

✅ Sample solution
from flask import Flask

app = Flask(__name__)


@app.route("/")
def home():
    return "Welcome to my first Flask app!"


@app.route("/about")
def about():
    return "I'm Ray, learning full stack web development."


@app.route("/hello/<name>")
def hello(name):
    return f"Hello, {name}! Nice to meet you."


if __name__ == "__main__":
    app.run(debug=True)

Why Flask here: the app is tiny and needs no admin panel, ORM, or auth — Flask's small core delivers exactly what's needed with zero overhead.

🎯 Quick Quiz

Question 1: What does calling Flask a "microframework" mean?

Question 2: Which two libraries form Flask's core?

Question 3: When would FastAPI be a better pick than Flask?

Summary & Quiz

🎉 Key Takeaways

  • Flask is a lightweight, flexible Python microframework — small core, add what you need.
  • "Microframework" means minimal core, not minimal capability; extensions cover ORM, forms, auth, and more.
  • Flask stands on Werkzeug (WSGI toolkit) and Jinja2 (templates).
  • Every request follows the same request–response cycle; the dev server is for development only.
  • Choose Flask for small–medium apps, APIs, and learning; Django for big structured sites; FastAPI for typed async APIs.
  • The application factory pattern (create_app() + init_app()) is the modern, testable way to structure a Flask app.

📚 Further Reading

🚀 What's Next?

Next we'll set up a real Flask project from scratch — virtual environment, folder structure, configuration classes, and the full application factory — so you have a solid foundation to build every later feature on.

🎉 Nice work!

You know what Flask is, what makes it tick, and when to reach for it. Time to build a proper project scaffold.