No description
Find a file
2026-04-19 18:48:20 +02:00
.forgejo/workflows ci: add Forgejo Actions workflow for build and deploy 2026-04-14 18:20:05 +02:00
apps feat(landing): add landing page with Hero, HowItWorks and FeatureCards 2026-04-19 18:24:42 +02:00
data-sources formatting 2026-04-10 20:09:46 +02:00
documentation updating docs 2026-04-19 18:48:20 +02:00
packages feat: multiplayer slice — end to end working 2026-04-18 23:32:21 +02:00
scripts updating docs 2026-04-19 09:31:01 +02:00
.dockerignore infra: add Docker Compose setup for local development 2026-03-25 18:56:04 +01:00
.env.example feat: add production deployment config 2026-04-14 11:38:40 +02:00
.gitignore setting up python env, download word data 2026-03-26 11:41:46 +01:00
.prettierignore chore: configure root eslint with react and tanstack router rules 2026-03-21 19:32:38 +01:00
.prettierrc chore: configure prettier with ignore rules and format scripts + running format 2026-03-20 18:37:38 +01:00
Caddyfile feat: add production deployment config 2026-04-14 11:38:40 +02:00
docker-compose.yml adding volumes 2026-04-15 05:07:52 +02:00
eslint.config.mjs fix(lint): resolve all eslint errors across monorepo 2026-04-17 16:46:33 +02:00
mise.toml setting up python env, download word data 2026-03-26 11:41:46 +01:00
package.json chore: rename project from glossa to lila 2026-04-13 10:00:52 +02:00
pnpm-lock.yaml feat(api): add WebSocket foundation and multiplayer game store 2026-04-17 09:36:16 +02:00
pnpm-workspace.yaml formatting 2026-03-21 19:33:07 +01:00
README.md updating docs 2026-04-19 18:40:01 +02:00
tsconfig.base.json formatting 2026-03-21 19:33:07 +01:00
tsconfig.json chore: configure prettier with ignore rules and format scripts + running format 2026-03-20 18:37:38 +01:00
vitest.config.ts chore: configure vitest with project-based setup and coverage 2026-03-20 19:25:00 +01:00

lila

Learn words. Beat friends.

lila is a vocabulary trainer built around a Duolingo-style quiz loop: a word appears in one language, you pick the correct translation from four choices. It supports singleplayer and real-time multiplayer, and is designed to work across multiple language pairs without schema changes.

Live at lilastudy.com.


Stack

Layer Technology
Monorepo pnpm workspaces
Frontend React 18, Vite, TypeScript
Routing TanStack Router
Server state TanStack Query
Styling Tailwind CSS
Backend Node.js, Express, TypeScript
Database PostgreSQL + Drizzle ORM
Validation Zod (shared schemas)
Auth Better Auth (Google + GitHub)
Realtime WebSockets (ws library)
Testing Vitest, supertest
Deployment Docker Compose, Caddy, Hetzner VPS
CI/CD Forgejo Actions

Repository Structure

lila/
├── apps/
│   ├── api/        — Express backend
│   └── web/        — React frontend
├── packages/
│   ├── shared/     — Zod schemas and types shared between frontend and backend
│   └── db/         — Drizzle schema, migrations, models, seeding scripts
├── scripts/        — Python scripts for vocabulary data extraction
└── documentation/  — Project docs

packages/shared is the contract between frontend and backend. All request/response shapes are defined there as Zod schemas and never duplicated.


Architecture

Requests flow through a strict layered architecture:

HTTP Request → Router → Controller → Service → Model → Database

Each layer only talks to the layer directly below it. Controllers handle HTTP only. Services contain business logic only. Models contain database queries only. All database code lives in packages/db — the API never imports Drizzle directly for queries.


Data Model

Words are modelled as language-neutral concepts (terms) with per-language translations. Adding a new language requires no schema changes — only new rows. CEFR levels (A1C2) are stored per translation for difficulty filtering.

Core tables: terms, translations, term_glosses, decks, deck_terms Auth tables (managed by Better Auth): user, session, account, verification

Vocabulary data is sourced from WordNet and the Open Multilingual Wordnet (OMW).


API

POST /api/v1/game/start     — start a quiz session (auth required)
POST /api/v1/game/answer    — submit an answer (auth required)
GET  /api/v1/health         — health check (public)
ALL  /api/auth/*            — Better Auth handlers (public)

The correct answer is never sent to the frontend — all evaluation happens server-side.


Multiplayer

Rooms are created via REST, then managed over WebSockets. Messages are typed via a Zod discriminated union. The host starts the game; all players answer simultaneously with a 15-second server-enforced timer. Room state is held in-memory (Valkey deferred).


Infrastructure

Internet → Caddy (HTTPS)
            ├── lilastudy.com      → web (nginx, static files)
            ├── api.lilastudy.com  → api (Express)
            └── git.lilastudy.com  → Forgejo (git + registry)

Deployed on a Hetzner VPS (Debian 13, ARM64). Images are built cross-compiled for ARM64 and pushed to the Forgejo container registry. CI/CD runs via Forgejo Actions on push to main. Daily database backups are synced to the dev laptop via rsync.

See documentation/deployment.md for the full infrastructure setup.


Local Development

Prerequisites

  • Node.js 20+
  • pnpm 9+
  • Docker + Docker Compose

Setup

# Install dependencies
pnpm install

# Create your local env file (used by docker compose + the API)
cp .env.example .env

# Start local services (PostgreSQL, Valkey)
docker compose up -d

# Build shared packages
pnpm --filter @lila/shared build
pnpm --filter @lila/db build

# Run migrations and seed data
pnpm --filter @lila/db migrate
pnpm --filter @lila/db seed

# Start dev servers
pnpm dev

The API runs on http://localhost:3000 and the frontend on http://localhost:5173.


Testing

# All tests
pnpm test

# API only
pnpm --filter api test

# Frontend only
pnpm --filter web test

Roadmap

Phase Description Status
0 Foundation — monorepo, tooling, dev environment
1 Vocabulary data pipeline + REST API
2 Singleplayer quiz UI
3 Auth (Google + GitHub)
4 Multiplayer lobby (WebSockets)
5 Multiplayer game (real-time, server timer)
6 Production deployment + CI/CD
7 Hardening (rate limiting, error boundaries, monitoring, accessibility) 🔄

See documentation/roadmap.md for task-level detail.