Several applications, one shared record
The platform is not a single application, and it is not a microservice architecture either. It is a set of autonomous applications — each with its own Node.js/Express backend, its own Angular interface, its own deployment and its own authentication — built around a shared database that serves as the integration point.
- Federation back office — licences, transfers, clubs, people, documents.
- Competitions — entries, calendars, matches, results.
- Public CMS — the federation's own website, administered by the federation itself.
- News — a reduced variant of the CMS for federations that only need to publish.
The trade-off is deliberate: no backend ever makes an HTTP call to another backend. Each interface talks only to its own backend, and consistency comes from the data model rather than from a service mesh. That removes cascading latency, distributed transactions and chain failures — at the cost of coupling at the schema, which three rules offset: one single migration chain for the whole suite, identical query conventions in every project, and no foreign keys at all (referential integrity is held in application code, which keeps each application in charge of its own writes).
Tenant isolation is the starting point, not a feature
Each federation reaches the platform on its own subdomain. The current federation is then derived from the authentication token only — never from a URL parameter, request body or form field, all of which can be forged. Every read and every write is filtered on it, no query joins two federations, and an identifier belonging to another federation returns 404 rather than "access denied": the second answer would confirm the record exists.
Two identifiers per record
Every business object carries two keys: an internal key used only for joins and never exposed, and a federation-scoped sequential identifier assigned automatically by a database trigger from a dedicated counter. That second identifier is what users, URLs and the API see — so licence holder no. 1,042 exists independently in each federation, with short gap-free numbers, while the database stays single. The rule holds everywhere: the public identifier appears in output only, never in a filter or a join.
Permissions computed, not hard-coded
Five federal roles ship predefined — super administration, federal administration, federal read-only, club management, club read-only — but the permissions attached to each role are configurable per federation, and any user can be granted or denied individual permissions on top of their role. The whole set is computed at sign-in, carried in the token, then checked server-side on every route and in the interface so that only genuinely permitted actions are rendered.
The one shared service: files
Signed licences, supporting evidence, photographs, logos — every document is centralised in a single file service, and it is the only dependency any application has on another. It works at two levels of access:
- backends call it with HMAC-signed requests; the service holds no session and no user accounts, and trusts the signature and nothing else;
- browsers reach private documents through time-limited signed URLs, with no authentication token and without ever seeing a real storage path.
Three details that matter in practice: the MIME type is validated from the file's content rather than from the declared header; the storage path must contain the federation segment, so a coding mistake cannot write into another federation's folder; and uploads happen in two stages — temporary deposit, then confirmation — with a cleanup job for files never confirmed.
Six modules, switched on at the federation's pace
Federation management, licences and transfers, competitions and matches, training/camps and diplomas, marketing and website, finance and treasury. All six share one record: a licence holder entered once is the same person on a match sheet, in a training session and in an accounting entry. A federation can enable two and add the rest later, with no data migration.