What is Flask?
Flask is a Python web framework built on top of Werkzeug (WSGI toolkit) and Jinja2 (templating engine). It is called a "micro" framework because it ships with minimal built-in tooling β no built-in database layer, form validation, or admin interface. Instead, you pick exactly the extensions you need.
| Feature | Flask | Django |
|---|---|---|
| Philosophy | Micro β add what you need | Batteries included |
| Learning curve | Gentle | Steeper |
| Flexibility | Very high | More opinionated |
| Best for | APIs, smallβmedium apps | Full-stack, CMS, large teams |
| ORM | Not built-in (use SQLAlchemy) | Built-in Django ORM |
Installing Flask
Flask is installed via pip. It is always best practice to work inside a virtual environment:
# Create and activate a virtual environment
python -m venv venv
source venv/bin/activate # macOS / Linux
# venv\Scripts\activate # Windows
# Install Flask
pip install flask
# Verify the installation
python -c "import flask; print(flask.__version__)"
# 3.0.x
A virtual environment isolates your project's dependencies from your system Python and other projects. This prevents version conflicts and keeps your environment reproducible.
Your First Flask Application
A minimal Flask app is just a few lines. Create a file called app.py:
from flask import Flask
app = Flask(__name__) # Create the Flask application instance
@app.route("/") # Register a route for the root URL
def home():
return "Hello, Flask!"
if __name__ == "__main__":
app.run(debug=True) # Start the development server
python app.py
Open your browser at http://127.0.0.1:5000 and you will see "Hello, Flask!". That is your first web application running.
Debug mode enables auto-reloading on code changes and an interactive debugger in the browser. Never use debug=True in production β it exposes your code and allows arbitrary code execution.
Routes and URL Rules
A route maps a URL path to a Python function (called a view function). The @app.route() decorator registers the mapping.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "Home Page"
@app.route("/about")
def about():
return "About Page"
# Dynamic route β captures URL segment as a parameter
@app.route("/user/<username>")
def user_profile(username):
return f"Profile page for {username}"
# Type-converters: string (default), int, float, path, uuid
@app.route("/post/<int:post_id>")
def show_post(post_id):
return f"Post #{post_id}"
# Multiple URLs for the same view
@app.route("/home")
@app.route("/index")
def index():
return "You reached the index!"
if __name__ == "__main__":
app.run(debug=True)
Visit /user/alice and Flask calls user_profile("alice"). Visit /post/42 and Flask calls show_post(42) with an integer, not a string.
HTTP Methods β GET and POST
By default, routes only accept GET requests. Use the methods argument to support POST, PUT, DELETE, and others:
from flask import Flask, request
app = Flask(__name__)
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form.get("username")
password = request.form.get("password")
if username == "admin" and password == "secret":
return f"Welcome, {username}!"
return "Invalid credentials", 401
# GET request β show the login form
return """
<form method="post">
<input name="username" placeholder="Username">
<input name="password" type="password" placeholder="Password">
<button type="submit">Log In</button>
</form>
"""
The request Object
Flask's request is a global context object that holds all information about the incoming HTTP request:
from flask import Flask, request
app = Flask(__name__)
@app.route("/data", methods=["GET", "POST"])
def data():
# --- GET parameters from query string: /data?name=alice&age=30
name = request.args.get("name", "anonymous")
age = request.args.get("age", "unknown")
# --- POST form data
form_value = request.form.get("field_name")
# --- JSON body (for API requests)
json_data = request.get_json() # Returns None if not JSON
# --- Request metadata
print(request.method) # GET, POST, etc.
print(request.url) # Full URL
print(request.remote_addr) # Client IP
print(request.headers) # All HTTP headers
print(request.cookies) # Cookies dict
return f"Hello {name}, age {age}"
Templates with Jinja2
Instead of returning HTML strings, Flask uses Jinja2 templates β HTML files with special syntax for variables, conditionals, and loops. Store templates in a templates/ folder next to your app:
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/users")
def users():
user_list = [
{"name": "Alice", "role": "admin"},
{"name": "Bob", "role": "editor"},
{"name": "Carol", "role": "viewer"},
]
return render_template("users.html", users=user_list, title="User List")
<!DOCTYPE html>
<html>
<head><title>{{ title }}</title></head>
<body>
<h1>{{ title }}</h1>
<ul>
{% for user in users %}
<li>{{ user.name }} β <em>{{ user.role }}</em></li>
{% endfor %}
</ul>
{% if users|length == 0 %}
<p>No users found.</p>
{% endif %}
</body>
</html>
Jinja2 syntax uses {{ variable }} for output and {% tag %} for logic. Common tags include for, if, elif, else, block, and extends.
Template Inheritance
Jinja2 supports template inheritance to avoid repeating your HTML boilerplate across pages:
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}My App{% endblock %}</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<nav><a href="/">Home</a> | <a href="/about">About</a></nav>
<main>{% block content %}{% endblock %}</main>
<footer>© 2024 MyApp</footer>
</body>
</html>
{% extends "base.html" %}
{% block title %}Home β MyApp{% endblock %}
{% block content %}
<h1>Welcome!</h1>
<p>This content is injected into the base template.</p>
{% endblock %}
Returning JSON β Building an API
Flask makes it trivial to return JSON responses, which is the foundation of REST APIs:
from flask import Flask, jsonify, request, abort
app = Flask(__name__)
# In-memory "database"
books = [
{"id": 1, "title": "Fluent Python", "author": "Luciano Ramalho"},
{"id": 2, "title": "Clean Code", "author": "Robert C. Martin"},
]
# GET all books
@app.route("/api/books", methods=["GET"])
def get_books():
return jsonify(books)
# GET a single book
@app.route("/api/books/<int:book_id>", methods=["GET"])
def get_book(book_id):
book = next((b for b in books if b["id"] == book_id), None)
if book is None:
abort(404) # Returns a 404 JSON error
return jsonify(book)
# POST β create a new book
@app.route("/api/books", methods=["POST"])
def create_book():
data = request.get_json()
if not data or "title" not in data:
abort(400, description="title is required")
new_book = {
"id": max(b["id"] for b in books) + 1,
"title": data["title"],
"author": data.get("author", "Unknown"),
}
books.append(new_book)
return jsonify(new_book), 201 # 201 Created
# DELETE a book
@app.route("/api/books/<int:book_id>", methods=["DELETE"])
def delete_book(book_id):
global books
books = [b for b in books if b["id"] != book_id]
return "", 204 # 204 No Content
if __name__ == "__main__":
app.run(debug=True)
Flask Project Structure
For anything beyond a single file, organise your Flask project like this:
my_flask_app/
βββ app/
β βββ __init__.py # create_app() factory function
β βββ routes/
β β βββ __init__.py
β β βββ main.py # Blueprint: general pages
β β βββ api.py # Blueprint: API endpoints
β βββ models.py # SQLAlchemy database models
β βββ templates/
β β βββ base.html
β β βββ home.html
β βββ static/
β βββ style.css
β βββ app.js
βββ tests/
β βββ test_routes.py
βββ config.py # Configuration classes
βββ requirements.txt
βββ run.py # Entry point: from app import create_app
Flask Blueprints
Blueprints let you split your app into modular components:
from flask import Blueprint, jsonify
api_bp = Blueprint("api", __name__, url_prefix="/api/v1")
@api_bp.route("/status")
def status():
return jsonify({"status": "ok", "version": "1.0"})
from flask import Flask
from .routes.api import api_bp
def create_app(config=None):
app = Flask(__name__)
app.config.from_object(config or "config.DevelopmentConfig")
app.register_blueprint(api_bp)
return app
Configuration
import os
class Config:
SECRET_KEY = os.environ.get("SECRET_KEY", "dev-key-change-in-prod")
DEBUG = False
TESTING = False
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = "sqlite:///dev.db"
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL")
# Never hardcode production secrets!
Deploying Flask
Flask's built-in server is not suitable for production. Use a WSGI server like Gunicorn behind a reverse proxy like Nginx:
# Install Gunicorn
pip install gunicorn
# Run with 4 worker processes
gunicorn -w 4 -b 0.0.0.0:8000 "app:create_app()"
# For Docker: create a Dockerfile
# FROM python:3.12-slim
# WORKDIR /app
# COPY requirements.txt .
# RUN pip install -r requirements.txt
# COPY . .
# CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "app:create_app()"]
ποΈ Practical Exercises
- Create a Flask app with at least three routes: home, about, and a dynamic
/greet/<name>page that renders a Jinja2 template. - Build a simple to-do API (in memory) with GET (list all), POST (create), and DELETE (by id) endpoints. Test using
curlor Postman. - Add a
/searchroute that accepts aqquery parameter and returns matching items from a list. - Refactor your app to use a Blueprint and the application factory pattern.
π₯ Challenge: Mini Blog API
Build a REST API for a mini blog with Flask. Requirements: posts have id, title, body, author, and created_at fields. Implement CRUD (GET all, GET one, POST, PUT/PATCH update, DELETE). Add validation β title and body are required. Return proper HTTP status codes (200, 201, 204, 400, 404). Store data in memory. Bonus: add pagination via ?page=1&per_page=5 query params.
- What is Flask and how does it differ from Django?
- What does
Flask(__name__)do? Why pass__name__? - How do you handle both GET and POST requests in the same route?
- What is
request.argsvsrequest.formvsrequest.get_json()? - What is Jinja2 and how do you pass variables to a template?
- What is a Flask Blueprint and why would you use one?
- What WSGI server would you use to deploy a Flask app in production?
- What does
abort(404)do in Flask? - Why should you never use
debug=Truein production?
π Summary
- Flask is a lightweight Python micro-framework for building web apps and APIs.
- Install with
pip install flaskinside a virtual environment. - Use
@app.route("/path")to register URL rules; addmethods=["POST"]for non-GET routes. - The
requestobject holds query params (.args), form data (.form), and JSON (.get_json()). render_template()renders Jinja2 HTML templates stored in thetemplates/folder.jsonify()returns JSON responses with the correctContent-Typeheader.- Blueprints and the application factory pattern keep larger apps maintainable.
- Use Gunicorn + Nginx to deploy Flask in production; never use the dev server.
Related Topics
Frequently Asked Questions
Yes, with proper structure. Use Blueprints to split the app into modules, an application factory to manage configuration, and extensions like Flask-SQLAlchemy, Flask-Login, and Flask-Migrate. Many large-scale applications are built on Flask.
FastAPI is an async-first framework with built-in data validation via Pydantic, automatic OpenAPI docs, and native async/await support. Flask is synchronous by default (though Flask 2.x supports async) and is a more established ecosystem. Choose FastAPI for new API-only projects; Flask for projects needing Jinja2 templating or that have a large existing Flask ecosystem.
Use Flask-SQLAlchemy (pip install flask-sqlalchemy) for an ORM-based approach with SQLite, PostgreSQL, or MySQL. For schema migrations, add Flask-Migrate. For simple key-value needs, Flask-Caching or direct Redis integration work well.
Install Flask-CORS (pip install flask-cors) and apply it: from flask_cors import CORS; CORS(app). You can restrict origins, headers, and methods as needed.