# 01 — Architecture > **Purpose:** Give an LLM the structural context needed to navigate the codebase and understand data flow. Concatenate with 00-project-overview.md and 99-current-task.md. > **Last updated:** 2026-05-15 > **Depends on:** 00-project-overview.md --- ## Monorepo Boundaries ``` lila/ ├── apps/ │ ├── api/ — Express backend: routers, controllers, services, WS handlers │ └── web/ — React frontend: routes, components, hooks, client state ├── packages/ │ ├── shared/ — Zod schemas, constants, derived types. THE CONTRACT. │ └── db/ — Drizzle schema, migrations, models (termModel, lobbyModel), seeding ├── data-pipeline/ — Kaikki extraction → enrichment → sync to PostgreSQL └── documentation/ — Human docs + ai-context/ ``` **Critical rule:** `apps/api` never imports `drizzle-orm` for queries. It only calls functions exported from `packages/db`. All database code lives in `packages/db`. --- ## Layered Architecture (HTTP) ``` HTTP Request ↓ Router — maps URL + method to controller (Express Router) ↓ Controller — validates input (Zod safeParse), calls service, sends response or next(error) for errorHandler middleware ↓ Service — business logic only. No HTTP, no direct DB access. Calls model functions from packages/db. ↓ Model — database queries only. No business logic. Lives in packages/db/src/models/ ↓ Database — PostgreSQL via Drizzle ORM ``` **Error flow:** Controller throws `ValidationError` (400) or `NotFoundError` (404) → caught by `errorHandler` middleware in `app.ts` → mapped to HTTP status. Unknown errors → 500. --- ## WebSocket Architecture The WS server attaches to the same Express HTTP server. Upgrades on `/ws` path. ``` WS Connection Upgrade ↓ Auth middleware — validates Better Auth session from cookie on upgrade ↓ Message Router — dispatches by `type` field (Zod discriminated union) ↓ Handler (lobby or game) — business logic, broadcasts state to room ↓ In-memory stores (lobby game state, game session state) ``` **Message protocol:** All WS messages validated against Zod schemas in `packages/shared/src/schemas/lobby.ts` and `packages/shared/src/schemas/game.ts`. Router switches on `type` field. **State storage:** - Lobby membership → PostgreSQL (`lobbies`, `lobby_players` tables) — durable - Game/room state → in-memory (`InMemoryLobbyGameStore`, `InMemoryGameSessionStore`) — ephemeral, lost on restart. Valkey migration planned. --- ## Data Flow: Singleplayer Quiz ``` POST /api/v1/game/start (GameRequestSchema) ↓ Controller validates → Service.createGameSession ↓ termModel.getGameTerms(filters) + termModel.getDistractors(filters) ↓ Service shuffles options, stores session in GameSessionStore ↓ Returns GameSession { sessionId, questions[] } ↓ [frontend] User selects option → confirms → POST /api/v1/game/answer ↓ Service evaluates server-side (correct answer NEVER sent to frontend) ↓ Returns AnswerResult { isCorrect, correctOptionId, selectedOptionId } ``` **Key design:** Correct answer is stored server-side only (in GameSessionStore). Frontend only sees `optionId` (0–3) and `text`. Prevents cheating. --- ## Data Flow: Multiplayer Game ``` Host creates lobby → POST /api/v1/lobbies → returns room code (e.g. WOLF-42) ↓ Players join via code → POST /api/v1/lobbies/:code/join ↓ All players WS connect → send lobby:join with room code ↓ Server broadcasts lobby:state (player list) to all in room ↓ Host clicks "Start" → WS lobby:start ↓ MultiplayerGameService generates questions, broadcasts game:question ↓ Players submit answers via WS game:answer within 15s server timer ↓ On all-answered or timeout → evaluate, broadcast game:answer_result ↓ After N rounds → broadcast game:finished with final scores ``` --- ## GameSessionStore Abstraction ```typescript // packages/shared/src/schemas/game.ts (interface defined in apps/api) interface GameSessionStore { createSession(session: GameSession): Promise; getSession(sessionId: string): Promise; // ... } ``` **Current:** `InMemoryGameSessionStore` — Map-based, process memory, lost on restart. **Planned:** `ValkeyGameSessionStore` — Redis-compatible, persists across restarts. Same pattern for `LobbyGameStore`. --- ## Key Files by Concern | Concern | Key Files | | --------------- | -------------------------------------------------------------------------------------- | | HTTP routing | `apps/api/src/routes/apiRouter.ts`, `gameRouter.ts`, `lobbyRouter.ts` | | Controllers | `apps/api/src/controllers/gameController.ts`, `lobbyController.ts` | | Services | `apps/api/src/services/gameService.ts`, `multiplayerGameService.ts`, `lobbyService.ts` | | Models | `packages/db/src/models/termModel.ts`, `lobbyModel.ts` | | WS handlers | `apps/api/src/ws/handlers/gameHandlers.ts`, `lobbyHandlers.ts` | | WS router | `apps/api/src/ws/router.ts` | | WS auth | `apps/api/src/ws/auth.ts` | | Shared schemas | `packages/shared/src/schemas/game.ts`, `lobby.ts`, `auth.ts` | | Constants | `packages/shared/src/constants.ts` | | DB schema | `packages/db/src/db/schema.ts` | | Auth config | `apps/api/src/lib/auth.ts` | | Auth middleware | `apps/api/src/middleware/authMiddleware.ts` |