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()"]
Flask: The Minimal, Do-It-Yourself Framework
Flask is a "micro" framework β it gives you routing and request handling, and leaves everything else (database, auth, forms) to libraries you choose. That minimalism is its appeal: nothing you don't ask for.
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/users/<int:uid>") # decorator maps URL β function
def get_user(uid):
return jsonify({"id": uid})
@app.route("/users", methods=["POST"])
def create_user():
data = request.get_json()
return jsonify(data), 201
| Flask | Django | |
|---|---|---|
| Philosophy | minimal, pick your own pieces | batteries included |
| ORM/admin/auth | add via extensions | built in |
| Best for | small apps, APIs, learning | large full-featured sites |
The route decorator is the core idea: @app.route("/path") above a function tells Flask "run this when that URL is requested." URL parts like <int:uid> become function arguments. The trade-off: Flask's freedom means you assemble the stack β SQLAlchemy for the database, Flask-Login for auth, Marshmallow for validation. Great for control and learning how the pieces fit; more decisions than Django's opinionated defaults. Choose Flask for microservices, small APIs, or when you want to understand each layer; choose Django when you want a full toolkit ready to go.
ποΈ 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.
π 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.
Interview Questions
- 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?
Related Topics
FAQ
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.

