Kodama (ζ¨ι, "echo") is a spaced-repetition app for learning English and Japanese β built as a solo full-stack project, backend to frontend, from scratch.
π·πΊ Π§ΠΈΡΠ°ΡΡ Π½Π° ΡΡΡΡΠΊΠΎΠΌ
- Create decks of words/phrases in English or Japanese, study them with an SM-2 spaced-repetition algorithm (the same family of algorithm behind Anki).
- Generate new decks automatically with an LLM (topic + level in, ready-made cards out), processed asynchronously so the request never blocks.
- Practice pronunciation: record yourself, get scored against the target word using speech models β Whisper for Japanese, wav2vec2 for English.
- Track progress per language (each language keeps its own review schedule), see stats and a 14-day review-load forecast.
- Real-time UI updates via WebSocket when a background job (generation, pronunciation scoring) finishes.
- Sign in with email/password or Google OAuth; light/dark theme.
This isn't a CRUD-only toy app β a few pieces exist specifically because "just call an API" wasn't good enough:
- AI generation and pronunciation scoring both run through RabbitMQ queues in separate worker processes, not inline in the request handler β an LLM call or a Whisper transcription can take seconds, and neither should block the API event loop. The client gets notified over WebSocket when the job finishes.
- The pronunciation checker never loads its ML models inside the API process β model loading is deferred behind a factory with lazy imports, so only the dedicated worker pays that cost.
- Answer grading is a layered pipeline, not a single
==check: normalize (NFKC + case + punctuation) β exact match β fuzzy match (tuned against real false positives, e.g.rain/train) β an LLM tier for genuinely ambiguous cases, with verdicts cached in Redis. - Language is a first-class dimension, not a UI filter β decks, review queues, and stats are all scoped by language at the database/service layer, not filtered client-side, because two languages need fully independent progress.
Backend β Python 3.12, FastAPI, PostgreSQL + SQLAlchemy 2.0 (typed Mapped[...]), Alembic migrations, Redis (caching), RabbitMQ (async job queues via aio-pika/pika), JWT auth in httpOnly cookies + Google OAuth, Whisper + wav2vec2 for pronunciation scoring, Docker.
Frontend β React 19, TypeScript (strict), TanStack Query, React Router, Tailwind CSS v4, Motion β hand-built UI components (no component library), inline SVG data visualization, light/dark theming with WCAG-checked contrast.
βββββββββββββββ REST + WS ββββββββββββββββ
β React SPA β ββββββββββββββββββββββββΊ β FastAPI β
βββββββββββββββ ββββββββββββββββββββββββ β (api) β
ββββββββ¬ββββββββ
β enqueue
ββββββββββββββββΌβββββββββββββββ
βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββββββββ
β worker β β pronunciation-worker β
β (LLM generation)β β (Whisper / wav2vec2) β
βββββββββββββββββββ βββββββββββββββββββββββββ
β β
ββββββββββββΊ RabbitMQ ββββββββββ
β
PostgreSQL ββββββββββββββΌβββββββββββββΊ Redis
Backend modules follow a thin router β service β schema β model layering (no repository layer β it hasn't earned its place yet). Frontend mirrors that with features/<feature>/{api, hooks, ui}.
Requires Docker and Docker Compose.
cp .env.example .env
# fill in .env β at minimum a JWT secret and an OpenRouter API key
# (generate a JWT secret with: python -c "import secrets; print(secrets.token_urlsafe(32))")
docker compose up --buildThis starts PostgreSQL, Redis, RabbitMQ, the API (runs migrations automatically), and both workers. The API is then available at http://localhost:8001.
For the frontend:
cd frontend
cp .env.example .env
npm install
npm run devActively developed. Core loop (auth, decks, SM-2 study cycle, AI generation, pronunciation scoring, stats) is complete and used daily by the author; deployment to a public instance is planned next.
Built solo by Nikita as a hands-on project to go deep on backend architecture (async processing, caching, ML integration) and frontend craft (accessible, responsive, hand-designed UI) β not a tutorial clone.