Technical case study – December 2025
1. Context and project origin
Four months ago, a regional volleyball referee (Centre-Val de Loire) shared a common pain point among amateur clubs:
- Match scoring is still done on paper or whiteboards
- Sheets are sent by photo or retyped into a spreadsheet
- License tracking is approximate (shared files + manual reminders)
- No reliable traceability for player evaluations
The goal was not to build a national or commercial product, but to ship a tool that referees, coaches, players, and club admins would actually adopt.

2. Main features delivered (MVP – 2 months)
- Sign-up and login with role selection (player – referee – coach – club admin)
- License status view (valid / expired / pending) + pre-expiry alert
- Real-time scoring during matches (scores + qualitative player evaluation)
- Dual evaluation grid: technical performance + sportsmanship
- Secure, filtered stats browsing per user role
- Self-service, full account deletion (GDPR and App Store requirement)
3. Architecture
Type: cross-platform mobile app + REST API backend
| Layer | Technology | Primary rationale |
|---|---|---|
| Mobile frontend | React Native + Expo SDK | Fast iOS/Android delivery, OTA updates, simplified build |
| Backend | Node.js + Prisma | Flexibility, strong typing, automatic OpenAPI |
| Database | PostgreSQL 16 | Integrity constraints, JSONB, solid performance |
| Authentication | Firebase Authentication | Delegated identity, native JWT, MFA support |
| Notifications | Expo notifications | Cross-platform reliability, native Auth integration |
| Hosting | Google Kubernetes Engine (GKE) | Horizontal scale, managed ops, high availability |
Architecture separates:
- business logic
- role-based access control (RBAC)
- data persistence
Global architecture diagram
graph TD
A[Mobile - React Native + Expo] -->|HTTPS + Firebase JWT| B[API FastAPI]
B -->|JWT verification + RBAC| C[PostgreSQL]
subgraph Backend
B
D[RBAC middleware]
E[Scoring service]
F[License service]
B --> D
D --> E
D --> F
end
subgraph Database
C --> G[Users + Roles]
C --> H[Matches]
C --> I[Ratings - performance + behavior]
C --> J[Licenses]
end
K[FCM - Notifications] -->|push| B
B -->|push| K
L[Club Admin] -->|license management| B
B -->|license management| L
M[GKE - Kubernetes] -->|Orchestration| B
N[Firebase Auth] -->|Authentication| A
A -->|Authentication| N
4. Key technical points
4.1 Relational model (simplified, anonymized extract)
-- Core tables (anonymized)
CREATE TABLE users (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
email text UNIQUE NOT NULL,
role text NOT NULL CHECK (role IN ('player','referee','coach','administrator')),
club_id uuid,
created_at timestamptz DEFAULT now()
);
CREATE TABLE licenses (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid REFERENCES users(id) ON DELETE CASCADE,
status text NOT NULL,
expires_at date NOT NULL
);
CREATE TABLE matches (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
date timestamptz NOT NULL,
club_id uuid
);
CREATE TABLE ratings (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
match_id uuid REFERENCES matches(id),
player_id uuid REFERENCES users(id),
score_technique integer CHECK (score_technique BETWEEN 1 AND 5),
score_comportement integer CHECK (score_comportement BETWEEN 1 AND 5),
created_by uuid REFERENCES users(id)
);
4.2 Role-based access control (conceptual extract)
// middleware/requireRole.js
const requireRole = (...allowedRoles) => {
return (req, res, next) => {
const user = req.user
if (!user || !user.role) {
return res.status(401).json({ error: 'Not authenticated' })
}
if (!allowedRoles.includes(user.role)) {
return res.status(403).json({ error: 'Forbidden for this role' })
}
next()
}
}
module.exports = requireRole
5. Main challenges and solutions
- Concurrent input on the same match → lightweight lock + version check
- In-match UX friction for referees → max 3 screens, instant visual feedback
- Fine-grained permissions → centralized RBAC + exhaustive tests
- Push notification reliability → FCM plus email fallback
- GDPR / App Review compliance → self-service deletion + anonymization
6. Lessons learned (engineer viewpoint)
- Poor relational modeling is far costlier to fix later than to get right upfront
- Role management looks simple but quickly becomes the most complex component
- A referee mid-match tolerates zero latency and zero friction
- Cross-platform push notifications remain a recurring weak spot
- Thinking about traceability and audit from day one prevents trust crises
7. Current status & next steps
Status: v1.0 submitted to the App Store (December 2025)
Pilot clubs: 21 clubs in Centre-Val de Loire (~25 active users)
Next steps being considered:
- PDF / CSV export of match reports
- Multi-club support for district committees
- Optional real-time collaborative sync (via Firebase or PostgreSQL LISTEN/NOTIFY)
Thank you for reading.
