Skip to content

Project Structure

This chapter lays out the recommended directory structure for a Gaia-powered web application. Following a consistent layout makes the codebase navigable, keeps backend and frontend concerns separated, and ensures that new team members (or AI assistants) can orient themselves quickly.


High-Level Layout

The project is split into three top-level directories — backend/, frontend/, and sdk/ — plus shared configuration at the root.

Text Only
my-gaia-app/
├── backend/
│   ├── main.py                 # FastAPI app factory + lifespan
│   ├── settings.py             # Pydantic BaseSettings (reads .env)
│   ├── api/
│   │   ├── __init__.py
│   │   ├── routes.py           # All FastAPI route handlers
│   │   └── dependencies.py     # Dependency injection (session, client)
│   ├── clients/
│   │   ├── __init__.py
│   │   └── gaia_client.py      # Thin wrapper or re-export of the SDK client
│   ├── models/
│   │   ├── __init__.py
│   │   ├── api_models.py       # Request/response Pydantic models
│   │   └── domain_models.py    # Internal domain types
│   ├── services/
│   │   ├── __init__.py
│   │   ├── auth_service.py     # Session management logic
│   │   ├── query_service.py    # Orchestration: ask, stream, refine
│   │   └── search_service.py   # Exhaustive search logic
│   ├── storage/
│   │   ├── __init__.py
│   │   └── session_store.py    # In-memory or SQLite session store
│   ├── utils/
│   │   ├── __init__.py
│   │   └── errors.py           # Exception helpers, HTTP error mappers
│   ├── requirements.txt
│   └── .env.example
├── frontend/
│   ├── index.html
│   ├── package.json
│   ├── tsconfig.json
│   ├── vite.config.ts
│   └── src/
│       ├── main.tsx            # React entry point
│       ├── App.tsx             # Root component + router
│       ├── api/
│       │   ├── client.ts       # Base fetch wrapper with session header
│       │   └── gaia.ts         # Typed Gaia API calls
│       ├── pages/
│       │   ├── LoginPage.tsx
│       │   ├── ChatPage.tsx
│       │   └── DatasetsPage.tsx
│       ├── components/
│       │   ├── ChatMessage.tsx
│       │   ├── ChatInput.tsx
│       │   ├── DatasetSelector.tsx
│       │   ├── DocumentViewer.tsx
│       │   └── LoginForm.tsx
│       ├── state/
│       │   ├── sessionStore.ts # Zustand store for session/auth
│       │   ├── chatStore.ts    # Zustand store for messages
│       │   └── datasetStore.ts # Zustand store for datasets
│       ├── hooks/
│       │   ├── useAuth.ts
│       │   └── useStreaming.ts
│       └── styles/
│           └── globals.css
├── sdk/
│   └── python/
│       ├── gaia_sdk/           # The provided Python SDK
│       │   ├── __init__.py
│       │   ├── client.py
│       │   ├── models.py
│       │   ├── exceptions.py
│       │   ├── streaming.py
│       │   └── auth.py
│       ├── setup.py
│       └── requirements.txt
├── .env                        # Root env (shared secrets)
├── .gitignore
├── docker-compose.yml
└── README.md

Backend Directory Breakdown

main.py

The FastAPI application factory. Creates the FastAPI instance, registers middleware (CORS), mounts routers, and defines the async lifespan context manager for startup/shutdown tasks.

settings.py

A single Pydantic BaseSettings class that reads all configuration from environment variables or a .env file. Every tunable — Gaia base URL, API key, CORS origins, timeouts — lives here.

api/routes.py

All HTTP route handlers. Each route is a thin function that validates input, calls into services/, and returns a Pydantic response model. Routes should contain minimal business logic.

api/dependencies.py

FastAPI dependency injection providers. These are functions that extract the session ID from request headers, look up the API key, and construct a GaiaClient — all available to routes via Depends().

clients/gaia_client.py

If you're using the provided SDK, this module simply re-exports GaiaClient from gaia_sdk. If you need customizations (extra headers, retry logic, caching), wrap the SDK client here.

models/

Pydantic models split into two files:

  • api_models.py — Shapes of your public API requests and responses.
  • domain_models.py — Internal types (e.g., Session, Principal) not exposed to callers.

services/

Business logic lives here. Each service module is a collection of pure-ish functions (or classes) that orchestrate calls to clients/ and storage/. Routes call services; services call clients.

storage/

Data persistence. For development, an in-memory dict is fine. For production, swap in SQLite, Redis, or a database. The session_store.py module manages session lifecycle (create, lookup, expire, delete).

utils/errors.py

Helper functions for mapping exceptions to HTTP responses. Centralizing error mapping keeps routes clean and ensures a consistent error envelope across all endpoints.


Frontend Directory Breakdown

api/client.ts

A thin fetch wrapper that attaches the X-Session-ID header to every request and provides typed get/post helper functions. All API communication flows through this module.

api/gaia.ts

Typed functions that call your backend API. Each function corresponds to a backend route — login(), ask(), listDatasets(), etc. — and returns typed responses.

pages/

Top-level route components. Each page corresponds to a URL path in react-router-dom. Pages compose components and call hooks; they should contain layout logic, not data-fetching details.

components/

Reusable UI components. These receive props and render UI. They should be as stateless as possible — state lives in Zustand stores and is accessed via hooks.

state/

Zustand stores. Each store manages a single domain:

  • sessionStore.ts — Session ID, login status, API key (transient).
  • chatStore.ts — Messages, streaming state, conversation ID.
  • datasetStore.ts — Available datasets, selected dataset(s).

hooks/

Custom React hooks that compose store access with side effects. For example, useAuth() wraps the session store with login/logout functions, and useStreaming() manages the SSE connection lifecycle.

styles/

Global CSS or Tailwind configuration. Component-specific styles can use CSS modules or Tailwind utility classes directly in JSX.


Initializing the Project from Scratch

Backend

Bash
mkdir -p my-gaia-app/backend
cd my-gaia-app/backend

python -m venv .venv
source .venv/bin/activate

pip install fastapi uvicorn httpx pydantic pydantic-settings python-dotenv
pip freeze > requirements.txt

mkdir -p api clients models services storage utils
touch api/__init__.py clients/__init__.py models/__init__.py \
      services/__init__.py storage/__init__.py utils/__init__.py

Frontend

Bash
cd my-gaia-app

npm create vite@latest frontend -- --template react-ts
cd frontend

npm install zustand react-router-dom
npm install -D tailwindcss @tailwindcss/vite

mkdir -p src/api src/pages src/components src/state src/hooks src/styles

SDK

Bash
cd my-gaia-app

# Copy or symlink the SDK into your project
cp -r /path/to/build-with-gaia/sdk .

# Install as editable so imports resolve
pip install -e sdk/python/

Using the Provided SDK vs. Building Your Own Client

The gaia_sdk package shipped with this guide is the recommended way to interact with the Gaia API. It provides:

Feature SDK DIY httpx Client
Typed request/response models You build them
Async context manager lifecycle You build it
SSE streaming with ask_stream / ask_stream_iter You parse SSE manually
Typed exception hierarchy You map status codes
Auth header management You build it

When to wrap the SDK

If you need features the SDK doesn't provide — request retries, response caching, or custom middleware — create a thin wrapper in clients/gaia_client.py that delegates to the SDK. Don't fork the SDK itself.

Don't duplicate the client

Avoid having both a raw httpx client and the SDK client in the same project. Pick one and use it consistently. The SDK uses httpx under the hood, so you get the same HTTP behavior either way.


Next Steps