Flask is a micro web framework for Python. It is small, lightweight, and easy to learn. With Flask, you can turn any Python script into a web application. You can create APIs, websites, dashboards, and more.
This lesson introduces Flask fundamentals: routes to handle different URLs, request methods (GET, POST), rendering HTML templates, and returning JSON responses. By the end, you will have built your first web application and API.
🕯️ Magic Note
Flask is a “micro” framework because it keeps the core simple and extensible. It does not include a database abstraction layer or form validation by default. You add what you need. This makes Flask perfect for beginners and small to medium projects.
Bash
pip install flask
Python
from flask import Flask
# Create Flask application instance
app = Flask(__name__)
# Define a route (URL)
@app.route(“/”)
def home():
return “Hello, World!”
# Run the application
if __name__ == “__main__”:
app.run(debug=True)
Bash
python app.py
# Output:
# * Serving Flask app ‘app’
# * Debug mode: on
# * Running on http://127.0.0.1:5000
🕯️ Magic Note
With debug=True, the server automatically restarts when you change your code. This is invaluable during development. Never use debug=True in production.
Python
from flask import Flask
app = Flask(__name__)
@app.route(“/”)
def home():
return “Welcome to my site!”
# Dynamic route with variable
@app.route(“/user/<name>”)
def user_profile(name):
return f”Hello, {name}!”
# Dynamic route with type converter
@app.route(“/square/<int:num>”)
def square(num):
return f”{num} squared is {num ** 2}”
# Multiple variables
@app.route(“/add/<int:a>/<int:b>”)
def add(a, b):
return f”{a} + {b} = {a + b}”
Python
from flask import Flask, request
app = Flask(__name__)
@app.route(“/login”, methods=[“GET”, “POST”])
def login():
if request.method == “POST”:
# Handle form submission
username = request.form.get(“username”)
password = request.form.get(“password”)
# Validate credentials…
return f”Welcome back, {username}!”
else:
# Show login form
return ”’
<form method=”post”>
<input type=”text” name=”username” placeholder=”Username”><br>
<input type=”password” name=”password” placeholder=”Password”><br>
<button type=”submit”>Login</button>
</form>
”’
🕯️ Magic Note
The request object contains all data sent by the client: request.args for URL parameters, request.form for form data, request.json for JSON data, and request.headers for HTTP headers.
Python
from flask import Flask, request
app = Flask(__name__)
# URL parameters (/?name=Ali&age=25)
@app.route(“/search”)
def search():
name = request.args.get(“name”, “Guest”)
age = request.args.get(“age”, type=int)
return f”Searching for {name}, age {age}”
# Form data (POST with application/x-www-form-urlencoded)
@app.route(“/submit”, methods=[“POST”])
def submit():
email = request.form.get(“email”)
message = request.form.get(“message”)
return f”Received: {email} – {message}”
# JSON data (POST with application/json)
@app.route(“/api/data”, methods=[“POST”])
def api_data():
data = request.json
if not data:
return {“error”: “No JSON data”}, 400
return {“received”: data}
Python
from flask import Flask, jsonify
app = Flask(__name__)
# Simple JSON API
@app.route(“/api/user/<int:user_id>”)
def get_user(user_id):
user = {
“id”: user_id,
“name”: “Feloriya”,
“age”: 25,
“skills”: [“Python”, “Web Design”]
}
return user # Flask automatically converts to JSON
# Using jsonify explicitly (more control)
@app.route(“/api/status”)
def status():
return jsonify({“status”: “ok”, “version”: “1.0”})
🕯️ Magic Note
When you return a dictionary from a Flask route, Flask automatically calls jsonify() on it. The response gets the correct Content-Type: application/json header.
HTML (templates/index.html)
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>{{ message }}</h1>
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
</body>
</html>
Python
from flask import Flask, render_template
app = Flask(__name__)
@app.route(“/”)
def home():
data = {
“title”: “My Flask App”,
“message”: “Hello from Flask!”,
“items”: [“Python”, “Flask”, “Jinja2”]
}
return render_template(“index.html”, **data)
Directory Structure
my_app/
├── app.py
├── templates/
│ └── index.html
└── static/
├── css/
│ └── style.css
├── js/
│ └── script.js
└── images/
└── logo.png
HTML Template
<link rel=”stylesheet” href=”{{ url_for(‘static’, filename=’css/style.css’) }}”>
<img src=”{{ url_for(‘static’, filename=’images/logo.png’) }}” alt=”Logo”>
🕯️ Magic Note
The url_for() function generates URLs for routes and static files. It is safer than hard-coding URLs because it works even if your application is not mounted at the root.
Python
from flask import Flask, redirect, url_for, abort
app = Flask(__name__)
@app.route(“/old-page”)
def old_page():
return redirect(url_for(“new_page”))
@app.route(“/new-page”)
def new_page():
return “You have been redirected!”
@app.route(“/secure/<int:user_id>”)
def secure(user_id):
if user_id != 1:
abort(403) # Forbidden
return “Welcome, admin!”
# Custom error handler
@app.errorhandler(404)
def not_found(error):
return {“error”: “Page not found”}, 404
Python
from flask import Flask, request, jsonify
from datetime import datetime
app = Flask(__name__)
# In-memory database
todos = []
next_id = 1
@app.route(“/api/todos”, methods=[“GET”])
def get_todos():
“””Get all todo items.”””
return jsonify(todos)
@app.route(“/api/todos”, methods=[“POST”])
def create_todo():
“””Create a new todo item.”””
global next_id
data = request.json
if not data or “title” not in data:
return {“error”: “Title is required”}, 400
todo = {
“id”: next_id,
“title”: data[“title”],
“description”: data.get(“description”, “”),
“completed”: data.get(“completed”, False),
“created_at”: datetime.now().isoformat()
}
todos.append(todo)
next_id += 1
return jsonify(todo), 201
@app.route(“/api/todos/<int:todo_id>”, methods=[“GET”])
def get_todo(todo_id):
“””Get a single todo by ID.”””
todo = next((t for t in todos if t[“id”] == todo_id), None)
if todo is None:
return {“error”: “Todo not found”}, 404
return jsonify(todo)
@app.route(“/api/todos/<int:todo_id>”, methods=[“PUT”])
def update_todo(todo_id):
“””Update a todo item.”””
todo = next((t for t in todos if t[“id”] == todo_id), None)
if todo is None:
return {“error”: “Todo not found”}, 404
data = request.json
if “title” in data:
todo[“title”] = data[“title”]
if “description” in data:
todo[“description”] = data[“description”]
if “completed” in data:
todo[“completed”] = data[“completed”]
return jsonify(todo)
@app.route(“/api/todos/<int:todo_id>”, methods=[“DELETE”])
def delete_todo(todo_id):
“””Delete a todo item.”””
global todos
todo = next((t for t in todos if t[“id”] == todo_id), None)
if todo is None:
return {“error”: “Todo not found”}, 404
todos = [t for t in todos if t[“id”] != todo_id]
return “”, 204
if __name__ == “__main__”:
app.run(debug=True)
Python
from flask import Flask, request, render_template_string
app = Flask(__name__)
HTML_TEMPLATE = ”’
<!DOCTYPE html>
<html>
<head><title>Calculator</title></head>
<body>
<h1>Simple Calculator</h1>
<form method=”get”>
<input type=”number” name=”a” placeholder=”First number” value=”{{ a }}” required>
<select name=”op”>
<option value=”add” {{ “selected” if op == “add” }}>+</option>
<option value=”sub” {{ “selected” if op == “sub” }}>-</option>
<option value=”mul” {{ “selected” if op == “mul” }}>×</option>
<option value=”div” {{ “selected” if op == “div” }}>÷</option>
</select>
<input type=”number” name=”b” placeholder=”Second number” value=”{{ b }}” required>
<button type=”submit”>Calculate</button>
</form>
{% if result is not none %}
<h2>Result: {{ result }}</h2>
{% endif %}
{% if error %}
<p style=”color: red”>{{ error }}</p>
{% endif %}
</body>
</html>
”’
@app.route(“/”)
def calculator():
a = request.args.get(“a”, type=float)
b = request.args.get(“b”, type=float)
op = request.args.get(“op”)
result = None
error = None
if a is not None and b is not None and op:
if op == “add”:
result = a + b
elif op == “sub”:
result = a – b
elif op == “mul”:
result = a * b
elif op == “div”:
if b == 0:
error = “Cannot divide by zero”
else:
result = a / b
else:
error = “Invalid operation”
return render_template_string(
HTML_TEMPLATE,
a=a, b=b, op=op, result=result, error=error
)
if __name__ == “__main__”:
app.run(debug=True)
- Forgetting to import request, render_template, etc.
- Using debug=True in production (security risk)
- Not handling POST data correctly (request.form vs request.json)
- Hard-coding URLs instead of using url_for()
- Not returning proper HTTP status codes (200, 404, 500)
- Storing files in the wrong directories (templates, static)
- How do you create a route that handles both GET and POST requests?
- Write a route that accepts a variable username from the URL and returns a greeting.
- How do you access JSON data sent in a POST request?
- What is the purpose of the url_for() function?
- Where should HTML templates be stored in a Flask project?
- Write a route that returns a JSON response with a 404 status code.
⚡ Whisper
Flask turns Python into the web. A few lines of code become a server. A route becomes a page. A function becomes an API endpoint. The web is not magic. It is requests and responses. Flask handles both. You define routes. You write functions. You return strings, templates, or JSON. The development server runs. You visit http://localhost:5000. Your code comes alive. This is not just a framework. It is a gateway. From scripts to services. From local to global. From your machine to the world. Learn Flask. Build a blog. Create an API. Deploy your first web app. The web is waiting. Your code is ready.