FastAPI Reference

FastAPI Reference for domus-api

This reference maps FastAPI concepts directly to patterns used in domus-api. Every example is drawn from the actual codebase.

Learning Path

The FastAPI tutorial at fastapi.tiangolo.com/tutorial/ covers 40 topics. For domus-api, the critical ones are:

Priority Topic Why It Matters for domus-api

P0

Bigger Applications — Multiple Files

This IS domus-api’s structure: APIRouter per domain, include_router in main.py

P0

Path Parameters

Every /{path:path} endpoint uses this: /pages/{path}, /codex/Core

P0

Query Parameters

Filtering: ?category=standards, ?q=mandiant, ?year=2026&month=04

P0

Request Body

Every POST endpoint: IncidentCreate, ChangeRequestCreate, ProjectCreate

P0

Response Model

PageList, SearchResponse, CaseStudyResponse — typed responses

P1

Dependencies

Replace the current cache: DocumentCache = None injection with proper Depends()

P1

Testing

TestClient + httpx + pytest — validate.sh should become Python tests

P1

Error Handling

Custom PageNotFound, InvalidPath — extend with proper error models

P2

Middleware

CORS is already configured. Add request logging, timing middleware.

P2

Background Tasks

Cache refresh, git commit after write operations

P2

Metadata and Docs URLs

Customize the Swagger UI title, description, tags

P3

Security

API key auth when exposing beyond localhost

How domus-api Maps to FastAPI Patterns

APIRouter (Bigger Applications)

domus-api uses one router per content domain — this is the official FastAPI pattern for large apps:

# routes/codex.py — one domain, one router
from fastapi import APIRouter

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

@router.get("")
async def list_codex_categories():
    ...

@router.get("/{category}")
async def list_codex_entries(category: str):
    ...
# main.py — register all routers
from domus_api.routes import codex, patterns, search, ...

app.include_router(codex.router)
app.include_router(patterns.router)
app.include_router(search.router)
# ... 25 routers total

Path Parameters

Three patterns used in domus-api:

# Simple parameter
@router.get("/{category}")
async def list_entries(category: str):  # "bash", "vim", "git"
    ...

# Path with slashes (file paths)
@router.get("/{path:path}")
async def get_page(path: str):  # "standards/operations/change-control"
    ...

# Enum for controlled vocabulary
class Severity(str, Enum):
    P1 = "P1"
    P2 = "P2"
    P3 = "P3"
    P4 = "P4"

Query Parameters

from fastapi import Query

@router.get("/pages")
async def list_pages(
    category: str | None = Query(None, description="Filter by category"),
    limit: int = Query(100, ge=1, le=500),
    offset: int = Query(0, ge=0),
):
    ...

@router.get("/search")
async def search(
    q: str = Query(..., min_length=2),  # ... means required
    scope: str | None = Query(None),
):
    ...

Request Body (Pydantic Models)

from pydantic import BaseModel, Field

class IncidentCreate(BaseModel):
    severity: Severity
    description: str = Field(min_length=5, max_length=200)
    systems_affected: str | None = None

@router.post("/case-studies/incidents", status_code=201)
async def create_incident(incident: IncidentCreate):
    # FastAPI auto-validates, auto-documents, auto-rejects bad input
    result = scaffolder.create_incident(
        severity=incident.severity.value,
        description=incident.description,
    )
    return result

Response Models

class PageSummary(BaseModel):
    path: str
    title: str | None = None
    category: str
    attributes: dict[str, str] = {}

class PageList(BaseModel):
    total: int
    pages: list[PageSummary]

@router.get("", response_model=PageList)
async def list_pages(...):
    # FastAPI validates the response matches PageList
    ...

Dependency Injection (Next Improvement)

The current cache injection pattern:

# CURRENT (works but not idiomatic)
cache: DocumentCache = None  # type: ignore
# Injected in lifespan: codex.cache = doc_cache

The proper FastAPI way with Depends():

# BETTER — dependency injection
from fastapi import Depends

def get_cache() -> DocumentCache:
    return doc_cache  # module-level singleton

@router.get("")
async def list_codex(cache: DocumentCache = Depends(get_cache)):
    ...

Testing (Next Improvement)

# tests/test_pages.py
from fastapi.testclient import TestClient
from domus_api.main import app

client = TestClient(app)

def test_health():
    r = client.get("/")
    assert r.status_code == 200
    assert r.json()["status"] == "operational"

def test_search():
    r = client.get("/search?q=mandiant")
    assert r.status_code == 200
    assert r.json()["total"] > 0

def test_create_incident():
    r = client.post("/case-studies/incidents", json={
        "severity": "P4",
        "description": "Test incident from pytest"
    })
    assert r.status_code == 201
    assert "INC-" in r.json()["id"]

Key Concepts

Concept domus-api Usage

async def

All route handlers are async. FastAPI runs them on the event loop.

Type hints

Every parameter and return type is annotated. FastAPI uses these for validation, docs, and serialization.

Pydantic BaseModel

Request bodies (IncidentCreate) and response models (PageList). Auto-validation, auto-docs.

Enum

Controlled vocabularies: Severity(P1-P4), RiskLevel(low-critical), ProjectCategory(work/personal/security).

status_code=201

POST endpoints return 201 Created, not 200 OK.

response_model

Tells FastAPI to validate and filter the response. Strips extra fields.

tags=["codex"]

Groups endpoints in Swagger UI. One tag per router.

Lifespan

@asynccontextmanager for startup (load cache) and shutdown (cleanup).

CORS Middleware

allow_origins=["*"] for local + Tailscale access.

OpenAPI auto-generation

/openapi.json is the machine-readable contract. /docs is the Swagger UI.

Further Reading