Backend Architecture
The ZəkaHouse backend is a FastAPI application written in Python, located at apps/api in the project. It serves as the central API layer that handles all business logic and data management.
Technology Stack
- FastAPI 0.135.3 — High-performance Python web framework
- Python 3.14
- SQLModel 0.0.38 — ORM for database interactions (built on SQLAlchemy and Pydantic)
- Alembic — Database migration management
- PostgreSQL 16 with pgvector extension — Primary relational database
- redis-py 7.4.0 — Python Redis client
- Uvicorn 0.44.0 — ASGI server
- uv — Python package manager
Key Dependencies
- psycopg2-binary — PostgreSQL adapter
- pyjwt[crypto] — JWT token handling
- pwdlib[argon2] — Password hashing with Argon2
- google-genai 1.71.0 — Gemini AI integration (the only AI provider)
- llama-index-core + pgvector — RAG and vector embeddings
- stripe — Payment processing
- workos — listed dependency for a planned SSO integration; not yet wired to any endpoint
- boto3 — S3-compatible storage
- resend — Transactional email
- sentry-sdk — Error tracking and monitoring
Configuration
The backend uses a dual configuration system:
- YAML config at
config/config.yamlfor structured settings - Environment variables which override YAML values when set
The API prefix for all endpoints is /api/v1/.
Responsibilities
The backend handles all core platform operations:
- Authentication and authorization — JWT-based token authentication with role-based access control
- Course management — CRUD operations for organizations, courses, chapters, and activities
- User management — Registration, profiles, and permissions
- File storage and uploads — Handling media uploads with support for local filesystem and S3-compatible storage (via boto3)
- AI features — Integration with Google Gemini and LlamaIndex for learning assistance and RAG
- Search — Content indexing and search across courses and activities
- Payments — Stripe integration for course monetization
- Email — Transactional email via Resend
Database Layer
SQLModel is used as the ORM, connecting to a PostgreSQL 16 database with the pgvector extension for AI embeddings. Database models define the schema for all entities: organizations, users, courses, chapters, activities, collections, and more. Migrations are managed with Alembic to keep the schema in sync across environments.
Caching
Redis is used for:
- Response caching — Frequently accessed data is cached to reduce database load.
- Session data — Temporary session information is stored in Redis for fast retrieval.
API Documentation
FastAPI automatically generates interactive API documentation, but it is only available when running in development mode.
Swagger UI (/docs/) and ReDoc (/redoc/) are only accessible when LEARNHOUSE_DEVELOPMENT_MODE=True is set. They are disabled in production.
Directory Structure
apps/api/
├── src/
│ ├── routers/ # API route definitions
│ ├── db/ # SQLModel table definitions
│ ├── services/ # Business logic
│ ├── security/ # Authentication and authorization
│ └── core/ # Configuration and dependencies
├── migrations/ # Alembic database migrations
├── ee/ # Enterprise Edition features
└── config/ # YAML configuration filesDevelopment
The backend runs on port 1338 by default during local development (configurable via LEARNHOUSE_PORT). In production Docker deployments, it runs on port 9000. Install dependencies and run the server:
uv sync # install dependencies
uv run python app.py # start the dev server (which calls uvicorn internally)When using the ZəkaHouse CLI (zeka-learnhouse dev), the backend starts automatically with hot reload enabled.
Running the tests against PostgreSQL
The suite runs on in-memory SQLite by default. SQLite is not the database this
application runs on, and the gap is not cosmetic — SQLAlchemy’s SQLite dialect
compiles with_for_update() away to nothing:
SQLite : SELECT pack.id FROM pack
Postgres: SELECT pack.id FROM pack FOR UPDATEso every row lock in the codebase — Stripe webhook pack activation, assignment submission, board mutation — is currently verified against a dialect that silently drops the lock. SQLite also does not enforce foreign keys by default, has no JSONB (it is rewritten to JSON to build the schema at all), and types values dynamically instead of rejecting a string compared against an integer column.
Point the suite at a real server with LEARNHOUSE_TEST_DATABASE_URL:
LEARNHOUSE_TEST_DATABASE_URL=postgresql+asyncpg://postgres@localhost:5432/postgres \
uv run pytest src/tests/zeka-learnhouse dev already runs a suitable Postgres. Each pytest-xdist worker gets
its own database, built once from the models, and each test runs inside a
transaction that is rolled back afterwards.
This is not yet the default. Turning it on surfaces pre-existing fixture defects that SQLite was concealing — overwhelmingly foreign keys pointing at rows the fixture never created. Those are being fixed in themed follow-up changes; until the last lands, CI stays on SQLite and this path is opt-in.
Optional extras
uv sync installs what the API needs to serve requests — about 221 MB.
| Extra | Install | What it adds |
|---|---|---|
ocr | uv sync --extra ocr | Local OCR for scanned PDFs and images, via Docling/RapidOCR |
ocr is separate because it resolves torch, which on Linux resolves triton
and 43 nvidia-* CUDA wheels: roughly 5 GB. The CUDA and triton wheels never
load on a CPU host, but torch itself does the work: Docling’s layout model,
TableFormer and RapidOCR all run on it. Without the extra, extract_text_via_ocr()
returns no text and logs once that the extra is missing — a scanned PDF is then
treated the same as a PDF with no extractable text layer. Nothing else in the API
changes.
Before installing it, know that the pipeline runs synchronously inside the API process, so a scanned document blocks the event loop for as long as it takes to convert. Docling also downloads its model weights from HuggingFace on first use.
Test dependencies (pytest, pytest-asyncio, pytest-cov, pytest-xdist,
faker) live in the dev dependency group, which uv sync installs by default
and uv sync --no-dev — what the Docker images run — leaves out.