Learn Postgres by running Postgres. Short, hands-on lessons where every learner gets a real, disposable Postgres database — provisioned in seconds, yours to break, one click to reset — and a psql-like terminal right in the browser. No installs, no Docker, no shared sandbox.
- Learning by doing. Every concept is introduced through queries you actually run. The prose exists to set up the next query, not the other way around.
- Real Postgres, not a simulation. Each (user, lesson) pair gets its own
Postgres branch.
EXPLAIN ANALYZEshows real plans, locks really block, and mistakes have real consequences — safely. - Short, focused lessons. One concept per lesson, honest time estimates, skimmable prose.
- Open source content. The whole course lives in
/lessonsas MDX + YAML + SQL. Improving a lesson is a normal pull request — see CONTRIBUTING.md.
10 modules, 43 lessons, from your first SELECT to production operations:
- Query fundamentals — SELECT, WHERE, sorting, aggregation
- Changing data — INSERT, UPDATE, DELETE, upserts, transactions
- Combining tables — joins, set operations, subqueries
- Schema and modeling — data types, constraints, DDL, normalization
- Intermediate querying — conditional expressions, CTEs, window functions
- Postgres power types — arrays, JSONB, ranges, full-text search
- Performance and indexing — indexes, EXPLAIN, query optimization, partitioning
- Programmability — views, functions, triggers, procedures
- Concurrency — MVCC, isolation, locking, deadlocks
- Expert and operations — roles, vacuum, extensions, a troubleshooting capstone
Lessons are readable without an account; signing in (GitHub) unlocks the interactive sandbox, checks, and progress tracking — including a shareable progress badge.
Browser
├─ Next.js pages (catalog, lessons, badge)
└─ xterm.js shell — client-side readline, history, \d-style meta-commands
│ one HTTP POST per statement
▼
Next.js server (stateless, Vercel-deployable)
├─ better-auth (GitHub OAuth)
├─ Lesson loader (MDX + YAML from /lessons, catalog built at build time)
├─ Branch manager (create / fork / reset / cleanup via Xata API)
├─ Query proxy (pg → the learner's branch, per request)
└─ Check runner (lesson-defined assertions, progress persisted)
▼
Xata Postgres
├─ main branch → app metadata (users, sessions, branch registry, progress)
├─ tpl-* branches → pre-seeded template per lesson, built at deploy time
└─ user branches → one per (user, lesson), copy-on-write fork of a template
The pieces that make it feel instant and stay cheap:
- Copy-on-write sandboxes. At deploy time,
templates:preparebuilds a pre-seeded template branch per lesson. Opening a lesson forks the template, so even lessons with multi-million-row seeds (the indexing module) open in seconds. "Reset lesson" just drops and re-forks. - A stateless shell. The terminal is fully client-side (line editing,
history, psql-style output and meta-commands like
\dand\dt); each completed statement is a single HTTP request to the query proxy. No WebSockets, no server-side sessions. - Optional auto-checking. Lessons can declare checks (row counts, schema state, arbitrary queries) that run against your branch and persist to your progress.
- Housekeeping. Idle branches are dropped by a daily cron; per-user branch count is capped with LRU eviction; queries are rate-limited and time-boxed.
Stack: Next.js 16 (App Router) · React 19 · TypeScript · Tailwind 4 · Drizzle · better-auth · next-mdx-remote · xterm.js · Xata · Vercel.
lessons/ the course — one folder per module, one per lesson
(lesson.mdx + lesson.yaml + seed.sql)
app/ Next.js App Router: catalog, lesson pages, badge,
API routes (query proxy, checks, reset, cron cleanup)
components/ lesson UI (<Run>, <Check>, sandbox panel) and the shell
internals (readline, formatter, meta-commands)
lib/ auth, db, lesson discovery/schema, branch manager,
check runner, rate limiting, Xata API client
db/ Drizzle schema + migrations for app metadata
scripts/ new-lesson scaffolder, lesson validator, template prep,
Vercel build entry, Xata cleanup
You'll need:
- Node 22.13+ and npm.
- A Postgres database for app metadata (sessions, progress). Any Postgres
works locally; production uses the Xata project's
mainbranch. - A GitHub OAuth app — sign-in is GitHub-only, so this is required to test
anything behind auth. Create one at
https://github.com/settings/developers with callback URL
http://localhost:3000/api/auth/callback/github. - A Xata project (optional) — only needed for the interactive sandbox. Without it you can still browse the catalog and read every lesson.
# 1. Install dependencies
npm install
# 2. Configure environment
cp .env.example .env.local
# Fill in (see comments in the file for where each value comes from):
# DATABASE_URL app-metadata Postgres
# BETTER_AUTH_SECRET openssl rand -base64 32
# GITHUB_CLIENT_ID/SECRET required for sign-in
# XATA_API_KEY / XATA_ORG_ID / XATA_PROJECT_ID required for sandboxes
# 3. Create the app tables in your metadata database
npm run db:push
# 4. Run it
npm run devOpen http://localhost:3000. Lessons render immediately; sign in with GitHub to get a sandbox. You do not need to prepare template branches locally — when a lesson has no template, the branch manager falls back to seeding the sandbox on demand.
| Script | What it does |
|---|---|
npm run dev |
Next.js dev server |
npm run build |
Production build (also compiles every lesson) |
npm run lint |
ESLint |
npm run lessons:validate |
Validate lesson YAML, structure, and check IDs |
npm run new-lesson |
Scaffold a lesson (see CONTRIBUTING.md) |
npm run templates:prepare |
Build pre-seeded template branches (deploy-time) |
npm run db:push |
Push the Drizzle schema to your metadata DB |
npm run db:studio |
Browse the metadata DB in Drizzle Studio |
Lesson improvements and new lessons are the most valuable contributions — CONTRIBUTING.md walks through the lesson format, the MDX escaping rules, seed constraints, and the PR conventions. App code contributions are welcome too.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Made with ❤️ by Xata 🦋