Module 2 · FastAPI · Drills

Drills: FastAPI Advanced

Reading the deep-dive is not knowing it. Type every one of these into a real app/ folder and run it before you reveal the solution. Effortful recall is the point.

How to use this page Each drill is a small task you can run with uvicorn or pytest. Attempt it first, run it, then click "Show solution" to compare. If yours works differently but correctly — great, that's fluency. Tick each box as you go; your progress is saved in this browser.

A · Warm-up reps Basic

Drill 1 Depends

Write a dependency get_config that returns a dict {"app": "DocChat", "version": "1.0"}, then inject it into a GET /info endpoint that returns it.

Show solution
from fastapi import Depends, FastAPI

app = FastAPI()

def get_config():
    return {"app": "DocChat", "version": "1.0"}

@app.get("/info")
def info(config: dict = Depends(get_config)):
    return config

Drill 2 CORS

Add CORSMiddleware to an app so a Next.js frontend on http://localhost:3000 can call it. Allow all methods and headers.

Show solution
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

Remember: CORS is enforced by the browser, not by Postman or curl. If a request works in Postman but fails from React, this middleware is what's missing.

Drill 3 APIRouter

Move a GET /documents/ endpoint out of main.py into an APIRouter (in routers/documents.py) with a /documents prefix, then mount it with include_router.

Show solution
routers/documents.py
from fastapi import APIRouter

router = APIRouter(prefix="/documents", tags=["documents"])

@router.get("/")
def list_docs():
    return [{"id": 1, "title": "Intro"}]
main.py
from fastapi import FastAPI
from routers import documents

app = FastAPI()
app.include_router(documents.router)

B · Stretch Intermediate

Drill 4 TestClient

Write a pytest test using TestClient that asserts GET /documents/ returns status 200. Assume the router from Drill 3.

Show solution
tests/test_documents.py
from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_documents_returns_200():
    response = client.get("/documents/")
    assert response.status_code == 200

Run it with pytest. TestClient drives the app in-process — no uvicorn, no real network, instant feedback.

Drill 5 BackgroundTasks

Add a POST /documents endpoint that returns {"status": "processing"} immediately and schedules a process_document function as a background task.

Show solution
from fastapi import APIRouter, BackgroundTasks

router = APIRouter(prefix="/documents")

def process_document(doc_id: int):
    print(f"processing {doc_id}...")   # chunk + embed later

@router.post("/")
def upload(doc_id: int, tasks: BackgroundTasks):
    tasks.add_task(process_document, doc_id)
    return {"status": "processing"}

The response returns first; process_document runs after. That's exactly how DocChat will accept an upload and embed it without making the user wait.

C · Build challenge Build

Mini-project Write a get_current_settings dependency that reads config from the environment using pydantic-settings, then inject it into an endpoint. This keeps secrets (DB URL, API key) out of code and makes them swappable in tests — the production-grade config pattern.

Build · settings dependency

Create a Settings class reading database_url and openai_api_key from the environment / a .env file. Expose it through a get_current_settings dependency and use it in GET /health.

Show solution
config.py
from functools import lru_cache
from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    database_url: str
    openai_api_key: str
    model_config = SettingsConfigDict(env_file=".env")

@lru_cache
def get_current_settings() -> Settings:
    return Settings()   # cached — built once, reused
main.py
from fastapi import Depends, FastAPI
from config import Settings, get_current_settings

app = FastAPI()

@app.get("/health")
def health(settings: Settings = Depends(get_current_settings)):
    return {"db_configured": bool(settings.database_url)}

Note @lru_cache: the settings are read once and reused across requests. In a test you can override the dependency — app.dependency_overrides[get_current_settings] = lambda: fake — to inject test config without touching the environment.

D · Rapid recall Flashcards

Click a card to flip it. Say the answer out loud before you flip — that's the rep that builds storage strength.

What is the purpose of Depends?
Inject reusable shared logic (DB session, config, auth) into endpoints — and swap it out in tests.
click to flip
Why does the React frontend need CORS?
Browsers block cross-origin responses; the API must send headers allowing localhost:3000.
click to flip
What does APIRouter do?
Splits routes into modules with a shared prefix/tags; mounted via app.include_router(...).
click to flip
What is TestClient for?
Calling the app in-process over HTTP in pytest — no server, no real network.
click to flip
What does a yield dependency give you?
Setup before yield, teardown after (in finally) — guaranteed cleanup of DB sessions etc.
click to flip
When use BackgroundTasks?
Return the response now, run slow work after — e.g. chunk + embed an uploaded document.
click to flip

E · Self-check before moving on

Tick each only if you can do it without looking:

Next All ticked? DocChat now has a real backend shape — routers, CORS, config, tests. Next we give it something to remember: Module 3 — SQL & Postgres Fundamentals.