Real-time multiplayer Farkle in the browser: human vs human or vs CPU, with shared rules in TypeScript so client and server stay aligned.
Stack: Vue 3 · Vite · TresJS / Three.js (dice) · Fastify · Socket.IO · pnpm workspace (@farkle/game-logic, @farkle/server, @farkle/web).
- Node.js 20+
- pnpm 9 (version pinned via
packageManagerin the rootpackage.json)
pnpm install
pnpm devThis runs the game server on 3030 and the Vite dev server (typically 5173). The SPA proxies API/WebSocket traffic to the backend; override the backend URL with VITE_DEV_BACKEND in apps/web/vite.config.ts if needed (default http://127.0.0.1:3030).
For local env files, copy from .env.example.
pnpm dev:server
pnpm dev:web| Command | Description |
|---|---|
pnpm build |
Build all packages and apps |
pnpm test |
Tests in @farkle/server and @farkle/game-logic |
pnpm lint |
ESLint over packages/ and apps/ |
pnpm changeset |
Add a Changesets changelog entry |
pnpm version-packages |
Apply version bumps from changesets |
pnpm release |
Publish packages (maintainers) |
docker compose up --build- Web: http://localhost:8081 (nginx serving the built SPA)
- Server: port 3030 (HTTP + Socket.IO)
The browser must reach the API at a URL known at image build time. Pass the public Socket.IO origin (no trailing slash):
docker compose build --build-arg VITE_SOCKET_URL=https://your-api.example.comCoolify and similar setups are covered in deployment-coolify.md.
Configure the Node process for public deployments (full list in .env.example):
| Variable | Notes |
|---|---|
CORS_ORIGINS |
Comma-separated allowed web origins (e.g. https://play.example.com). Required for real production; if unset, any origin is allowed (dev convenience only). |
PORT |
Listen port (default 3030). |
CONNECT_RATE_LIMIT_MAX |
Max Socket.IO connection attempts per IP per window (default 30). |
CONNECT_RATE_LIMIT_WINDOW_MS |
Sliding window in ms (default 60000). |
ABANDONED_ROOM_TTL_MS |
Inactive rooms are dropped after this many ms (default 1 hour). |
These are inlined at build time:
VITE_SOCKET_URL— Browser-visible origin of the game server (scheme + host + port, no trailing slash).VITE_GA_MEASUREMENT_ID— Optional Google Analytics 4.VITE_PUBLIC_SITE_URL— Optional canonical site URL.VITE_SOCIAL_TWITTER_URL/VITE_SOCIAL_GITHUB_URL— Footer links (defaults point at the upstream maintainer; set for your fork).
- CORS: Wide-open CORS when
CORS_ORIGINSis unset is for local development. Lock it down in production. - Rate limiting: Limits apply per client IP derived from
X-Forwarded-For(first hop) when present, else the socket address. Use a trusted reverse proxy that setsX-Forwarded-Forfrom the real client; otherwise the header can be spoofed and limits weakened. - Room codes: Eight hex characters (~32 bits of entropy), suitable for casual play—not for resisting aggressive scanning or join flooding. Only connection attempts are rate-limited, not every join payload.
- Accounts: None. Display names are not moderated by the server; handle abuse at the product level if you need to.
| Path | Role |
|---|---|
packages/game-logic |
Shared rules, scoring, and engine-facing types |
apps/server |
Fastify + Socket.IO; rooms, presence, CPU turns |
apps/web |
Vue 3 SPA and 3D dice UI |
Pushes and pull requests to main run pnpm lint, pnpm test, and pnpm build via GitHub Actions.