A lightweight Node.js + Express app that demonstrates live sports match management and real-time commentary using WebSockets, Drizzle ORM (Postgres / Neon), Zod validation, and simple observability integration.
- Express REST API for matches and commentary (JSON middleware)
- WebSocket server (via
ws) for real-time broadcasts (/ws) - Drizzle ORM with
node-postgres(pg) adapter for type-safe DB access - 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
src/index.js— Express server + WebSocket attachws/server.js— WebSocket server + subscription managementroutes/matches.js— endpoints for creating/listing matchescommentary.js— nested routes:/matches/:id/commentary
db/db.js— Drizzle +pgPool clientschema.js— DB schema (matches,commentary,match_statusenum)
validation/matches.js— Zod schemas for match endpointscommentary.js— Zod schemas for commentary
ws/— WebSocket handlers and broadcasting functionsarcjet.js— Arcjet protection middleware
drizzle.config.js— Drizzle Kit config for migrations.env— environment variables (ignored by git)apminsight.json— removed secrets; uses${APM_LICENSE_KEY}
Prerequisites: Node.js (LTS), npm, a Postgres DB (Neon recommended)
- Install dependencies
npm install- Add environment variables (create
.envat project root)
# DB connection strings
DATABASE_URL="postgresql://.../dbname?..." # pooler (app)
DATABASE_URL_DIRECT="postgresql://.../dbname?..." # direct (migrations)
# Optional APM
APM_LICENSE_KEY="your_apm_license_key"Note: Do not commit
.envorapminsight.jsonwith 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- Run DB demo (quick CRUD flow)
npm run db:demo- GET
/— health/status - GET
/matches— list matches (query:limit) - POST
/matches— create a match (payload validated with Zod) - GET
/matches/:id/commentary— list commentary for a match (validated params and query) - POST
/matches/:id/commentary— create commentary (validated payload)
WebSocket endpoint: ws://<host>/ws
- Messages:
{ type: 'subscribe', matchId },{ type: 'unsubscribe', matchId } - Server broadcasts:
match_created,commentary
- 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). SeeSECURITY.mdrecommendations. .gitignoreincludes.envandapminsight.jsonto avoid committing secrets.- Arcjet middleware is used for rate-limiting / bot protection (enabled only if
ARCJET_KEYset).
- Migrations failing with
ETIMEDOUTusually indicate a network/connectivity issue to the DB endpoint. VerifyDATABASE_URL_DIRECT, your network, and that Neon allows your IP. - If
drizzle-kitisn’t found duringnpm run db:migrate, ensuredrizzle-kitis installed as a dev dependency. - 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.
If you want, I can also:
- add
SECURITY.mdwith steps to rotate and scrub keys ✅ - add basic tests for your routes and validation ✅
Happy to continue — tell me which follow-up you'd like next! 💡