Demo app¶
A working Todo CRUD app shipped with bluefox-core as a reference implementation.
Enabling it¶
This mounts a router at /demo/todos with full CRUD operations.
Note
The demo app requires a database. Make sure DATABASE_URL is set.
Endpoints¶
| Method | Path | Description |
|---|---|---|
GET | /demo/todos | List all todos |
POST | /demo/todos | Create a todo |
GET | /demo/todos/{id} | Get a todo by ID |
PATCH | /demo/todos/{id} | Update a todo |
PATCH | /demo/todos/{id}/complete | Mark a todo as completed |
DELETE | /demo/todos/{id} | Delete a todo |
Code structure¶
The demo follows the CQRS convention used across Bluefox apps:
bluefox_core/demo/
__init__.py # mount_demo(app)
models.py # DemoTodo SQLAlchemy model
schemas.py # Pydantic request/response schemas
reads.py # Pure read functions (list_todos, get_todo)
writes.py # Pure write functions (create, update, complete, delete)
api.py # FastAPI router wiring reads/writes to endpoints
tests/
conftest.py # App and client fixtures with session override
factories.py # DemoTodoFactory for test data
test_api.py # Full CRUD test coverage
Reads and writes separation¶
Read functions only query — they never modify state:
async def list_todos(session: AsyncSession) -> list[DemoTodo]:
result = await session.execute(select(DemoTodo))
return list(result.scalars().all())
Write functions modify state but don't commit — the API layer handles commits:
async def create_todo(session: AsyncSession, data: TodoCreate) -> DemoTodo:
todo = DemoTodo(**data.model_dump())
session.add(todo)
await session.flush()
return todo
This pattern keeps business logic testable and gives the caller control over transaction boundaries.