Day 27: Building REST APIs with Flask
🐍 Day 27: Building REST APIs with Flask
1. Learning Objectives
By the end of Day 27, you will be able to:
- Understand the REST architecture and how APIs communicate over HTTP
- Set up a basic Flask application
- Define routes and handle HTTP methods (GET, POST, PUT, DELETE)
- Parse request data (query parameters, JSON body)
- Return JSON responses with proper status codes
- Test your API endpoints with
pytestandrequests - Know when to choose Flask vs. FastAPI
2. Concept Explanation
2.1 What is a REST API?
A REST API (Representational State Transfer) exposes your application’s functionality over HTTP using standard methods. It’s like a web server that returns data (usually JSON) instead of HTML pages.
- Resource: an entity like a user, product, or post (identified by a URL)
- Endpoint: a URL + HTTP method combination (e.g.,
GET /users,POST /users) - Stateless: each request contains all the info needed; the server doesn’t remember previous requests
Typical URL pattern:
/api/v1/users # list all users
/api/v1/users/5 # user with ID=5
/api/v1/users/5/orders # orders for user 5
2.2 Why Flask?
Flask is a micro‑framework – lightweight, minimal, and easy to learn. It gives you the essentials (routing, request handling, templating) and lets you add only what you need.
Alternatives:
- FastAPI: modern, async, automatic docs, data validation with Pydantic (great for production)
- Django REST Framework: heavy, full‑featured, built‑in admin
Today we’ll use Flask for its simplicity. The concepts transfer directly to FastAPI.
2.3 Setting Up Flask
Install Flask:
pip install flask
Minimal app:
# app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "Hello, API!"
if __name__ == "__main__":
app.run(debug=True)
Run it:
python app.py
Visit http://127.0.0.1:5000/ → “Hello, API!”.
2.4 Routes and HTTP Methods
Routes map URLs to Python functions. Add methods to handle different verbs.
from flask import request
@app.route("/api/users", methods=["GET", "POST"])
def users():
if request.method == "GET":
# Return list of users (static for now)
return {"users": [{"id": 1, "name": "Alice"}]}
elif request.method == "POST":
data = request.get_json()
# Process data, return 201 Created
return {"msg": "User created", "user": data}, 201
2.5 Accessing Request Data
- Query parameters:
request.args.get("key") - JSON body:
request.get_json() - Form data:
request.form["field"] - Headers:
request.headers.get("Authorization")
2.6 Returning JSON Responses
Flask automatically converts dict to JSON, and you can specify a tuple (response, status_code).
@app.route("/api/items/<int:item_id>")
def get_item(item_id):
if item_id == 1:
return {"id": 1, "name": "Widget"}, 200
else:
return {"error": "Item not found"}, 404
2.7 Error Handling and Custom Responses
Create custom error handlers:
from flask import jsonify
@app.errorhandler(404)
def not_found(e):
return jsonify(error="Resource not found"), 404
2.8 Testing Flask APIs with pytest
We can test our app using Flask’s test client.
# test_app.py
import pytest
from app import app
@pytest.fixture
def client():
app.config["TESTING"] = True
with app.test_client() as client:
yield client
def test_get_users(client):
resp = client.get("/api/users")
assert resp.status_code == 200
assert "users" in resp.get_json()
def test_create_user(client):
resp = client.post("/api/users", json={"name": "Bob"})
assert resp.status_code == 201
data = resp.get_json()
assert data["user"]["name"] == "Bob"
2.9 Flask vs. FastAPI – Quick Guide
| Feature | Flask | FastAPI |
|---|---|---|
| Async support | Not built‑in | Native async |
| Data validation | Manual / extensions | Built‑in Pydantic |
| Automatic docs | No | Swagger UI & ReDoc |
| Learning curve | Easy | Moderate |
| Use case | Small to medium projects, learning | Large, production, async needs |
Both are excellent. Start with Flask to grasp the fundamentals, then explore FastAPI.
3. Code Examples — Complete API
Let’s build a simple Task Manager API (in‑memory storage).
# task_api.py
from flask import Flask, request, jsonify
app = Flask(__name__)
tasks = [
{"id": 1, "title": "Learn Flask", "done": False},
{"id": 2, "title": "Write API", "done": False},
]
next_id = 3
# List all tasks
@app.route("/api/tasks")
def list_tasks():
return {"tasks": tasks}
# Get single task
@app.route("/api/tasks/<int:task_id>")
def get_task(task_id):
task = next((t for t in tasks if t["id"] == task_id), None)
if task:
return task
return {"error": "Task not found"}, 404
# Create a task
@app.route("/api/tasks", methods=["POST"])
def create_task():
global next_id
data = request.get_json() or {}
if "title" not in data:
return {"error": "Title is required"}, 400
new_task = {
"id": next_id,
"title": data["title"],
"done": data.get("done", False)
}
next_id += 1
tasks.append(new_task)
return new_task, 201
# Update a task
@app.route("/api/tasks/<int:task_id>", methods=["PUT"])
def update_task(task_id):
task = next((t for t in tasks if t["id"] == task_id), None)
if not task:
return {"error": "Task not found"}, 404
data = request.get_json() or {}
task["title"] = data.get("title", task["title"])
task["done"] = data.get("done", task["done"])
return task
# Delete a task
@app.route("/api/tasks/<int:task_id>", methods=["DELETE"])
def delete_task(task_id):
global tasks
task = next((t for t in tasks if t["id"] == task_id), None)
if not task:
return {"error": "Task not found"}, 404
tasks = [t for t in tasks if t["id"] != task_id]
return {"message": "Task deleted"}, 200
if __name__ == "__main__":
app.run(debug=True)
Test it:
# test_task_api.py
import json
from task_api import app
def test_list_tasks():
with app.test_client() as client:
resp = client.get("/api/tasks")
assert resp.status_code == 200
data = resp.get_json()
assert len(data["tasks"]) == 2
def test_create_task():
with app.test_client() as client:
resp = client.post("/api/tasks", json={"title": "New task"})
assert resp.status_code == 201
task = resp.get_json()
assert task["title"] == "New task"
4. Hands-On Exercises
Exercise 1: Hello API
Create a Flask app with a single route /hello that returns {"message": "Hello, World!"}. Test it in your browser or with requests.
Exercise 2: Query Parameter API
Add a route /greet that accepts a query parameter name (e.g., /greet?name=Alice) and returns {"greeting": "Hello, Alice!"}. If no name is given, default to “there”.
Exercise 3: In‑Memory Book Collection
Extend the task example to manage a collection of books (title, author, year). Implement GET (list), GET by ID, POST, PUT, and DELETE. Use curl or requests to test each endpoint.
Exercise 4: Error Handling
Add custom error handlers for 400, 404, and 500 errors in your book API. Verify that invalid requests return proper JSON error messages.
Exercise 5: Test with pytest
Write a test suite using pytest that covers all endpoints of your book API. Include tests for successful creation, fetching, updating, deletion, and not‑found scenarios.
5. Applied Challenge Task 🏗️
Personal Expense Tracker API
Build a REST API for the expense tracker (Day 23) instead of the CLI. Use Flask and an SQLite database.
Requirements:
Database schema – same as Day 23:
expensestable with id, amount, category, description, date.Endpoints:
GET /api/expenses– list all expenses (optional query param?category=Food)POST /api/expenses– create a new expense (validate required fields)GET /api/expenses/<id>– get one expensePUT /api/expenses/<id>– update an expenseDELETE /api/expenses/<id>– delete an expense
Data validation: Return 400 for missing/invalid data.
Error handling: 404 for missing records, 500 for unexpected errors.
Write tests with
pytestthat cover each endpoint, including error cases.
Stretch goals:
- Add user authentication (API keys) using headers (preview of advanced security).
- Generate a summary endpoint:
GET /api/expenses/summarythat returns total per category. - Use
Flask‑RESTfulorFlask‑RESTXto organize routes as resources. - Explore FastAPI and compare the developer experience.
6. Brief Review Summary
| Concept | Key Points |
|---|---|
| Flask | Lightweight web framework |
| Route decorator | @app.route("/path", methods=[...]) |
| Request data | request.args, request.get_json() |
| Response | Return dict/tuple (data, status) |
| Error handling | @app.errorhandler(status_code) |
| Testing | Flask test client, pytest |
| RESTful design | Use nouns for resources, HTTP verbs correctly |
7. Preview of Next Topic — Day 28
Tomorrow we’ll step into the world of graphical interfaces:
- Basic GUI Development with Tkinter
- Windows, buttons, labels, entry fields
- Event‑driven programming
- Building a simple desktop application (calculator, to‑do list)
🎯 Your Action Items for Day 27:
- ✅ Complete all 5 exercises
- ✅ Build the Personal Expense Tracker API
- ✅ Experiment with
curlor Postman to interact with your API - ✅ Read about FastAPI – compare its syntax to Flask
Comments
Post a Comment
Leave us your comments here...