Computer Programming 2 · Week 8 Session 2 (Enhanced)
Think of a web server like a waiter in a restaurant:
Flask is the waiter. You write the kitchen logic.
# The smallest possible Flask app
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "Hello, world!"
if __name__ == "__main__":
app.run(debug=True)
# That's it – 8 lines to run a web server!
app.py# Step 1: Create folder
mkdir my-flask-api
cd my-flask-api
# Step 2: Create virtual environment
python3 -m venv .venv
# Step 3: Activate it
source .venv/bin/activate # macOS / Linux
# .venv\Scripts\Activate.ps1 # Windows PowerShell
# Prompt changes to: (.venv) $
# Step 4: Install Flask
pip install Flask
# Step 5: Verify
python -c "import flask; print(flask.__version__)"
Create app.py in your project folder. Let's break down every line:
from flask import Flask — import the Flask classapp = Flask(__name__) — create the application object@app.route("/") — register a URL route (the decorator)def home(): — the function that runs when that URL is visitedapp.run(debug=True) — start the server, auto-restart on changespython app.py then open http://127.0.0.1:5000 in your browser.
from flask import Flask # ① import
app = Flask(__name__) # ② create app
@app.route("/") # ③ register URL
def home(): # ④ view function
return "Hello, Flask!"
# ⑤ Run the dev server
if __name__ == "__main__":
app.run(debug=True)
Open two terminals: one to run the server, one to test it.
python app.pycurl http://127.0.0.1:5000/http://127.0.0.1:5000/
# Terminal 1 – run the server
python app.py
# * Running on http://127.0.0.1:5000
# * Debug mode: on
# Terminal 2 – test it
curl http://127.0.0.1:5000/
# Hello, Flask!
# curl -v shows headers too:
curl -v http://127.0.0.1:5000/
# HTTP/1.1 200 OK
# Content-Type: text/html
# Hello, Flask!
Every @app.route("/path") maps a URL to a Python function.
/ → home() |
/about → about() |
/ping → ping()
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "Welcome to my API!"
@app.route("/about")
def about():
return "This is a beginner Flask API."
@app.route("/ping")
def ping():
return "pong"
@app.route("/version")
def version():
return "v1.0.0"
if __name__ == "__main__":
app.run(debug=True)
Starting from the Hello World app, add these three routes:
/hello — returns "Hello, student!"/today — returns today's date as a string (use datetime)/lucky — returns a random number between 1–100 (use random)from datetime import date then str(date.today())import random then str(random.randint(1, 100))
curl .../hello → Hello, student!curl .../today → 2026-05-04 (or current date)curl .../lucky → a random number
Here is one possible solution. Compare with yours:
date and random at the toprandom.randint(1, 100) every request gives a new valuefrom flask import Flask
from datetime import date
import random
app = Flask(__name__)
@app.route("/")
def home():
return "Welcome!"
@app.route("/hello")
def hello():
return "Hello, student!"
@app.route("/today")
def today():
return str(date.today()) # e.g. "2026-05-04"
@app.route("/lucky")
def lucky():
return str(random.randint(1, 100))
if __name__ == "__main__":
app.run(debug=True)
APIs return JSON (JavaScript Object Notation), not plain text.
jsonify() to convert a Python dict to a JSON responseContent-Type: application/json headerreturn {"key": "value"} directly{"name": "Ana", "age": 20}{"name": "Ana", "age": 20} (looks the same, but it's text!)
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/status")
def status():
# jsonify converts dict → JSON response
return jsonify({"service": "api", "status": "ok"})
@app.route("/info")
def info():
data = {
"name": "My Flask API",
"version": "1.0",
"author": "Student"
}
return jsonify(data)
# Flask 2+: shortcut (returns dict directly)
@app.route("/ping")
def ping():
return {"message": "pong"} # works in Flask 2+
Create a route /me that returns a JSON object describing yourself (or a fictional student).
name, course, year, hobbies (a list)curl http://127.0.0.1:5000/me{
"name": "Ana",
"course": "Computer Programming 2",
"year": 2,
"hobbies": ["coding", "gaming", "reading"]
}
/team route that returns a list of such objects (at least 3 members).
Which Flask function sets the Content-Type: application/json header automatically?
Put <variable> in the route URL to capture parts of the path.
/hello/<name> — captures a string/square/<int:n> — captures and converts to int/price/<float:amount> — captures as float/hello/Ana → name = "Ana"/square/7 → n = 7 (already an int!)
from flask import Flask, jsonify
app = Flask(__name__)
# String parameter
@app.route("/hello/<name>")
def hello(name):
return jsonify({"message": f"Hello, {name}!"})
# Integer parameter (auto-converted)
@app.route("/square/<int:n>")
def square(n):
return jsonify({"n": n, "result": n * n})
# Float parameter
@app.route("/double/<float:x>")
def double(x):
return jsonify({"input": x, "doubled": x * 2})
Add these routes to your app.py:
/greet/<name> — returns {"greeting": "Hi, Ana!"}/multiply/<int:a>/<int:b> — returns the product of a × b/upper/<word> — returns the word in UPPERCASE/repeat/<int:times>/<word> — returns the word repeated times times, space-separatedcurl http://127.0.0.1:5000/greet/Sam
# {"greeting": "Hi, Sam!"}
curl http://127.0.0.1:5000/multiply/6/7
# {"a": 6, "b": 7, "result": 42}
curl http://127.0.0.1:5000/upper/flask
# {"result": "FLASK"}
curl http://127.0.0.1:5000/repeat/3/hello
# {"result": "hello hello hello"}
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/greet/<name>")
def greet(name):
return jsonify({"greeting": f"Hi, {name}!"})
@app.route("/multiply/<int:a>/<int:b>")
def multiply(a, b):
return jsonify({"a": a, "b": b, "result": a * b})
@app.route("/upper/<word>")
def upper(word):
return jsonify({"result": word.upper()})
@app.route("/repeat/<int:times>/<word>")
def repeat(times, word):
# " ".join([word] * times) repeats the word
result = " ".join([word] * times)
return jsonify({"result": result})
if __name__ == "__main__":
app.run(debug=True)
?key=valueQuery params come after the ? in the URL. They are optional extras.
request.args.get("key")?name=Ana&age=20/users/42 — the ID is required, part of the resource/search?q=flask — optional filter or modifier
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/search")
def search():
# .get() returns None if missing – provide a default!
q = request.args.get("q", "")
page = int(request.args.get("page", 1))
size = int(request.args.get("size", 10))
return jsonify({
"query": q,
"page": page,
"page_size": size
})
# Test: /search?q=flask&page=2&size=5
# {"page": 2, "page_size": 5, "query": "flask"}
Create these routes that use query parameters:
/greet?name=Ana&greeting=Hello — returns "Hello, Ana!" (use defaults: name=World, greeting=Hi)/add?a=5&b=3 — returns the sum of a and b as JSON/shout?msg=hello×=3 — returns the message in UPPERCASE repeated times timescurl "http://127.0.0.1:5000/greet?name=Sam&greeting=Hey"
# {"message": "Hey, Sam!"}
curl "http://127.0.0.1:5000/greet"
# {"message": "Hi, World!"} ← defaults
curl "http://127.0.0.1:5000/add?a=10&b=25"
# {"a": 10, "b": 25, "sum": 35}
curl "http://127.0.0.1:5000/shout?msg=hello×=3"
# {"result": "HELLO HELLO HELLO"}
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/greet")
def greet():
name = request.args.get("name", "World")
greeting = request.args.get("greeting", "Hi")
return jsonify({"message": f"{greeting}, {name}!"})
@app.route("/add")
def add():
a = int(request.args.get("a", 0))
b = int(request.args.get("b", 0))
return jsonify({"a": a, "b": b, "sum": a + b})
@app.route("/shout")
def shout():
msg = request.args.get("msg", "")
times = int(request.args.get("times", 1))
result = " ".join([msg.upper()] * times)
return jsonify({"result": result})
if __name__ == "__main__":
app.run(debug=True)
The method tells the server what action to take:
| Method | Action | Example URL | Body? |
|---|---|---|---|
| GET | Read data | GET /items | No |
| POST | Create new data | POST /items | Yes (JSON) |
| PUT | Replace / update | PUT /items/1 | Yes (JSON) |
| DELETE | Remove data | DELETE /items/1 | No |
methods=["POST"] (or others) to @app.route().
Every response has a status code that says what happened:
| Code | Meaning | When to use |
|---|---|---|
| 200 | OK | Default success for GET/PUT/DELETE |
| 201 | Created | POST that successfully created a resource |
| 400 | Bad Request | Client sent invalid data (missing field, wrong type…) |
| 404 | Not Found | The item the client asked for doesn't exist |
| 405 | Method Not Allowed | Wrong HTTP method for this route |
| 500 | Internal Server Error | A bug in your Python code |
return jsonify({"error": "not found"}), 404
When a client sends data, it goes in the request body (not in the URL).
methods=["POST"] to the routerequest.is_json (optional but good practice)request.get_json()-X POST — use POST method-H "Content-Type: application/json" — tell server the body is JSON-d '{"key":"value"}' — the actual body
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/echo", methods=["POST"])
def echo():
# 1. Check Content-Type header
if not request.is_json:
return jsonify({"error": "Must send JSON"}), 400
# 2. Parse the body
data = request.get_json(silent=True)
if data is None:
return jsonify({"error": "Invalid JSON"}), 400
# 3. Echo it back
return jsonify({"you_sent": data}), 200
# Test:
# curl -X POST http://127.0.0.1:5000/echo \
# -H "Content-Type: application/json" \
# -d '{"name": "Ana", "age": 20}'
Build two POST routes:
POST /add-numbers — body: {"a": 5, "b": 3} → returns {"sum": 8}. Return 400 if a or b is missing.POST /register — body: {"username": "ana", "password": "secret"} → returns {"message": "Registered: ana"}. Return 400 if either field is missing or blank.# Add numbers
curl -X POST http://127.0.0.1:5000/add-numbers \
-H "Content-Type: application/json" \
-d '{"a": 10, "b": 5}'
# {"sum": 15}
# Missing field (should return 400)
curl -X POST http://127.0.0.1:5000/add-numbers \
-H "Content-Type: application/json" \
-d '{"a": 10}'
# {"error": "..."}
from flask import Flask, request, jsonify
app = Flask(__name__)
def bad(msg):
"""Helper: return a 400 error response."""
return jsonify({"error": msg}), 400
@app.route("/add-numbers", methods=["POST"])
def add_numbers():
body = request.get_json(silent=True) or {}
a = body.get("a")
b = body.get("b")
if a is None or b is None:
return bad("Both 'a' and 'b' are required")
try:
return jsonify({"sum": float(a) + float(b)})
except (TypeError, ValueError):
return bad("'a' and 'b' must be numbers")
@app.route("/register", methods=["POST"])
def register():
body = request.get_json(silent=True) or {}
username = (body.get("username") or "").strip()
password = (body.get("password") or "").strip()
if not username:
return bad("username is required")
if not password:
return bad("password is required")
return jsonify({"message": f"Registered: {username}"}), 201
Before we use a database, we can store data in a plain Python list.
items list and a counter for IDsfrom flask import Flask, request, jsonify
app = Flask(__name__)
# Our "database" in memory
items = []
next_id = 1
@app.route("/items", methods=["GET"])
def list_items():
return jsonify(items) # returns the whole list
@app.route("/items", methods=["POST"])
def create_item():
global next_id
body = request.get_json(silent=True) or {}
name = (body.get("name") or "").strip()
if not name:
return jsonify({"error": "name is required"}), 400
item = {"id": next_id, "name": name}
items.append(item)
next_id += 1
return jsonify(item), 201 # 201 Created
Let clients request one specific item by its ID.
<int:item_id>next((i for i in items if i["id"]==item_id), None)None if not found.
from flask import Flask, jsonify
app = Flask(__name__)
items = [{"id": 1, "name": "apple"}, {"id": 2, "name": "banana"}]
def find_item(item_id):
"""Return item with matching id, or None."""
return next((i for i in items if i["id"] == item_id), None)
@app.route("/items/<int:item_id>", methods=["GET"])
def get_item(item_id):
item = find_item(item_id)
if item is None:
return jsonify({"error": "Item not found"}), 404
return jsonify(item)
# Test:
# curl http://127.0.0.1:5000/items/1 → {"id":1,"name":"apple"}
# curl http://127.0.0.1:5000/items/99 → 404 not found
Try these commands in order. Each builds on the previous one.
# 1. LIST (empty)
curl http://127.0.0.1:5000/items
# []
# 2. CREATE two items
curl -X POST http://127.0.0.1:5000/items \
-H "Content-Type: application/json" -d '{"name": "apple"}'
# {"id": 1, "name": "apple"}
curl -X POST http://127.0.0.1:5000/items \
-H "Content-Type: application/json" -d '{"name": "banana"}'
# {"id": 2, "name": "banana"}
# 3. LIST (now has 2 items)
curl http://127.0.0.1:5000/items
# [{"id": 1, "name": "apple"}, {"id": 2, "name": "banana"}]
# 4. GET one item
curl http://127.0.0.1:5000/items/1
# {"id": 1, "name": "apple"}
# 5. UPDATE item 1
curl -X PUT http://127.0.0.1:5000/items/1 \
-H "Content-Type: application/json" -d '{"name": "green apple"}'
# {"id": 1, "name": "green apple"}
# 6. DELETE item 2
curl -X DELETE http://127.0.0.1:5000/items/2
# (empty 204 response)
# 7. LIST again
curl http://127.0.0.1:5000/items
# [{"id": 1, "name": "green apple"}]
You want to create a new item in an API. Which HTTP method and status code are correct?
A note has: id, title, body, created_at (datetime string).
/notes — list all notes/notes/<int:id> — get one note (404 if not found)/notes — create note (require title, optional body) → 201/notes/<int:id> — update title/body → 404 if missing/notes/<int:id> — delete → 204from datetime import datetime then datetime.now().isoformat() for the timestamp.notes = []
GET /notes?search=flask that filters notes whose title contains the search term.
from flask import Flask, request, jsonify
from datetime import datetime
app = Flask(__name__)
notes = []
next_id = 1
def find(note_id):
return next((n for n in notes if n["id"] == note_id), None)
@app.route("/notes", methods=["GET"])
def list_notes():
search = request.args.get("search", "").lower()
result = [n for n in notes if search in n["title"].lower()] if search else notes
return jsonify(result)
@app.route("/notes/<int:note_id>", methods=["GET"])
def get_note(note_id):
note = find(note_id)
if not note:
return jsonify({"error": "not found"}), 404
return jsonify(note)
@app.route("/notes", methods=["POST"])
def create_note():
global next_id
body = request.get_json(silent=True) or {}
title = (body.get("title") or "").strip()
if not title:
return jsonify({"error": "title required"}), 400
note = {"id": next_id, "title": title,
"body": body.get("body", ""),
"created_at": datetime.now().isoformat()}
notes.append(note)
next_id += 1
return jsonify(note), 201
@app.route("/notes/<int:note_id>", methods=["PUT"])
def update_note(note_id):
note = find(note_id)
if not note:
return jsonify({"error": "not found"}), 404
body = request.get_json(silent=True) or {}
if "title" in body:
note["title"] = (body["title"] or "").strip() or note["title"]
if "body" in body:
note["body"] = body["body"]
return jsonify(note)
@app.route("/notes/<int:note_id>", methods=["DELETE"])
def delete_note(note_id):
note = find(note_id)
if not note:
return jsonify({"error": "not found"}), 404
notes.remove(note)
return "", 204
By default, Flask returns HTML error pages. For an API, return JSON instead.
@app.errorhandler(code) to intercept errors"error" key so client code can handle them consistently.
from flask import Flask, jsonify
app = Flask(__name__)
@app.errorhandler(404)
def not_found(e):
return jsonify({"error": "Resource not found"}), 404
@app.errorhandler(405)
def method_not_allowed(e):
return jsonify({"error": "Method not allowed"}), 405
@app.errorhandler(500)
def internal_error(e):
return jsonify({"error": "Internal server error"}), 500
# Now ALL unmatched routes return JSON 404
# instead of Flask's default HTML page
@app.route("/items")
def items():
return jsonify([])
if __name__ == "__main__":
app.run(debug=True)
A client sends a POST /items request but forgets to include the required "name" field. What status code should you return?
pip install Flask installs to system Python, not your project.
source .venv/bin/activate
request.get_json() returns None if the header is missing.
Always add -H "Content-Type: application/json" to curl.
request.get_json(silent=True) or {} to avoid crashes.
debug=False before deploying.
jsonify() so clients get proper JSON with correct headers.
pytest and Flask's test clientpip install flask-sqlalchemy for database support