Lesson 30 of 34

FastAPI & Pydantic: Build an API

FastAPI combines , validation, support, and automatic documentation. Pydantic turns untrusted into validated Python objects.

Bash
python -m pip install "fastapi[standard]"
fastapi dev main.py
# Open http://127.0.0.1:8000/docs for interactive API documentation
Python
# main.py
from typing import Annotated
from fastapi import FastAPI, HTTPException, Query, status
from pydantic import BaseModel, Field

app = FastAPI(title="Book API")

class BookCreate(BaseModel):
    title: str = Field(min_length=1, max_length=200)
    author: str = Field(min_length=1, max_length=120)
    pages: int = Field(gt=0, le=20_000)

class Book(BookCreate):
    id: int

books: dict[int, Book] = {}

@app.get("/health")
def health() -> dict[str, str]:
    return {"status": "ok"}

@app.get("/books", response_model=list[Book])
def list_books(
    search: Annotated[str | None, Query(max_length=100)] = None,
) -> list[Book]:
    results = list(books.values())
    if search:
        needle = search.casefold()
        results = [book for book in results if needle in book.title.casefold()]
    return results

@app.post("/books", response_model=Book, status_code=status.HTTP_201_CREATED)
def create_book(payload: BookCreate) -> Book:
    book = Book(id=len(books) + 1, **payload.model_dump())
    books[book.id] = book
    return book
Python
@app.get("/books/{book_id}", response_model=Book)
def get_book(book_id: int) -> Book:
    if book_id not in books:
        raise HTTPException(status_code=404, detail="Book not found")
    return books[book_id]

@app.delete("/books/{book_id}", status_code=204)
def delete_book(book_id: int) -> None:
    if books.pop(book_id, None) is None:
        raise HTTPException(status_code=404, detail="Book not found")

# test_main.py
from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_create_and_read_book():
    created = client.post("/books", json={
        "title": "The Left Hand of Darkness",
        "author": "Ursula K. Le Guin",
        "pages": 304,
    })
    assert created.status_code == 201
    book_id = created.json()["id"]
    assert client.get(f"/books/{book_id}").json()["pages"] == 304
Finished reading?Mark it done to track your progress.

Tip: use the ← and → keys to move between lessons.