Étude de cas full-stack
Tournwa
Le backend qui propulse Tournwa, une plateforme sportive émiratie — un moteur de compétition à quatre formats, du tirage au sort aux classements de ligue, des frais d'inscription via Stripe Connect, et l'inscription des athlètes en solo, en équipe ou en double.
- Rôle
- Lead Développeur Backend
- Durée
- 8 mois — en cours
- Année
- 2026
- Stack
- Node.js · Express.js · PostgreSQL · Redis · Stripe
- En ligne
- tournnwa.com

01 / Le problème
Une vraie compétition demande plus qu'un formulaire d'inscription.
Les organisateurs de compétitions sportives structurées ont besoin de catégories, de tirage au sort, de phases de groupes, de tableaux à élimination directe, de scores par set et de points de ligue — plus des frais d'inscription, des validations, des limites de capacité et le consentement parental pour les mineurs. Les athlètes doivent trouver les événements et s'inscrire en solo, en équipe ou avec un partenaire de double. Aucun tableur ne survit à ça.
Utilisateurs cibles
Trois rôles : les organisateurs — clubs et académies qui gèrent tournois, entraînements et matchs libres ; les athlètes, y compris les mineurs derrière des parcours de consentement parental ; et les admins de la plateforme.
02 / La solution
Un seul backend, trois applications.
Une seule API Express servant trois surfaces — une app mobile athlète, une app web organisateur et un panneau d'administration — à travers 338 endpoints répartis en 31 groupes de routes. Le moteur de compétition porte une catégorie du tirage au sort jusqu'au règlement des points en passant par les groupes et les tableaux ; Stripe Connect achemine les frais d'inscription vers les organisateurs premium, la commission de la plateforme étant prélevée automatiquement par événement ; et cinq files d'attente en arrière-plan gardent SMS, e-mails et notifications push hors du chemin de la requête.
Pourquoi cette stack
- PostgreSQL — index uniques partiels, verrous de ligne et transactions protègent l'argent
- Redis — cache, liste noire de JWT, stockage d'OTP et limites de débit d'un seul tenant
- BullMQ — cinq files gardent Twilio, SMTP et Expo push hors du chemin de la requête
- Cluster Node — workers multi-cœurs avec pools de connexions budgétés par worker
03 / Fonctionnalités
Ce que la plateforme fait.
Auth, quatre façons
E-mail, Google, Apple et OTP par téléphone avec liste noire de tokens Redis
Moteur de compétition
Tirage, groupes, tableaux, scores par set et classements départagés
Stripe Connect
Paiements à destination avec commissions par événement et versements
Inscription flexible
Solo, équipe ou double — avec consentement parental pour les mineurs
Points de ligue
Modèles placement-vers-points réglés dans un registre en ajout seul
Espaces organisateurs
Communautés privées qui ne fuitent jamais dans les listes publiques
04 / Défis techniques
Les parties difficiles — et comment elles ont cédé.
Un moteur de compétition à quatre formats
Élimination directe, round robin à un ou plusieurs groupes, et groupes puis phase finale en deux temps — avec des exemptions, des méthodes de tirage, des matchs pour la troisième place, et des règles qui doivent se figer dès le coup d'envoi.
Comment je l'ai résolu
Les tableaux sont complétés jusqu'à la puissance de deux supérieure, les têtes de série ordonnées par un miroir récursif 1-contre-N et les arêtes gagnant/perdant précâblées, si bien que terminer un match propage les résultats mécaniquement ; les exemptions se résolvent d'elles-mêmes à la génération. Les groupes sont répartis en serpentin et planifiés par la méthode du cercle. Des gardes imposent des statuts de match irréversibles et figent les réglages de la compétition dès qu'un tirage existe — chaque mutation transactionnelle, l'invalidation du cache différée après le commit.
Des paiements à l'épreuve des courses critiques
La finalisation peut arriver deux fois — webhook et sondage client —, les utilisateurs soumettent deux fois leurs intentions, et un paiement ne doit jamais valider deux fois une inscription ni réserver deux fois une place.
Comment je l'ai résolu
Deux index uniques partiels n'autorisent qu'un seul paiement actif et un seul paiement terminé par inscription ; la finalisation prend un verrou de ligne, ressort immédiatement si c'est déjà fait, puis valide l'inscription, réserve la place et active les participants en une seule transaction. Les clés d'idempotence Stripe dérivent d'identifiants internes, et les e-mails de reçu sont doublement protégés par des ID de job dédupliqués et une mise à jour conditionnée à un champ nul.
Des points réglés exactement une fois
À la fin d'un tournoi, les placements doivent se convertir en points de ligue exactement une fois par catégorie, même en cas de relance — tout en restant corrigeables si l'organisateur modifie ensuite ses modèles de points.
Comment je l'ai résolu
Le règlement dérive les placements selon le format et saute toute catégorie ayant déjà une écriture au registre pour ce tournoi. Les corrections comparent les totaux souhaités à la somme du registre et écrivent des lignes compensatoires — l'historique n'est jamais modifié ni supprimé. Les classements sont ordonnés en SQL avec partage des égalités, derrière un cache de cinq minutes indexé par version.
05 / Architecture
Comment le système s'assemble.
THREE CLIENT APPS
mobile athlète · web organisateur · admin
EXPRESS 5 API
338 endpoints · JWT + RBAC · cluster
POSTGRESQL
~60 modèles · transactions · verrous de ligne
STRIPE CONNECT
frais d'inscription & versements
REDIS + BULLMQ
cache · OTP · cinq files
TWILIO · SMTP · EXPO
sms · e-mail · push
CLOUDFLARE R2
téléversements média présignés
Services externes
06 / Captures d'écran
Le produit, de près.


07 / Résultats
Ce que ça a changé concrètement.
- Endpoints d'API
- 0
- Cas de test automatisés
- 0
- Modèles de données
- 0+
- Files d'arrière-plan
- 0
Une API en production derrière une app mobile athlète, une app web organisateur et un panneau d'administration. Les organisateurs mènent des compétitions complètes — tirage, groupes, tableaux, scores, classements — pendant que les frais d'inscription se règlent via Stripe Connect, la commission de la plateforme étant prélevée automatiquement à chaque événement. Les chemins de l'argent et les chemins critiques du moteur sont couverts par une suite Jest de 29 fichiers.
08 / Stack technique
Construit avec.
Frameworks
- Node.js
- Express 5
- Sequelize
- Jest
Bibliothèques
- BullMQ
- Stripe
- ioredis
- Joi
- Twilio
- Expo Push
Infrastructure
- PostgreSQL
- Redis
- Railway
- Cloudflare R2
- Stripe Connect
09 / Ma contribution
Ce que j'ai construit, précisément.
- Conçu et développé le moteur de compétition : quatre combinaisons de formats, tirage avec résolution automatique des exemptions, répartition en serpentin, planification par la méthode du cercle et classements avec départages récursifs.
- Développé le système de paiement Stripe Connect : deux modes de versement, idempotence par index uniques partiels et verrous de ligne, et finalisation pilotée par webhook qui valide et réserve en une transaction.
- Développé le règlement idempotent des points de ligue avec un registre de corrections en ajout seul et des classements ordonnés en SQL avec partage des égalités.
- Développé les Espaces organisateurs — communautés privées cloisonnées par en-tête avec isolation stricte de la visibilité, vérifiée par des tests de fuite dédiés.
- Développé la couche opérationnelle : serveur en cluster avec arrêt gracieux, cache Redis à clé de version, cinq workers BullMQ dont un balayage de rappels idempotent, limitation de débit, et la suite de 186 tests. (Coéquipier : intégration de l'auth sociale et CRUD de gestion d'équipes.)