Learn SQL by playing through a fantasy RPG. Instead of querying tables of employees and orders, you write real SQL against a game world of characters, quests, creatures, and items — clearing narrative levels as your queries get you further into the story.
Status: in active development. The core engine works today — real query execution, the first levels, and a free practice mode are all functional. Visual polish and persistent progress are in progress and called out explicitly below.
Most people learn SQL against dry sample databases and forget it a week later. SQL Guild wraps the same concepts — SELECT, JOIN, filtering, aggregation — in a game world you actually want to explore. You learn by writing queries that do something: find the character, uncover the quest, count the creatures. The querying is real; only the dataset is fun.
All functional now:
- Narrative levels — progress through story-driven levels (three built so far) with characters and dialogue, solving a SQL challenge to advance
- Live SQL sandbox — write a query, run it, and see the results in a table. Queries execute for real against the game database, not a fake preview
- Practice mode — a separate free-drill mode with tagged exercises and practice sessions, for reps outside the story
- Query validation — submitted SQL is normalised and validated before execution
- A full fantasy game-world schema — races, professions, characters, locations, items, skills, quests, creatures and more, with seed data, so there's a rich world to query from the first level
app/api/query/route.ts ← receives the player's SQL
│
▼
lib/db.ts createSeededDb() ← fresh in-memory SQLite (:memory:)
│ schema (lib/schema.ts) + seed (lib/seed.ts)
▼
better-sqlite3 executes the query
│
▼
QueryResultTable ← results rendered back to the player
Built on Next.js (App Router) with React 19 and TypeScript. The database is better-sqlite3 running in-memory: createSeededDb() builds the schema and loads seed data on demand, so every query runs against a clean, fully-populated world.
Levels live in lib/levels/ (world background, characters, dialogue, per-level definitions, and validation); the practice system lives in lib/practice/ (exercises, sessions, tags). Domain types are modelled in lib/models/.
Why an in-memory, per-request database? Letting learners run arbitrary SQL is the whole point — and also the risk. Seeding a throwaway :memory: database means a player can run anything, including a bad query, without corrupting shared state: every request starts from a clean world. It's safe by construction.
The trade-off — and the current gap. Because the query database is ephemeral by design, player progress isn't persisted yet: which levels you've cleared and your practice history don't survive a reload. Separating "safe, resettable query execution" from "durable progress tracking" is deliberate — they're different layers — and the progress layer is the main build-out remaining (see roadmap).
Next.js 16 (App Router) · React 19 · TypeScript · better-sqlite3 · Tailwind CSS v4
- Visual polish — a proper design pass on levels, the sandbox, and results
- Persistent progress — save cleared levels and practice history across sessions (durable store + player identity)
- More levels — extend beyond the current three, covering joins, aggregation, and subqueries in sequence
- Hardened execution for shared data — if any query ever runs against persistent shared data, enforce read-only access and statement limits (arbitrary SQL against a durable DB is a real risk; the current ephemeral model sidesteps it)
- Hint system and per-level solution checking
In active development. Core query engine, the first levels, and practice mode are working; visual polish and progress persistence are underway. Personal / portfolio project.
Source-available for review and contribution; not licensed for reuse or redistribution.