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 pytest and requests
  • 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

FeatureFlaskFastAPI
Async supportNot built‑inNative async
Data validationManual / extensionsBuilt‑in Pydantic
Automatic docsNoSwagger UI & ReDoc
Learning curveEasyModerate
Use caseSmall to medium projects, learningLarge, 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:

  1. Database schema – same as Day 23: expenses table with id, amount, category, description, date.

  2. 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 expense
    • PUT /api/expenses/<id> – update an expense
    • DELETE /api/expenses/<id> – delete an expense
  3. Data validation: Return 400 for missing/invalid data.

  4. Error handling: 404 for missing records, 500 for unexpected errors.

  5. Write tests with pytest that 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/summary that returns total per category.
  • Use Flask‑RESTful or Flask‑RESTX to organize routes as resources.
  • Explore FastAPI and compare the developer experience.

6. Brief Review Summary

ConceptKey Points
FlaskLightweight web framework
Route decorator@app.route("/path", methods=[...])
Request datarequest.args, request.get_json()
ResponseReturn dict/tuple (data, status)
Error handling@app.errorhandler(status_code)
TestingFlask test client, pytest
RESTful designUse 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:

  1. ✅ Complete all 5 exercises
  2. ✅ Build the Personal Expense Tracker API
  3. ✅ Experiment with curl or Postman to interact with your API
  4. ✅ Read about FastAPI – compare its syntax to Flask

Comments

Popular posts from this blog

Day 1: Welcome to Python — Your Journey Begins

Python: Your Gateway to Coding Adventures

Day 11: List Comprehensions & Lambda Functions

Earn From the Comfort of Your Home

Build the skills to work comfortably from home, on your own terms.

Get Started Today