A Node.js + Express app for live sports match management and real-time commentary over WebSockets, with auth, subscriptions, notifications, and structured match events — backed by PostgreSQL (Neon) via Drizzle ORM, seeded with real fixtures/results from TheSportsDB.
- Express REST API — matches, commentary, structured match events, subscriptions, notifications
- Auth — JWT-based register/login/logout, session tracking, role-based access (viewer / commentator / admin)
- WebSocket server (via
ws) for real-time broadcasts (/ws) — match creation, commentary, structured events, score/status updates - Real sports data — fixtures and results pulled from TheSportsDB's free API (top European soccer leagues) instead of static seed data
- PostgreSQL + Drizzle ORM (
drizzle-orm/node-postgres) — works with any Postgres, including a free Neon project - Migrations using
drizzle-kit - Zod validation schemas for requests
- APM (apminsight) integration via environment variable (
APM_LICENSE_KEY) (optional) - Arcjet middleware used for request protection/rate limits (optional)
- React + Vite client (
client/) — matches list, match detail, subscriptions, notifications, profile
src/index.js— Express server + WebSocket attach + CORSws/server.js— WebSocket server + subscription managementroutes/—auth.js,matches.js,commentary.js,events.js,subscriptions.js,notifications.jsdb/db.js— Postgres pool (pg) + Drizzle instanceschema.js— DB schema (users, matches, commentary, subscriptions, match_events, notifications)
validation/— Zod schemas per routeservices/sportsdb.js— TheSportsDB API client + event→match mapperseed/seed.js— pulls real fixtures/results and simulates one match livearcjet.js— Arcjet protection middleware
drizzle.config.js— Drizzle Kit config for migrationsclient/— Vite React app.env— environment variables (ignored by git)
Prerequisites: Node.js (LTS), npm, a PostgreSQL database (local, or a free Neon project)
- Install dependencies
npm install- Add environment variables (create
.envat project root — see.env.example)
# Postgres connection string (Neon example includes ?sslmode=require)
DATABASE_URL=postgresql://user:password@localhost:5432/sportrealtime
# Server
PORT=8000
HOST=0.0.0.0
# JWT
JWT_SECRET=change-this-to-a-long-random-secret
JWT_EXPIRES_IN=7d
# TheSportsDB — free public test key works out of the box
THESPORTSDB_API_KEY=123
# Optional APM / Arcjet
APM_LICENSE_KEY=
ARCJET_KEY=Note: Do not commit
.envwith secrets.
- Generate / apply migrations
npm run db:generate
npm run db:migrate- Run the server
npm run dev # development with watcher
npm start # production-mode- Seed real fixtures (requires running server)
npm run seed- Run the client
cd client
npm install
npm run devGET /— health/status,/auth— register, login, logout, me, profile update/matches— list (query:limit,status,sport,search), get by id, create, patch score, patch status/matches/:id/commentary— list/create commentary/matches/:id/events— structured match events (auth required; commentator/admin to post)/subscriptions— authenticated subscription CRUD/notifications— authenticated notifications
WebSocket endpoint: ws://<host>/ws (?token=<jwt> for authenticated subscription restore)
- Client → server:
{ type: 'subscribe', matchId },{ type: 'unsubscribe', matchId } - Server → client:
match_created,commentary,match_event,match_status,subscriptions_restored
- Database: create a free Neon project, copy the pooled connection string into
DATABASE_URL. - API: Render Web Service — build
npm install, startnpm start. SetDATABASE_URL,JWT_SECRET,THESPORTSDB_API_KEY, andCORS_ORIGIN(the client's Render Static Site URL) as environment variables. - Client: Render Static Site — root
client/, buildnpm run build, publish dirdist. SetVITE_API_URLandVITE_WS_URLto the API service's URL/host.
See LLM_DEPLOYMENT_CONTEXT.md for a fuller deployment reference.
- APM key rotation: If a secret is accidentally committed, rotate it immediately, remove it from repo, and scrub git history (use
git filter-repoor BFG). .gitignoreincludes.envandapminsight.jsonto avoid committing secrets.- Arcjet middleware is used for rate-limiting / bot protection (enabled only if
ARCJET_KEYset).
- If
drizzle-kitisn't found duringnpm run db:migrate, ensuredrizzle-kitis installed as a dev dependency. - If migrations fail, confirm
DATABASE_URLis reachable and includes?sslmode=requirefor managed Postgres (Neon). - If
npm run seedreports no fixtures, check network access tothesportsdb.comandTHESPORTSDB_API_KEY. - If imports fail (module not found), run
npm installto updatenode_modulesand commitpackage.json/package-lock.json.
- Add unit/integration tests for route handlers and validation
- Add CI (GitHub Actions) to run lint/tests and migrations against a test DB
- Add pagination & cursors to commentary listing
- Add DB indexes for performance (e.g.,
commentary(match_id, created_at desc))
PRs welcome. For security-sensitive changes (rotating keys, scrubbing secrets), coordinate changes with the team and do not push secrets to the repo.