Flask is a lightweight web framework for Python. It lets you turn a few functions into a live website or an API with remarkably little code, while still giving you the freedom to structure larger projects however you like. This chapter builds from a minimal app to templated pages, forms, and a small REST API.
A web framework maps URLs to Python functions. When a browser requests /about, Flask calls the function decorated with @app.route("/about") and sends its return value back to the browser. Flask is "micro" — it does the core job well and leaves databases, forms, and extensions to you, chosen as needed.
Install it once per environment:
pip install flask
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "<h1>Hello from Flask</h1>"
@app.route("/about")
def about():
return "<p>A page about this project.</p>"
if __name__ == "__main__":
app.run(debug=True)
Run the file, then open http://127.0.0.1:5000 in your browser. Flask starts a development server and, with debug=True, reloads automatically whenever you edit the code.
Flask looks for templates in a templates/ folder and for CSS, images, and JavaScript in a static/ folder, both next to your app file.
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def home():
name = "Ada"
return render_template("home.html", name=name)
if __name__ == "__main__":
app.run(debug=True)
templates/home.html:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<h1>Welcome, {{ name }}</h1>
</body>
</html>
The double braces {{ name }} print a value passed from Python, and url_for builds correct links to files inside static/.
Browsers send GET requests to fetch a page and POST requests to submit data. A search form is a classic mix: GET to show the form, then a GET with a query string, or POST when the data should not appear in the URL.
from flask import Flask, request
app = Flask(__name__)
@app.route("/search", methods=["GET", "POST"])
def search():
if request.method == "POST":
term = request.form.get("q", "")
return f"<p>You searched for: {term}</p>"
return "<form method='POST'><input name='q'><button>Search</button></form>"
if __name__ == "__main__":
app.run(debug=True)
request.form holds POST body values; request.args holds the values from a GET query string.
Parts of a URL can be captured as variables using angle brackets, then used as function arguments.
@app.route("/user/<name>")
def profile(name):
return f"<p>Profile of {name}</p>"
@app.route("/posts/<int:post_id>")
def post(post_id):
return f"<p>Showing post number {post_id}</p>"
The int: converter ensures the argument is an integer; otherwise Flask returns a 404.
Jinja2, bundled with Flask, adds control flow to templates.
@app.route("/scores")
def scores():
data = {"Mia": 88, "Noah": 72, "Leo": 95}
return render_template("scores.html", scores=data)
templates/scores.html:
<ul>
{% for name, score in scores.items() %}
<li>{{ name }}:
{% if score >= 90 %}Excellent{% elif score >= 75 %}Good{% else %}Needs work{% endif %}
({{ score }})</li>
{% endfor %}
</ul>
Beyond HTML pages, Flask serves data to other programs as JSON. A REST API exposes resources and maps the verbs GET (read), POST (create), PUT (update), and DELETE (remove) onto them.
from flask import Flask, request, jsonify
app = Flask(__name__)
tasks = {} # simple in-memory store: id -> title
next_id = 1
@app.route("/tasks", methods=["GET"])
def list_tasks():
return jsonify(tasks)
@app.route("/tasks", methods=["POST"])
def create_task():
global next_id
body = request.get_json()
tasks[next_id] = body.get("title", "untitled")
reply = {"id": next_id, "title": tasks[next_id]}
next_id += 1
return jsonify(reply), 201
@app.route("/tasks/<int:tid>", methods=["PUT"])
def update_task(tid):
body = request.get_json()
if tid not in tasks:
return jsonify({"error": "not found"}), 404
tasks[tid] = body.get("title", tasks[tid])
return jsonify({"id": tid, "title": tasks[tid]})
@app.route("/tasks/<int:tid>", methods=["DELETE"])
def delete_task(tid):
if tid not in tasks:
return jsonify({"error": "not found"}), 404
del tasks[tid]
return jsonify({"deleted": tid}), 200
if __name__ == "__main__":
app.run(debug=True)
request.get_json() parses the incoming JSON body, and jsonify serializes a Python dict into a proper JSON response with the right content type. Status codes (201 created, 404 missing, 200 success) tell the caller what happened.
curl command or a tool like Postman. For example, curl -X POST -H "Content-Type: application/json" -d '{"title":"buy milk"}' http://127.0.0.1:5000/tasks creates a task, and curl -X DELETE http://127.0.0.1:5000/tasks/1 removes it.
Build a small note-taking app with three routes:
GET / renders a template showing all saved notes.POST /add accepts a note text from a form and stores it.POST /delete/<int:nid> removes a note by id and redirects home.from flask import Flask, render_template, request, redirect, url_for
app = Flask(__name__)
notes = []
next_id = 1
@app.route("/")
def home():
return render_template("notes.html", notes=notes)
@app.route("/add", methods=["POST"])
def add():
global next_id
text = request.form.get("text", "").strip()
if text:
notes.append({"id": next_id, "text": text})
next_id += 1
return redirect(url_for("home"))
@app.route("/delete/<int:nid>", methods=["POST"])
def delete(nid):
global notes
notes = [n for n in notes if n["id"] != nid]
return redirect(url_for("home"))
if __name__ == "__main__":
app.run(debug=True)
templates/notes.html:
<h1>My Notes</h1>
<form method="POST" action="{{ url_for('add') }}">
<input name="text" placeholder="New note...">
<button>Save</button>
</form>
<ul>
{% for note in notes %}
<li>{{ note.text }}
<form method="POST" action="{{ url_for('delete', nid=note.id) }}">
<button>Delete</button>
</form>
</li>
{% else %}
<li>No notes yet.</li>
{% endfor %}
</ul>
notes list to a SQLite table using the sqlite3 skills from Chapter 9. Add a GET /notes.json route that returns all notes as JSON using jsonify, turning your note app into both a website and a tiny REST API.