Built for the way real cricket is actually played.
Every cricket scoring app assumes both teams are locked before the match starts, overs never change, and no player can appear in both sides. None of that is true in turf cricket, college cricket, or gully cricket.
CricTrack is a real-time match management platform built specifically for local cricket — where a player shows up late, overs get renegotiated mid-game, and one person genuinely plays for both teams. It handles the full match lifecycle from toss to career stats, syncing every delivery instantly across all connected devices via WebSockets.
Live Scoring — Umpire View |
Over Break — Bowler Selection |
Match Setup — Joker Player Selection |
Match Summary — Top Performers |
The Dugout — Career Stats Table |
Player Card — Flip for Batting / Bowling |
| What other apps assume | What actually happens in local cricket |
|---|---|
| Both squads locked before match | Players show up late — add them mid-game |
| Overs never change | "Let's make it 8 overs" after over 3 |
| No player on both teams | One strong player bats and bowls for both sides |
| Substitutions follow official rules | Bench a batter, bring them back later |
CricTrack is the only scoring platform built around these realities.
- Register, login, password reset, profile update with photo
- Create groups with auto-generated 6-character invite codes
- Admin and member roles — manage your regular playing group
- Per-group career statistics completely isolated across groups
- Full status pipeline — Upcoming → Toss → Live → Innings Break → Completed
- Coin flip animation with bat/bowl choice
- Create upcoming matches in advance or start immediately
- Record every delivery — runs, wides, no-balls, byes, leg-byes, wickets
- Automatic strike rotation including end-of-over and odd-run edge cases
- Bench a batter mid-innings — retired not out, can return later
- Bench and replace — swap a batter atomically in a single socket event
- Undo last delivery with full state restoration including bench state
- Over break flow — bowler selection between every over
- Designate one player to appear in both teams within a single match
- Joker bats for their primary team and bowls for the opposition
- Career statistics tracked separately per innings — no double-counting
- Set or dissolve joker status at any over break
- Change total overs at any over break — increase or decrease freely
- Add players to either team mid-game — late arrivals handled natively
- Reshuffle players between teams at over break
- All changes reflected instantly on every connected device
- Real-time score, batting pair, current bowler, overs, run rate
- Ball-by-ball over chip display
- Second innings target, required run rate, balls remaining
- Shareable viewer link — opens live match feed in any browser
- Per-group batting career stats — runs, balls, strike rate, fours, sixes, 30s/50s/100s
- Per-group bowling career stats — overs, wickets, economy, 3W/4W/5W hauls, best figures
- Flip card per player — batting side and bowling side in one card
- Search and filter across your entire group player pool
- Full scorecard for both innings with fall of wickets
- Man of the Match scoring across batting and bowling contributions
- Top Performers — top scorer, top wickets, best economy, best strike rate
| Layer | Technology |
|---|---|
| Frontend | React 19, Vite 7, React Router DOM 7, Tailwind CSS 4 |
| Backend | Node.js, Express 4 |
| Real-time | Socket.IO 4 — WebSocket-only transport |
| Database | MongoDB Atlas via Mongoose 8 |
| Auth | JWT (15-day sessions), bcryptjs |
| Security | Helmet, express-rate-limit, Socket.IO JWT handshake auth |
| Deployment | Vercel (frontend) · Render (backend) |
Why embedded player snapshots in Match documents?
Avoids populate() on the hot scoring path. Every delivery handler reads and writes a single document — no joins, no risk of stale references mid-over.
Why GroupPlayerStats separate from Player?
The same player can have entirely different histories in different groups. A single global stats document would be meaningless — career stats are only useful within the context of a consistent group of players.
Why atomic bench+replace in one socket event?
Splitting bench and replace into two events creates a window where a delivery could be recorded between them, placing a ball with a vacant crease position. A single benchAndReplace event eliminates this race condition entirely.
Why the Joker is tracked as two playerStats entries?
One entry per team. Bowling stats route to the bowling-team entry, batting stats route to the batting-team entry. No special-casing needed downstream — all existing stat aggregation logic works without modification.
Why VersionError retry with exponential backoff on Socket.IO handlers? Concurrent delivery events from multiple clients (umpire + scorer) can hit the same Mongoose document version. Three retries at 20ms, 50ms, 80ms intervals resolve conflicts without a distributed lock, keeping the handler stateless.
Why deterministic stat recalculation at match end? Stats computed from the match record at completion, not accumulated live. Undos, corrections, and mid-match changes never produce drift — the final number is always exactly derivable from the timeline.
Why WebSocket-only transport? Long-polling disabled to eliminate the HTTP upgrade round-trip on mobile networks. Players score from their phones on the field — every millisecond of latency is felt.
cricket-tracker/
│
├── backend/
│ └── src/
│ ├── config/ # MongoDB Atlas connection
│ ├── controllers/ # Auth, match, player, group handlers
│ ├── middleware/ # JWT Bearer token verification
│ ├── models/
│ │ ├── Match.js # Embedded player snapshots
│ │ ├── GroupPlayerStats.js # Per-group career stats
│ │ ├── Group.js # Invite code, member roles
│ │ ├── Player.js
│ │ └── User.js
│ ├── routes/ # Express route definitions
│ ├── sockets/
│ │ └── matchSocket.js # All live match event handlers
│ ├── utils/
│ │ └── statsUpdater.js # Deterministic stat recalculation
│ └── server.js # Express + Socket.IO entry point
│
└── frontend/
└── src/
├── components/ # BottomSheet, OverBreakDrawer, BottomNav
├── context/ # AuthContext, ActiveGroupContext
├── hooks/ # usePageCache
├── pages/ # UmpireScorerPage, ScoreboardPage,
│ # PlayerProfilesPage (Dugout), HomePage
├── routes/ # RequireAuth guard
├── services/ # API client, Socket.IO client
└── utils/ # Match result calculation
- Node.js 18+
- MongoDB Atlas cluster (free tier works)
git clone https://github.com/Dev-06-06/cricket-tracker.git
cd cricket-trackercd backend
npm installCreate backend/.env:
JWT_SECRET=your_jwt_secret_here
PORT=5000
CLIENT_ORIGIN=http://localhost:5173
MONGODB_URI=your_mongodb_atlas_connection_stringnpm run dev
# Runs on http://localhost:5000cd frontend
npm installCreate frontend/.env.local:
VITE_API_BASE_URL=http://localhost:5000npm run dev
# Runs on http://localhost:5173| Variable | Purpose |
|---|---|
JWT_SECRET |
Secret key for signing JWTs |
PORT |
Server port (default: 5000) |
CLIENT_ORIGIN |
CORS allowed origin |
MONGODB_URI |
MongoDB Atlas connection string |
| Variable | Purpose |
|---|---|
VITE_API_BASE_URL |
Backend base URL |
| Method | Endpoint | Description |
|---|---|---|
| POST | /register |
Register new user |
| POST | /login |
Login |
| GET | /me |
Get current user |
| PUT | /profile |
Update name, email, photo |
| POST | /reset-password |
Reset password |
| Method | Endpoint | Description |
|---|---|---|
| POST | / |
Create group |
| GET | / |
List user's groups |
| POST | /join |
Join via invite code |
| POST | /:groupId/leave |
Leave group |
| GET | /:groupId/players |
List group players |
| POST | /:groupId/players |
Add player to group |
| DELETE | /:groupId/players/:id |
Remove player |
| Method | Endpoint | Description |
|---|---|---|
| POST | / |
Create match |
| GET | /live |
List live matches |
| GET | /upcoming |
List upcoming matches |
| GET | /completed |
List completed matches |
| GET | /:id |
Get match details |
| DELETE | /:id |
Delete match |
| Method | Endpoint | Description |
|---|---|---|
| GET | / |
List all players |
| POST | / |
Create player |
| GET | /by-group/:groupId |
Players with career stats |
| Event | Purpose |
|---|---|
joinMatch |
Join match room, receive full state |
tossResult |
Finalize toss |
setOpeners |
Set opening lineup |
setNewBatter |
Bring in batter after wicket or bench return |
setNewBowler |
Select bowler for new over |
umpire_update |
Record a delivery |
benchBatter |
Bench a batter |
benchAndReplace |
Bench and replace atomically |
overBreakCommit |
Commit over break — bowler, overs, players, joker |
swapStriker |
Manually rotate strike |
undo_delivery |
Undo last ball |
complete_match |
Mark match completed |
| Event | Purpose |
|---|---|
matchState |
Full match state broadcast |
overBreakStarted |
Over break begun |
inningsBreakStarted |
Second innings starting |
innings_complete |
First innings ended |
match_completed |
Final result and career stats updated |
groupMatchUpdate |
Group-level match status change |
matchError |
Error with message |
- Scorecard sharing as image export
- Push notifications for live match updates
- Tournament bracket and points table
- Match highlights reel — auto-generated from wickets and big hits
- DLS method for rain-affected matches
- Password reset is name + email match only — no OTP email flow yet
- No offline delivery queue — balls recorded during disconnect are lost on that session
- Scoreboard viewer requires login — public shareable link planned
Built with React · Node.js · Socket.IO · MongoDB Atlas
Deployed on Render + Vercel






