SpinRank is a Vite frontend backed by a Cloudflare Worker and D1 database. The frontend still talks to the backend through a single action-envelope POST /api contract.
- Frontend: Vite + TypeScript
- Backend: Cloudflare Workers
- Database: Cloudflare D1
- Auth: Google Sign-In, with app-issued session tokens
- Node.js 20+ with
npm - A Cloudflare account with D1 and Workers enabled
- A Google OAuth client ID for Google Identity Services
- Frontend app: src/
- Worker app: worker/
- Contract notes: docs/current-api-contract.md
- Local seed data: worker/seed/dev_seed.sql
npm installcd worker
npm install
cd ..From the worker directory:
cd worker
npx wrangler d1 create spinrank-db
npx wrangler d1 create spinrank-e2e-dbThis prints the database IDs you need for worker/wrangler.toml. Replace:
env.dev.d1_databases[0].database_idenv.dev.d1_databases[0].preview_database_idenv.e2e.d1_databases[0].database_idenv.e2e.d1_databases[0].preview_database_id
The checked-in env.e2e IDs are intentionally dummy placeholder UUIDs, so replace them before using the e2e Worker environment.
If you want separate preview and production databases, create both and set the IDs explicitly.
worker/wrangler.toml already contains:
APP_ENVAPP_ORIGIN
Set the secrets from the worker directory:
cd worker
npx wrangler secret put GOOGLE_CLIENT_ID
npx wrangler secret put APP_SESSION_SECRETUse:
GOOGLE_CLIENT_ID: your Google web client IDAPP_SESSION_SECRET: a long random secret used to sign session tokens
cd worker
npm run db:local:migrate
npm run db:local:migrate:e2ecd worker
npm run db:local:seed
npm run db:local:seed:e2eThis loads the manual verification dataset from worker/seed/dev_seed.sql.
Copy the example file:
cp .env.example .env.localCurrent frontend env vars:
VITE_APP_ENV=devVITE_API_BASE_URL=/apiVITE_GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
Update at least:
VITE_GOOGLE_CLIENT_ID
Keep VITE_API_BASE_URL=/api for local development so the Vite dev server can proxy requests to the local Worker.
cd worker
npm run devBy default the frontend proxies these local backend routes:
http://127.0.0.1:8787/api
Health check:
http://127.0.0.1:8787/health
npm run test— run every Vitest suite undertests/once. This is the defaulttestscript used by CI.npm run test:watch— keep Vitest running in watch mode while you work on the UI.npm run test:unit/npm run test:integration— focus ontests/unitortests/integrationwhen you only need that surface.npm run test:coverage— run Vitest with coverage reporting; results land incoverage/.npm run test:e2e— run the Playwright tests configured inplaywright.config.ts. Install the browsers once withnpx playwright installbefore running this script. This command starts the Vite frontend intestmode and a dedicated test Worker automatically.- The Playwright global setup now applies the local
e2eD1 migrations into an isolated Wrangler state directory before the Worker boots, so the browser suite no longer depends on a pre-migrated local test database. npm run test:all— sequentially runstest:coverageandtest:e2efor a full handoff in one command.
For local e2e runs, Playwright boots the Vite dev server in test mode (loading .env.test) and spawns the Cloudflare Worker in test mode via worker/dev:test. The worker exposes the authenticated /test/bootstrap-user route and reads TEST_AUTH_SECRET from the Node environment, defaulting to test-auth-secret.
If you want to override the default local test secret, export it when you run the suite:
TEST_AUTH_SECRET=your-test-secret npm run test:e2eRun every script from the repo root. Vitest inherits the Vite config, so npm run typecheck must succeed before tests can pass; fix any type errors before rerunning.
npm run test— from withinworker/, this runs the worker-specific Vitest suites intests/unit/workerandtests/integration/worker.- You must
cd workerbefore running npm scripts that reference the worker’spackage.json. npx wrangleror the worker tests may rely on local D1 state and environment variables, so ensure the worker dependencies are installed and the same local setup steps (migrations, seeds) have been applied if the suites expect persisted data.
The worker tests share the repo’s TypeScript config, so rerun npm run typecheck in the root if you see build failures.
From the repo root:
npm run devIf you prefer containers, the provided docker-compose.yml now defines two isolated stacks:
dev: frontend + worker for day-to-day development on5173/8787e2e: frontend + worker dedicated to Playwright on4173/8788, plus a separatee2erunner container
The stacks use separate bridge networks, separate node_modules volumes, separate published ports, and separate Wrangler state volumes so they do not share runtime state or local D1 data.
-
Start the development stack:
docker compose -p spinrank-dev --profile dev up --build worker-dev frontend-dev
- Visit
http://localhost:5173to see the frontend. - The worker API is available at
http://localhost:8787/apiand/health. - Override secrets or IDs from your shell only if you need to, for example:
APP_SESSION_SECRET=... TEST_AUTH_SECRET=... VITE_GOOGLE_CLIENT_ID=... docker compose -p spinrank-dev --profile dev up --build worker-dev frontend-dev
If you seed the local D1 database on your host, the Docker worker will not see that data because Docker uses its own Wrangler state volume. Seed the Docker-backed local database with:
docker compose -p spinrank-dev --profile dev run --rm worker-dev npm run db:local:migrate docker compose -p spinrank-dev --profile dev run --rm worker-dev npm run db:local:seed
- Visit
-
Start the isolated e2e stack when you want to inspect the test frontend/worker manually:
docker compose -p spinrank-e2e --profile e2e up --build frontend-e2e worker-e2e
The test frontend is exposed at
http://localhost:4173and the test worker athttp://localhost:8788. -
Run Playwright inside the dedicated test container:
docker compose -p spinrank-e2e --profile e2e run --rm e2e
This is the simplest Docker e2e command because it brings up the required services automatically. If you already started
frontend-e2eandworker-e2eseparately, the same command reuses them.Or run the worker suites with
docker compose -p spinrank-e2e --profile e2e run --rm worker-e2e npm run test. -
When you are done, stop either stack without affecting the other:
docker compose -p spinrank-dev down docker compose -p spinrank-e2e down
If you want to open the dev app on your phone without changing your local network setup, you can expose the Vite frontend through a temporary Cloudflare Tunnel.
-
Start the local dev stack:
docker compose -p spinrank-dev --profile dev up --build worker-dev frontend-dev
-
In a second terminal, create a temporary tunnel to the frontend:
cloudflared tunnel --url http://localhost:5173
Cloudflare prints a temporary public URL such as:
https://example-tunnel-name.trycloudflare.com -
Add the generated hostname to Google Cloud Console:
- Open
APIs & Services > Credentials. - Open your OAuth 2.0 Web Client.
- Add the exact origin under
Authorized JavaScript origins, for example:
https://example-tunnel-name.trycloudflare.com - Open
-
Restart the Docker dev stack with the tunnel hostname wired into both Vite and the worker:
APP_ORIGIN=https://example-tunnel-name.trycloudflare.com \ __VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS=example-tunnel-name.trycloudflare.com \ docker compose -p spinrank-dev --profile dev up --build worker-dev frontend-dev
-
Open the Cloudflare URL on your phone and test the app there.
Notes:
trycloudflare.comhostnames are temporary. If you start a new tunnel and get a different hostname, update both the Google OAuth origin and__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS.- Google Sign-In matches origins exactly. Use the full
https://...trycloudflare.comorigin and do not include a path.
Frontend checks:
npm run typecheck
npm run buildWorker checks:
cd worker
npx tsc --noEmitBasic local flow:
- Start the Worker.
- Start the frontend.
- Sign in with Google.
- Confirm dashboard data loads.
- Create a season, tournament, and match.
- Confirm leaderboard, progress, and recent matches update.
- Confirm soft-delete actions recalculate rankings.
The production stack runs on Cloudflare:
- Frontend: Cloudflare Pages builds the Vite app and serves it under a Pages domain.
- Backend Worker: A Cloudflare Worker (built via
wrangler publish) powers the/apiendpoints. - Database: A Cloudflare D1 database for the production data.
Use wrangler d1 create from within worker/ to create the prod database:
cd worker
npx wrangler d1 create spinrank-prodUpdate worker/wrangler.toml:
database_id→ the new prod D1 IDpreview_database_id→ optional if you keep a separate preview database
After the database exists, run the remote migrations & seed data:
cd worker
npm run db:remote:migrateIf you keep a dedicated remote e2e database, migrate it separately:
cd worker
npm run db:remote:migrate:e2eStill inside worker/, set the production secrets:
npx wrangler secret put GOOGLE_CLIENT_ID
npx wrangler secret put APP_SESSION_SECRET
npx wrangler secret put APP_ORIGINGOOGLE_CLIENT_ID: Google OAuth web client ID for the production origin.APP_SESSION_SECRET: long random string (used for signing sessions).APP_ORIGIN: the Cloudflare Pages URL (e.g.,https://spinrank.pages.dev).
The Worker deploys automatically when you push to main if you have configured a GitHub workflow that runs npm run deploy (see worker/.github/workflows or your custom action). If you prefer to deploy manually:
cd worker
npm run deployAfter deployment, note the Worker URL (something like https://spinrank.worker.dev). This becomes your production VITE_API_BASE_URL.
-
Go to Cloudflare Pages and create a new project connected to this repository.
-
Set the build command to:
npm run typecheck && npm run build -
Set the build output directory to
dist. -
Add production environment variables:
Name Value VITE_APP_ENVprodVITE_API_BASE_URLhttps://<your-worker-domain>/apiVITE_GOOGLE_CLIENT_IDyour production Google client ID APP_ORIGINthe Pages URL (e.g., https://spinrank.pages.dev) -
Enable automatic builds on pushes to
main.
Pages will now run npm install, then the combined build command, and deploy the dist output to the Pages URL. Cloudflare caches the assets and injects the correct headers automatically.
The existing .github/workflows/pages.yml shows the equivalent steps for a GitHub Pages build (checkout, install, typecheck, build). When you pivot to Cloudflare Pages, replicate that by:
- Running
npm run typecheckbeforenpm run build(as shown in the workflow). - Ensuring
VITE_API_BASE_URLandVITE_GOOGLE_CLIENT_IDare supplied via Cloudflare Pages environment variables. - Optionally copying the workflow logic into Pages via the
Build commandfield, or keeping a GitHub Action that mirrors the Pages build but uploads to Cloudflare Pages usingcloudflare/pages-action.
- Point
VITE_API_BASE_URLto the worker/apiURL. - Confirm
APP_ORIGIN(worker secret + Pages env var) matches the Pages domain. - Rebuild the frontend (either via Pages rebuild or rerun the workflow) whenever you change environment-dependent code.
- The frontend uses a single action-envelope API instead of multiple REST routes.
- Google Sign-In is live; Apple Sign-In is still deferred.
- Local Worker state lives in
worker/.wrangler/state.