Backend
The backend is a FastAPI application using async SQLAlchemy and PostgreSQL. It owns authentication, authorization, persistence, AI/RAG orchestration, uploads, notifications, and all business rules that cannot be trusted to the client.
App Factory and Middleware
server/app/main.py creates the FastAPI app. It:
- Registers exception handlers for domain errors, validation errors, integrity conflicts, and unknown exceptions.
- Mounts all
/api/*routers. - Serves public
/uploads/*. - Serves the built SPA from
web/distin production. - Adds security headers and CORS.
- Enables Langfuse/LangSmith setup during lifespan when configured.
- Marks interrupted document ingests as failed on startup.
Route mounting order matters: campaign sub-resources are mounted before the base campaign router so longer paths win.
Router Pattern
Routers are grouped by domain in server/app/routers/.
Every route should:
- Authenticate with
require_authorrequire_admin. - Authorize with
require_member,require_dm, or domain-specific helpers. - Validate request bodies with Pydantic schemas.
- Delegate non-trivial logic to
server/app/lib/. - Serialize ORM data through
server/app/serializers.py.
Error Handling
Raise helpers from server/app/errors.py instead of returning ad-hoc error
responses. The app factory converts those errors into a consistent JSON
envelope.
Use:
badRequestfor invalid input or impossible state transitions.unauthorizedfor missing/invalid credentials.forbiddenfor authenticated users lacking permission.notFoundfor missing scoped resources.conflictfor state conflicts.
Business Logic
Keep routers thin. Shared or complex logic belongs under server/app/lib/.
Examples:
| Module | Responsibility |
|---|---|
access.py | Campaign role checks, AI access checks, visible monster scope. |
campaign_clone.py | Campaign duplication. |
combat.py | Combat tracker mutations and encounter expansion. |
encounter_xp.py | Deterministic encounter difficulty math. |
encounter_suggest.py | AI encounter suggestion orchestration. |
entities.py | Codex entity and recap-apply logic. |
npcs.py | NPC helper logic and relationship cleanup. |
uploads.py | Public upload paths and validation. |
rag/ | Document ingestion, retrieval, chat, generation, tracing. |
Background Work
The app uses FastAPI BackgroundTasks for document ingestion, re-processing,
email notifications, and recap extraction. Background tasks run in-process; they
do not survive a server restart. Ingestion recovery in rag/ingest.py marks
stuck work as failed at startup so the UI can offer a retry.
Any blocking model or file-processing work inside async flows should run through
asyncio.to_thread so the event loop remains responsive.