Skip to content

Commit 2a45cd2

Browse files
travisjneumanclaude
andcommitted
feat: add Module 04 — FastAPI Web Apps curriculum
Five progressive projects teaching FastAPI from first endpoint to production-quality authenticated API with tests. Covers uvicorn, Pydantic validation, SQLAlchemy ORM, JWT auth, CORS, and TestClient. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1bcf673 commit 2a45cd2

30 files changed

Lines changed: 2443 additions & 0 deletions

File tree

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Module 04 / Project 01 — Hello FastAPI
2+
3+
Home: [README](../../../../README.md)
4+
5+
## Focus
6+
7+
First endpoint, running with uvicorn, automatic interactive docs at `/docs`.
8+
9+
## Why this project exists
10+
11+
FastAPI is the fastest way to build a Python web API. This project gets you from zero to a running server in minutes. You will create your first endpoint, learn how FastAPI turns decorators into routes, and discover the automatic documentation that makes testing your API effortless.
12+
13+
## Run
14+
15+
```bash
16+
cd projects/modules/04-fastapi-web/01-hello-fastapi
17+
python app.py
18+
```
19+
20+
Then open your browser to:
21+
22+
- **http://127.0.0.1:8000** — your root endpoint
23+
- **http://127.0.0.1:8000/docs** — interactive Swagger UI documentation
24+
- **http://127.0.0.1:8000/items/42?q=hello** — path and query parameters in action
25+
26+
Press `Ctrl+C` in the terminal to stop the server.
27+
28+
## Expected output
29+
30+
Visiting `http://127.0.0.1:8000` returns:
31+
32+
```json
33+
{"message": "Hello, FastAPI!"}
34+
```
35+
36+
Visiting `http://127.0.0.1:8000/items/42?q=hello` returns:
37+
38+
```json
39+
{"item_id": 42, "q": "hello"}
40+
```
41+
42+
Visiting `http://127.0.0.1:8000/health` returns:
43+
44+
```json
45+
{"status": "healthy"}
46+
```
47+
48+
## Alter it
49+
50+
1. Add a new `GET /greet/{name}` endpoint that returns `{"greeting": "Hello, {name}!"}`.
51+
2. Add an optional query parameter `uppercase` (bool) to the greet endpoint. When true, return the greeting in all caps.
52+
3. Add a `GET /add/{a}/{b}` endpoint that takes two integers and returns their sum.
53+
54+
## Break it
55+
56+
1. Change the decorator from `@app.get("/")` to `@app.get` (remove the parentheses and path). What error do you get?
57+
2. Try visiting `http://127.0.0.1:8000/items/not-a-number`. What does FastAPI return? Why?
58+
3. Change `item_id: int` to `item_id: str` in the function signature. How does the `/docs` page change?
59+
60+
## Fix it
61+
62+
1. After removing the parentheses, put them back and add the path string. Confirm the endpoint works again.
63+
2. The 422 error from passing a non-integer is FastAPI's automatic validation. There is nothing to fix because validation is the correct behavior. Explain why in your notes.
64+
3. Restore the `int` type hint. Notice how FastAPI uses Python type hints to validate inputs automatically.
65+
66+
## Explain it
67+
68+
1. What does the `@app.get("/")` decorator do? What happens if two routes have the same path?
69+
2. What is the difference between a path parameter (`/items/{item_id}`) and a query parameter (`?q=hello`)?
70+
3. Where does the interactive documentation at `/docs` come from? Did you write any HTML?
71+
4. What role does `uvicorn` play? Why can't you just run a FastAPI app with `python app.py` without it?
72+
73+
## Mastery check
74+
75+
You can move on when you can:
76+
77+
- start the server and visit `/docs` without looking at these instructions,
78+
- add a new endpoint with both path and query parameters,
79+
- explain what uvicorn does and why FastAPI needs it,
80+
- describe how type hints control validation.
81+
82+
## Next
83+
84+
Continue to [02-crud-api](../02-crud-api/).
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# ============================================================================
2+
# Project 01 — Hello FastAPI
3+
# ============================================================================
4+
# This is your first FastAPI application. It creates a web server with three
5+
# endpoints. FastAPI uses Python type hints and decorators to define routes,
6+
# validate inputs, and generate interactive documentation automatically.
7+
#
8+
# Run this file:
9+
# python app.py
10+
#
11+
# Then visit:
12+
# http://127.0.0.1:8000 — root endpoint
13+
# http://127.0.0.1:8000/docs — interactive API documentation (Swagger UI)
14+
# ============================================================================
15+
16+
from fastapi import FastAPI
17+
18+
# ----------------------------------------------------------------------------
19+
# Create the FastAPI application instance.
20+
# This object is the core of your API. You attach routes to it using
21+
# decorators like @app.get(), @app.post(), etc.
22+
# ----------------------------------------------------------------------------
23+
app = FastAPI()
24+
25+
26+
# ----------------------------------------------------------------------------
27+
# Route 1: GET /
28+
# The @app.get("/") decorator tells FastAPI: "When someone sends a GET request
29+
# to the root path (/), call this function and return its result as JSON."
30+
#
31+
# FastAPI automatically converts the returned dictionary to a JSON response.
32+
# ----------------------------------------------------------------------------
33+
@app.get("/")
34+
def read_root():
35+
"""Return a welcome message. This docstring appears in the /docs page."""
36+
return {"message": "Hello, FastAPI!"}
37+
38+
39+
# ----------------------------------------------------------------------------
40+
# Route 2: GET /items/{item_id}
41+
# Curly braces in the path create a "path parameter." The value in the URL
42+
# gets passed to the function as an argument.
43+
#
44+
# item_id: int — the type hint tells FastAPI to validate that item_id is an
45+
# integer. If someone passes a string like /items/abc, FastAPI automatically
46+
# returns a 422 Unprocessable Entity error. You did not write that validation
47+
# logic; FastAPI inferred it from the type hint.
48+
#
49+
# q: str | None = None — this is a "query parameter." It is not in the path,
50+
# so FastAPI looks for it in the URL query string (e.g., /items/42?q=hello).
51+
# The default value of None makes it optional.
52+
# ----------------------------------------------------------------------------
53+
@app.get("/items/{item_id}")
54+
def read_item(item_id: int, q: str | None = None):
55+
"""Fetch an item by its ID, with an optional search query."""
56+
return {"item_id": item_id, "q": q}
57+
58+
59+
# ----------------------------------------------------------------------------
60+
# Route 3: GET /health
61+
# Health check endpoints are standard in production APIs. Monitoring tools
62+
# ping this endpoint to verify the server is running. It returns a simple
63+
# status message.
64+
# ----------------------------------------------------------------------------
65+
@app.get("/health")
66+
def health_check():
67+
"""Health check endpoint for monitoring."""
68+
return {"status": "healthy"}
69+
70+
71+
# ----------------------------------------------------------------------------
72+
# Run the server when this file is executed directly.
73+
#
74+
# uvicorn is an ASGI server — it listens for HTTP requests and forwards them
75+
# to your FastAPI app. Without uvicorn, your app is just a Python object that
76+
# nobody can reach over the network.
77+
#
78+
# host="127.0.0.1" — only accept connections from your own machine.
79+
# port=8000 — listen on port 8000.
80+
# reload=True — restart the server automatically when you edit this file.
81+
# This is a development convenience. Never use reload=True
82+
# in production.
83+
# ----------------------------------------------------------------------------
84+
if __name__ == "__main__":
85+
import uvicorn
86+
uvicorn.run("app:app", host="127.0.0.1", port=8000, reload=True)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Notes — Hello FastAPI
2+
3+
## What I learned
4+
5+
6+
## What confused me
7+
8+
9+
## What I want to explore next
10+
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Module 04 / Project 02 — CRUD API
2+
3+
Home: [README](../../../../README.md)
4+
5+
## Focus
6+
7+
GET/POST/PUT/DELETE endpoints, Pydantic models for request validation, proper HTTP status codes.
8+
9+
## Why this project exists
10+
11+
Most web APIs revolve around four operations: Create, Read, Update, Delete (CRUD). This project builds a todo list API that demonstrates all four. You will learn how Pydantic models validate incoming data automatically, how to return the right HTTP status codes, and how to structure an API that follows REST conventions.
12+
13+
## Run
14+
15+
```bash
16+
cd projects/modules/04-fastapi-web/02-crud-api
17+
python app.py
18+
```
19+
20+
Then open **http://127.0.0.1:8000/docs** to test every endpoint interactively.
21+
22+
Press `Ctrl+C` to stop the server.
23+
24+
## Expected output
25+
26+
Using the `/docs` interface or `curl`:
27+
28+
```bash
29+
# Create a todo
30+
curl -X POST http://127.0.0.1:8000/todos -H "Content-Type: application/json" -d '{"title": "Learn FastAPI"}'
31+
# Returns: {"id": 1, "title": "Learn FastAPI", "completed": false} (status 201)
32+
33+
# List all todos
34+
curl http://127.0.0.1:8000/todos
35+
# Returns: [{"id": 1, "title": "Learn FastAPI", "completed": false}]
36+
37+
# Update a todo
38+
curl -X PUT http://127.0.0.1:8000/todos/1 -H "Content-Type: application/json" -d '{"title": "Learn FastAPI", "completed": true}'
39+
# Returns: {"id": 1, "title": "Learn FastAPI", "completed": true}
40+
41+
# Delete a todo
42+
curl -X DELETE http://127.0.0.1:8000/todos/1
43+
# Returns: (empty, status 204)
44+
```
45+
46+
## Alter it
47+
48+
1. Add a `description` field (optional string) to the todo model. Make sure it appears in create, update, and response models.
49+
2. Add a `GET /todos?completed=true` query parameter that filters the list to only completed or only incomplete todos.
50+
3. Add a `PATCH /todos/{todo_id}` endpoint that allows partial updates (only the fields you send get changed).
51+
52+
## Break it
53+
54+
1. Send a POST request with an empty body (no JSON). What error does FastAPI return?
55+
2. Send a POST request where `title` is an integer instead of a string. Does Pydantic accept it? Why?
56+
3. Try to GET, PUT, or DELETE a todo with an ID that does not exist. What happens?
57+
58+
## Fix it
59+
60+
1. The empty body error (422) is correct — Pydantic requires the `title` field. No fix needed; understand why validation matters.
61+
2. Pydantic coerces the integer to a string by default. If you want strict validation, use `StrictStr` from pydantic. Try it and see how the behavior changes.
62+
3. The 404 response for missing IDs is already handled. If it is not, add a check that raises `HTTPException(status_code=404, detail="Todo not found")`.
63+
64+
## Explain it
65+
66+
1. What is the difference between `TodoCreate` (the request model) and `TodoResponse` (the response model)? Why use separate models?
67+
2. Why does the POST endpoint return status code 201 instead of 200?
68+
3. Why does the DELETE endpoint return status code 204 with no body?
69+
4. How does Pydantic validation work? Where do you define what fields are required vs. optional?
70+
71+
## Mastery check
72+
73+
You can move on when you can:
74+
75+
- create all four CRUD endpoints from memory,
76+
- explain why separate request and response Pydantic models are a good practice,
77+
- intentionally trigger a 422 validation error and explain the response body,
78+
- describe what status codes 200, 201, 204, 404, and 422 mean.
79+
80+
## Next
81+
82+
Continue to [03-database-backed](../03-database-backed/).

0 commit comments

Comments
 (0)