Développer un écosystème web full-stack : les défis concrets du projet Rezo Pouce
Crédit : Olivier Fillol
Pendant cinq ans, j’ai conçu, développé et maintenu seul l’écosystème technique de Rezo Pouce, un service de mobilité solidaire déployé sur plus de 3 000 communes en France. Cet écosystème comprend une API REST Symfony, un site public (Symfony / AngularJS), deux back-offices Angular et l’infrastructure serveur associée.
Les articles de ce portfolio détaillent chaque composant. Celui-ci traverse la stack de bout en bout pour montrer, à travers des exemples concrets, les compétences mobilisées au quotidien.
Construire une API REST consommée par plusieurs clients
L’API Symfony est le point central de l’écosystème. Elle est consommée par quatre clients distincts — le site public, le back-office d’administration, le back-office d’inscription et un service partenaire — chacun avec ses propres besoins en termes de données, de droits d’accès et de format de réponse.

Le défi n’est pas de construire une API — c’est de construire une API qui serve plusieurs clients sans multiplier les endpoints. Un même concept, comme un territoire, n’est pas exposé de la même manière selon qu’on l’affiche sur le site public (nom, carte, nombre de points d’arrêt) ou dans le back-office (configuration complète, gestionnaires, paramètres Séniors, statistiques). La réponse passe par des endpoints différenciés avec des niveaux de détail adaptés, une sérialisation conditionnelle selon le rôle de l’appelant, et une gestion fine des droits à chaque requête.
Côté Symfony, cela se traduit par une couche de services métier solide, découplée des contrôleurs. Les contrôleurs gèrent le HTTP — validation des entrées, sérialisation des sorties, codes de retour. Les services gèrent la logique — vérification des droits, calculs, coordination entre entités. Cette séparation, classique sur le papier, devient indispensable quand la même logique métier est sollicitée par des endpoints différents.
Gérer le géocodage multi-sources
Le géocodage — convertir une adresse en coordonnées GPS et inversement — est omniprésent dans le projet. Chaque point d’arrêt, chaque adresse de trajet, chaque commune doit être localisée précisément.
Le problème, c’est qu’aucun service de géocodage n’est fiable à 100 %. Google Maps est le plus précis mais coûte cher à l’usage. La Base Adresse Nationale (BAN) est gratuite et performante sur les adresses françaises, mais peut ne pas reconnaître certains lieux-dits. Nominatim (OpenStreetMap) offre une couverture mondiale mais avec une précision variable.
La solution retenue est un service de géocodage abstrait avec une stratégie de fallback : la requête est d’abord envoyée à la source principale, et en cas d’échec ou de résultat insuffisant, les sources secondaires prennent le relais. Ce pattern d’abstraction permet de changer de stratégie sans toucher au code des composants qui consomment le service — un point important quand Google change ses tarifs ou quand la BAN améliore sa couverture.
Côté front, le géocodage se manifeste de manière bidirectionnelle sur les fiches de points d’arrêt. Le gestionnaire peut déplacer un marqueur sur la carte Leaflet, ce qui déclenche un géocodage inverse pour mettre à jour l’adresse postale. Inversement, la modification de l’adresse dans le formulaire recalcule les coordonnées et repositionne le marqueur. Cette synchronisation en temps réel entre la carte et le formulaire repose sur des Observables RxJS qui coordonnent les événements de la carte (drag-end du marqueur) et les changements du formulaire (validation du champ adresse).
Intégrer des librairies tierces dans Angular
Un back-office métier ne se construit pas entièrement from scratch. L’espace d’administration intègre plusieurs librairies spécialisées : FullCalendar pour le calendrier des trajets, Leaflet pour la cartographie, Highcharts et Chart.js pour les graphiques statistiques, Quill pour l’édition de texte riche.
Chaque intégration pose ses propres défis. FullCalendar doit charger les trajets par périodes de trois mois pour éviter de saturer le navigateur avec des milliers d’événements — un chargement paresseux piloté par les callbacks de changement de vue. Leaflet doit cohabiter avec Angular Material dans des composants réutilisables — le même composant de carte sert dans la fiche d’un point d’arrêt, dans la vue d’une commune, dans le configurateur de fiches mobilité et dans la création de trajet. L’enjeu est d’encapsuler chaque librairie dans un composant Angular propre, avec des entrées et sorties typées, pour que le reste de l’application n’ait pas à connaître les détails d’implémentation de chaque librairie.
Le composant cartographique illustre bien cette approche. Il accepte en entrée une liste de marqueurs, des couches GeoJSON, un niveau de zoom. Il émet en sortie les événements de clic, de déplacement de marqueur, de changement de vue. Le clustering automatique des marqueurs gère les zones denses. Plusieurs fonds de carte sont disponibles — OpenStreetMap, vue aérienne, topographique. Tout cela est transparent pour les composants parents, qui manipulent des données métier sans se soucier de la carte.
Concevoir des formulaires complexes avec Reactive Forms
Le formulaire d’inscription des utilisateurs Séniors et le formulaire de création de trajet sont parmi les plus complexes de l’application. Le parcours d’inscription comporte quatre étapes avec validation progressive : identifiants avec vérification d’unicité en temps réel auprès de l’API, profil avec contraintes conditionnelles (un mineur ne peut pas être conducteur), éligibilité géographique avec autocomplétion de commune, et validation documentaire avec upload de pièces justificatives.
Le système de Reactive Forms d’Angular est conçu pour ces cas d’usage. Chaque étape est un FormGroup avec ses propres validateurs — synchrones pour les règles locales (format d’email, longueur de mot de passe), asynchrones pour les vérifications distantes (unicité de l’identifiant via l’API). Les validateurs croisés vérifient la cohérence entre champs : le type d’utilisateur influence les documents requis, la commune détermine l’éligibilité au service.
La création de trajet pose un défi similaire. Le formulaire en trois étapes gère deux trajets interdépendants — le retour pré-remplit ses adresses en inversant celles de l’aller, et son horaire se calcule automatiquement. L’autocomplétion des adresses propose l’historique du passager en plus des résultats de géocodage. À chaque changement d’adresse, la carte se met à jour avec le nouvel itinéraire calculé via Graphhopper, et les estimations de distance, durée et coût sont recalculées.
Sécuriser les échanges front/back
L’authentification repose sur des tokens JWT dont la gestion est entièrement centralisée par deux intercepteurs HTTP Angular.
L’intercepteur de requêtes injecte automatiquement le token dans chaque appel API. Il alimente également un service de suivi des requêtes actives — un Observable qui émet le nombre de requêtes en cours. Ce mécanisme sert deux objectifs : afficher un indicateur de chargement dans l’interface, et empêcher la fermeture accidentelle du navigateur pendant qu’une sauvegarde est en cours. Un détail, mais un détail qui évite la perte de données quand un référent ferme son onglet après avoir cliqué sur « Enregistrer » sans attendre la confirmation.
L’intercepteur de réponses gère les erreurs de manière centralisée. Une erreur 401 (token expiré) déclenche une déconnexion automatique et une redirection vers la page de connexion. Une erreur 403 (droits insuffisants) affiche une notification. Une erreur 500 affiche un message compréhensible. Sans cette centralisation, chaque composant devrait gérer ses propres cas d’erreur — une source de duplication et d’oublis.
Côté API, chaque endpoint vérifie les droits de l’appelant selon une hiérarchie de rôles à trois niveaux : rôle global (administrateur, consultant), rôle par domaine (gestionnaire Séniors, animateur territorial), et rôle contextuel (gestionnaire de ce territoire). Cette vérification en cascade garantit qu’un gestionnaire de territoire ne peut pas accéder aux données d’un territoire dont il n’a pas la charge — même en forgeant manuellement une requête.
Traiter les imports de données côté serveur
L’import CSV de points d’arrêt illustre une problématique classique du développement full-stack : recevoir un fichier depuis le front, le traiter côté serveur, et renvoyer un retour progressif au client.
Côté API Symfony, le traitement commence par les problèmes les plus fréquents : l’encodage du fichier (UTF-8, Latin-1, Windows-1252) et le séparateur de champs (virgule, point-virgule, tabulation). Ces deux paramètres, anodins en apparence, sont la première source d’échec en conditions réelles — les fichiers proviennent de tableurs variés, avec des configurations locales différentes. Le parser détecte l’encodage et tente d’identifier le séparateur avant de commencer le traitement.
Vient ensuite la validation : correspondance des en-têtes avec les champs attendus, puis validation ligne par ligne avec typage adaptatif — les booléens acceptent « oui », « vrai », « true » ou « 1 ». Les coordonnées géographiques fournies sont automatiquement géocodées pour obtenir l’adresse postale. Chaque erreur est collectée dans un rapport détaillé plutôt que de provoquer un arrêt immédiat, ce qui permet au gestionnaire de corriger son fichier en une seule passe.
Côté Angular, le composant d’import affiche la progression en temps réel et restitue le rapport d’erreurs de manière lisible. L’enjeu est de transformer un processus technique (parser un CSV, valider des données, insérer en base) en une expérience utilisable par une personne qui n’est pas développeur.
Générer des documents PDF à la volée
Les fiches mobilité — des PDF multi-pages présentant les points d’arrêt d’une commune — sont générées à la volée au moment où l’utilisateur les demande, garantissant des données toujours à jour.
Le configurateur intégré au back-office permet de construire chaque fiche commune par commune avec une prévisualisation en temps réel. Le gestionnaire ajuste le nombre de points d’arrêt par page, ajoute ou retire des pages, et insère des captures de carte. Ces captures sont réalisées directement depuis l’interface grâce à une conversion DOM-to-image du composant Leaflet — pas besoin de logiciel externe.
Côté serveur, la génération du PDF assemble les données (points d’arrêt, équipements, modes de transport), les paramètres de mise en page sauvegardés en JSON et les images capturées. Des paramètres par défaut permettent de produire une fiche exploitable même pour une commune non configurée manuellement. Le téléchargement en lot — toutes les fiches d’un territoire dans une archive ZIP — impose une gestion attentive de la mémoire et des temps de réponse.
Maintenir un projet sur cinq ans
Construire un système est une chose. Le faire vivre pendant cinq ans en est une autre. Les dépendances vieillissent — AngularJS a atteint sa fin de vie, Symfony 3.4 n’est plus supporté, Bootstrap 4 a été supplanté. Les besoins évoluent — chaque retour du terrain se traduit par un écran, un filtre, un export supplémentaire.
L’architecture modulaire d’Angular — chaque domaine fonctionnel dans son propre module, chargé en lazy loading — a permis d’absorber cette croissance sans refonte majeure. Les ajouts fonctionnels restent contenus dans leur module, sans effet de bord sur le reste de l’application. Côté API, la séparation entre contrôleurs et services métier permet de faire évoluer la logique sans toucher aux endpoints existants.
La dette technique s’accumule malgré tout. Savoir la gérer — identifier ce qui doit être corrigé maintenant et ce qui peut attendre, refactorer par petites touches plutôt que par grande refonte — fait partie du métier autant que savoir écrire du code.
Ce que ce projet représente
Cinq ans de développement full-stack en autonomie complète sur un projet en production. Pas un exercice académique, pas une maquette — un outil utilisé quotidiennement par des équipes sur le terrain, avec de vrais utilisateurs, de vraies contraintes de performance, de vraies données.
Ce projet m’a confronté à l’ensemble du spectre du développement web : concevoir une API et la faire consommer par plusieurs clients, construire des interfaces riches avec des librairies spécialisées, gérer l’authentification et les droits, traiter des fichiers, générer des documents, optimiser les performances, maintenir la cohérence d’un écosystème qui grandit. C’est cette expérience transversale que je mets aujourd’hui au service de nouveaux projets.
Les articles suivants détaillent chaque composant de l’écosystème :
- Série site public : architecture de l’écosystème, territoires et cartographie, authentification et inscription, Rezo Séniors
- Série back-offices : les deux applications Angular, architecture de l’administration, authentification et contrôle d’accès, gestion des territoires, pilotage Rezo Séniors, cartographie
- Série API : architecture de l’API, authentification et contrôle d’accès, service Rezo Séniors, géocodage, documents et import
Une question ou un projet ?
Je suis disponible pour en discuter.