Case study

Raith Rovers Scouting Platform

A high-performance full-stack web application for football club operations, combining tactical squad management with a deeply customizable, data-heavy scouting engine.

Raith Rovers Football Club · June 2025

Visit live site

Demo login

Use these credentials to sign in and explore the live demo.

Email
test@example.com
Password
password

Overview

This platform was built to streamline football club operations and modernize player scouting for Raith Rovers Football Club. The experience is built around the club’s branding: users manage the current squad, visualize tactical formations, and scout a global database of players.

The product is powered by Wyscout-style data, ingesting deep statistical profiles for thousands of players. Scouts can create custom, weighted attribute templates (Profiles) so the app goes beyond basic search and surfaces players who fit highly specific tactical requirements.

The challenge

The hardest constraint was keeping the UI responsive while querying a very large, complex dataset.

The application holds roughly 4,800 players across leagues. Wyscout-style data is stored as flexible, mixed documents in MongoDB, so each player record is dense—about 150 raw attributes, with 87 distinct statistical keys modeled in the weighting system. The database totals about 14.09 MB (9.83 MB storage size).

At first, complex queries—especially cross-referencing user-defined profile weights with nested statistical fields—were prohibitively slow. A typical deep filter took about 22 seconds, which made the search UI unusable in practice.

Choosing the stack

I chose Angular 19 with standalone components for a structured, scalable SPA with strong tooling for forms, tables, and complex state. Angular Material and the CDK covered accessible UI patterns; Tailwind filled in layout and fine-grained styling. RxJS fit naturally for debounced search, pagination, and combining async streams.

On the server, Node.js 20 and Express provided a straightforward REST layer with JWT authentication. MongoDB and Mongoose matched the semi-structured scouting payloads and evolving schema needs.

The frontend is hosted on Render, with CORS tuned so local development and production both work smoothly.

The architecture

The system splits into clear areas: authenticated club users interact with an Angular SPA that talks to a Node/Express API. MongoDB holds player documents, profiles, and cached scoring artifacts.

Dedicated API routes ingest and serve large payloads from external scouting providers—the backend is configured to accept bulk JSON updates on the order of tens of megabytes where needed.

Feature modules cover tactical squad management (sorting, filtering, formation assignment) and the scouting workspace (search, profiles, ranking), sharing consistent patterns for pagination and error handling.

What I built

The platform is organized into robust modules for club management and scouting:

  • Tactical squad management: dynamic squad views with sorting and filtering (position, age, contract expiry, injury/loan status), plus a visual formation view to map custom shapes and slot players into roles.
  • Granular player search: debounced inputs, dynamic sorting, and filters for age bands, physical metrics, minutes played, and more.
  • Custom scouting profiles: CRUD for weighted templates—scouts assign numeric weights across 87 per-90 and percentage attributes in nine categories (passing, shooting, defending, and others), with fine step sizes for low-volume metrics.
  • Wyscout-style data integration: API routes to ingest, store, and serve large-scale provider data, including support for bulk JSON payloads up to about 50MB for mass updates.

Together, these pieces form a single operational surface for squad planning and data-driven scouting—not a thin CRUD shell over a spreadsheet.

Performance & engineering decisions

The turning point was treating query performance as a first-class design problem rather than something to fix later.

I analyzed real query patterns and added 27 targeted MongoDB indexes—compound and text indexes aligned with the heaviest filters and sorts.

Relative scoring was another hotspot. Instead of recomputing how each player compares to the pool on every request, I introduced a caching layer for profile Z-scores: values are precomputed per profile with versioning so they invalidate cleanly when weights change.

That shifted serious work onto indexed MongoDB paths and precomputed fields instead of repeated application-side crunching over huge documents.

My role

I led development end to end: Angular application structure and UX for squad and scouting flows, Express APIs and authentication, MongoDB modeling, ingestion routes for provider data, and the indexing and caching work that made search viable at this data size.

Outcome

Deep-filter operations dropped from about 22 seconds to roughly 3–4 seconds. Network traces on typical paginated search requests with limits now often finish in under two seconds (for example about 1.14s–1.81s), so the product feels like a professional scouting tool instead of a bottlenecked prototype.

Tech stack

  • Angular 19 (standalone components)
  • Angular Material + CDK
  • Tailwind CSS
  • RxJS
  • Node.js 20
  • Express
  • JWT authentication
  • MongoDB
  • Mongoose
  • Render (frontend hosting)
  • CORS for local and production environments

Up next

View all case studies