FULL STACK · TECHNICAL DEMO · v1.0.0
MatchdaySimulated betting, real football data.
Matchday is a simulated sports betting platform: real fixtures from Europe’s top five leagues, odds generated by the app and fictitious credits. I designed and released it as a TypeScript monorepo with a React site, an Express API on PostgreSQL and an Angular back-office, on Vercel with Neon; the code was written with Claude Code and every change went through a merge request.
- Role
- Personal AI-assisted project: design, integration, review and release
- Stack
- React · Angular · Express · TypeScript · PostgreSQL · Drizzle · Zod
- Quality
- Vitest · React Testing Library · Cypress · GitLab CI
- Status
- v1.0.0 released on Vercel and Neon · fictitious credits
The goal
The goal was to show a complete application, not just an interface: data from an external provider with usage limits, rules that must hold on every request, an operator panel and a controlled release. A simulated betting system brings these constraints together without any real money.
How it works
Three applications in one repository, with shared types. The API integrates football-data.org, generates and stores odds, and records bets. The public site shows fixtures, odds and the bet slip. The back-office lets operators manage odds and events.
Features
Football data
Competitions, fixtures and standings from football-data.org. The key stays on the server, responses are validated with Zod and mapped to internal types, and a cache helps respect the free tier’s limits. If the provider is slow or rate-limits a refresh, the API serves the last cached response instead of an error.
Simulated odds
A deterministic model for the 1X2 market: points per game, home advantage, a draw probability that falls as the strength gap widens, and a 5% margin. It is a pure function, so it is easy to test.
Public site
Serie A, Premier League, La Liga, Bundesliga and Ligue 1, with upcoming fixtures and a match page with odds. Mobile-first layout with CSS Modules and design tokens, plus loading, error, empty and not-found states.
Bet slip and weekly limit
Selections, total odds, potential payout and a weekly limit in fictitious credits. On submission the API rechecks the odds and records the bet in a transaction that verifies the remaining allowance.
Angular back-office
Operator sign-in to suspend and resume fixtures, set manual odds and review fictitious users, bets and remaining limits. Sessions in PostgreSQL, an HttpOnly cookie and a CSRF token for changes.
Tests and accessibility
Unit and component tests for logic, API and interfaces; two Cypress flows for browsing, the bet slip, placing a bet and the limit block, using fixed responses. Semantic structure, keyboard support, focus management in the bet slip and announcements for loading and errors.
Screens



Real screenshots of the app, captured with test data rather than live football-data.org data.
Technical decisions
Layered API
HTTP routes, business services, external clients and database repositories each have a distinct responsibility. External dependencies are injected or replaced in tests.
Consistent contracts
TypeScript types shared across the apps, Zod validation at the API boundary and a single format for responses and errors. The rest of the code does not depend on the provider’s format.
Persistent state where it matters
Offered odds, bets, limits and operator sessions live in PostgreSQL and survive serverless restarts. A stored price keeps what the user was shown, along with back-office changes. Migrations run at API startup under a Postgres advisory lock, so several simultaneous cold starts never apply them twice.
Same-origin API calls
The site and back-office call /api on their own domain and Vercel forwards requests to the API: no CORS and first-party cookies. Neon uses a pooled connection for requests and a direct one for migrations.
AI-assisted workflow and release
Development with Claude Code on feature/* branches, with review in merge requests and required GitLab pipelines for linting, types, tests, builds and security. Preview separate from Production, manual checks of the main flows and a v1.0.0 tag.