A real-time multiplayer trivia game where players compete head-to-head with timed questions, speed-based scoring, and live leaderboards. Built for parties, game nights, and casual fun.
Live App: https://trivia-app.jmorrison.workers.dev
- Real-time multiplayer — Create or join game rooms with 4-character room codes
- Timed questions — 15 seconds per question with speed-based scoring
- Streak bonuses — Earn extra points for consecutive correct answers
- Big Board display — Connect a TV or monitor as a spectator display for parties
- Play Again — Seamlessly start a new game with the same group
- Mobile-friendly — Touch-optimized interface with responsive layouts
- Host creates a game — Chooses player count and number of questions
- Players join — Enter the room code and a display name
- Host starts — Game begins when everyone is ready
- Answer questions — Timed multiple choice with instant feedback and commentary
- See results — Final rankings with scores, accuracy, and streaks
- TanStack Start — Full-stack React framework
- Cloudflare Workers — Serverless edge runtime
- Cloudflare D1 — Serverless SQLite database
- Cloudflare Durable Objects — Real-time game state + WebSockets
- Drizzle ORM — Type-safe database queries
- Tailwind CSS v4 + Shadcn UI
- React 19, TanStack Query, Vite
- Node.js 18+
- Cloudflare account with Wrangler CLI access
npm install- Create the D1 database (if starting fresh):
npx wrangler d1 create trivia-db- Apply migrations:
npx wrangler d1 execute trivia-db --remote --file=./drizzle/0000_*.sql- Seed the question bank:
# Hit the seed endpoint after starting the dev server
curl -X POST http://localhost:3003/api/seednpm run devThe app runs at http://localhost:3003. Dev mode uses --remote to connect to the production D1 database.
npm run build && npm run deployThe app uses two complementary Cloudflare storage layers:
- D1 — Persistent data: questions, game records, player answers, scores
- Durable Objects — Real-time ephemeral state: WebSocket connections, timers, live game orchestration
During lobby phase, clients poll for state updates. Once the game starts, all communication switches to WebSockets through the GameRoom Durable Object for instant question delivery, timer sync, and result broadcasting.
| Component | Points |
|---|---|
| Correct answer | 100 base |
| Speed bonus | +10 per second remaining |
| Streak bonus | +50 for 3+ correct in a row |
src/
├── routes/ # File-based routing (TanStack Router)
│ ├── game.create.tsx # Create game form
│ ├── game.join.tsx # Join by room code
│ ├── game.$roomCode.tsx # Game lobby
│ ├── game.$roomCode.play.tsx # Live gameplay (WebSocket)
│ ├── game.$roomCode.results.tsx # Final results
│ ├── display.$roomCode.tsx # Big Board display
│ └── api.games.*.ts # Game API routes
├── durable-objects/
│ └── GameRoom.ts # Real-time game orchestration
├── db/
│ └── schema.ts # 5-table schema (questions, games, players, answers)
└── lib/
├── scoring.ts # Points calculation
└── room-codes.ts # Room code generation
| Command | Description |
|---|---|
npm run dev |
Start dev server (port 3003, remote D1) |
npm run build |
Production build |
npm run deploy |
Deploy to Cloudflare Workers |
npm run test |
Run Vitest test suite |
npm run db:generate |
Generate Drizzle migrations |
npm run db:studio |
Open Drizzle Studio GUI |