Popixa
← Toutes les actualités
Développement21 février 2026

Bus Tracker 2 – Architecture et analyse technique d'un système de suivi de transport en temps réel

Bus Tracker 2 est un projet open source de bonne facture technique, qui illustre comment construire un système de données temps réel robuste avec un stack TypeScript homogène.

Bus Tracker 2 – Architecture et analyse technique d'un système de suivi de transport en temps réel

Projet open source · TypeScript · Node.js · React · Redis · PostgreSQL
Source : github.com/kevinbioj/bus-tracker-2

Introduction

Bus Tracker est une application web open source permettant de suivre en temps réel les véhicules de transport en commun à travers la France. Elle s'appuie exclusivement sur l'open data publiée par les autorités organisatrices de transport (AOT) et les opérateurs. Depuis mars 2025, le projet couvre la majorité des grands réseaux urbains français et traque plus de 8 000 véhicules quotidiennement.

Cet article analyse l'architecture technique du projet dans sa version 2, entièrement réécrite fin 2024, et met en lumière les choix de conception, les technologies employées et les patterns d'ingénierie notables.

Vue d'ensemble de l'architecture

La version 2 adopte une architecture orientée événements reposant sur Redis comme bus de messages (pub/sub). Le système est découpé en trois couches distinctes :

  • Les providers : processus indépendants qui consomment les données de transport (GTFS-RT, APIs propriétaires) et publient des lots de données vers Redis.
  • Le serveur (core) : agrège les données reçues des providers, les normalise, les stocke, et les expose aux clients via une API REST.
  • Le client : application React qui consomme l'API et affiche les véhicules sur une carte en temps réel.

Cette séparation stricte permet de faire évoluer chaque composant indépendamment et de brancher de nouveaux providers sans modifier le cœur de l'application.

Providers ──(Redis pub/sub)──► Server ──(HTTP/REST)──► Client (React)
│ │
│ PostgreSQL
│ (persistance)
└── GTFS-RT, APIs propriétaires, ...

Monorepo et outillage

Le projet est structuré en monorepo pnpm avec workspaces, organisé selon la convention suivante :

bus-tracker-2/
├── apps/
│ ├── server/ # Backend Node.js (Hono)
│ └── client/ # Frontend React
├── libraries/
│ └── contracts/ # Types et schémas partagés (Zod)
└── providers/
├── gtfs/ # Processeur GTFS et GTFS-RT
├── flowly/ # Provider Flowly (Île-de-France)
├── hawk/ # Provider Hawk
├── idelis/ # Provider Idelis
├── rtm/ # Régie des Transports Métropolitains
├── twisto/ # Twisto (Caen)
└── dkbus/ # DK'Bus (Dunkerque)

L'outillage de développement s'appuie sur Biome pour le linting et le formatage (remplaçant ESLint + Prettier), et SonarCloud pour l'analyse continue de la qualité du code. Les tests unitaires des providers GTFS utilisent Vitest.

La bibliothèque contracts : contrat de données partagé

Le package @bus-tracker/contracts est un élément central de l'architecture. Il définit, via Zod, l'ensemble des types et schémas de données qui transitent entre providers et serveur. C'est le seul point de couplage entre ces deux parties du système.

Le schéma principal, vehicleJourneySchema, modélise une course en temps réel :

export const vehicleJourneySchema = z.object({
id: z.string(),
line: vehicleJourneyLineSchema.optional(),
direction: z.enum(["OUTBOUND", "INBOUND"]).optional(),
destination: z.string().optional(),
calls: z.array(vehicleJourneyCallSchema).optional(),
position: vehicleJourneyPositionSchema,
occupancy: vehicleJourneyOccupancyEnum.optional(),
networkRef: z.string(),
journeyRef: z.string().optional(),
vehicleRef: z.string().optional(),
serviceDate: z.string().date().optional(),
updatedAt: z.string().datetime(),
});

Ce schéma capture les données essentielles d'un véhicule en service : sa position GPS (avec optionnellement le cap et un indicateur d'arrêt), les arrêts desservis avec horaires théoriques et estimés (calls), le taux d'occupation, et des références permettant de lier le trajet à une ligne, un opérateur et un réseau.

Les types de lignes supportés (vehicleJourneyLineTypes) couvrent l'ensemble des modes de transport : tramway, métro, rail, trolleybus, funiculaire, bus, ferry, car. Cette richesse permet à Bus Tracker de gérer des réseaux multimodaux comme Île-de-France Mobilités ou la SNCF.

Le serveur : agrégation et exposition des données

Stack technique

Le serveur est une application Node.js (ESM) écrite en TypeScript, utilisant le framework Hono — un framework HTTP minimaliste et performant, initialement conçu pour les edge runtimes mais parfaitement adapté à Node.js. Hono gère les middlewares CORS, le logging, la gestion de sessions (via cookies chiffrés) et le routage.

import { Hono } from "hono";
import { cors } from "hono/cors";
import { sessionMiddleware } from "hono-sessions";

export const hono = new Hono<HonoWithSession>();
hono.use(cors({ origin: "*" }));
hono.use("*", sessionMiddleware({ ... }));
hono.route("/v2", v2app);

La gestion de la persistance s'effectue via Drizzle ORM avec PostgreSQL — un ORM TypeScript-first, léger et orienté "SQL-like", qui génère les migrations automatiquement à partir des schémas de table.

Gestion du store en mémoire et cycle de vie des courses

Le serveur maintient un journeyStore en mémoire (Map) qui contient l'état courant de toutes les courses actives. Chaque lot de données reçu depuis Redis déclenche la fonction handleVehicleBatch, qui effectue plusieurs opérations :

  1. Groupement par réseau : les courses sont regroupées par networkRef pour traiter chaque réseau de manière cohérente.
  2. Import/résolution des entités : lignes et véhicules sont résolus ou créés en base de données via des fonctions importLines, importNetwork, importVehicles. Cette couche d'import assure la déduplication et la persistance des entités métier.
  3. Filtrage temporel : toute course dont le dernier update remonte à plus de 10 minutes est ignorée, garantissant que le store ne contient que des données fraîches.
  4. Enregistrement des activités : les voyages associés à un véhicule identifié sont enregistrés en base via registerActivities, alimentant l'historique.

export async function handleVehicleBatch(vehicleJourneys: VehicleJourney[]) {
const now = Temporal.Now.instant();
const vehicleJourneysByNetwork = Map.groupBy(
vehicleJourneys,
(vj) => vj.networkRef
);
// ...
}

On remarque l'utilisation de Temporal (via temporal-polyfill), l'API de gestion du temps proposée pour succéder à Date en JavaScript. Ce choix illustre une volonté de modernité et de robustesse dans la manipulation des dates et fuseaux horaires — un domaine particulièrement critique pour les données de transport.

API REST v2

L'API expose plusieurs ressources : networks, regions, lines, vehicles, vehicle-journeys, announcements, users. La sécurisation est assurée par un middleware d'authentification basé sur les sessions et par un mécanisme d'OAuth (via @hono/oauth-providers). La validation des paramètres d'entrée s'effectue avec Zod.

Les providers : traduction des données ouvertes

Les providers sont des processus Node.js autonomes, chacun responsable d'un réseau ou d'un standard de données. Ils partagent tous le même pattern : consommer une source de données, la traduire vers le format VehicleJourney, et publier le résultat dans Redis.

Le provider GTFS

Le provider @bus-tracker/processor-gtfs est le plus important : il prend en charge le standard GTFS (General Transit Feed Specification) et GTFS-RT (GTFS Realtime), les formats ouverts les plus répandus dans le transport public français et mondial.

Il utilise notamment :

  • gtfs-realtime-bindings : décodage des messages protobuf GTFS-RT
  • papaparse : parsing efficace des fichiers CSV statiques GTFS
  • decompress : extraction des archives ZIP des feeds GTFS
  • croner : planification des rafraîchissements périodiques
  • p-limit : limitation de la concurrence des requêtes HTTP

Le provider est configurable par des fichiers .mjs permettant de paramétrer les URLs des feeds, les intervalles de rafraîchissement, et les éventuelles transformations spécifiques à chaque réseau.

Les providers propriétaires

Les providers flowly, hawk, idelis, rtm, twisto, dkbus gèrent les APIs spécifiques de certains opérateurs qui ne publient pas en GTFS-RT standard. Ils appliquent les mêmes principes : récupérer, normaliser, publier.

Le client : cartographie temps réel

Le frontend est une application React 19 (avec le nouveau compilateur React) bundlée via Rolldown-Vite (le successeur de Vite basé sur le bundler Rolldown, encore expérimental en 2025). Le stylisme repose sur Tailwind CSS v4.

Cartographie

La carte utilise deux bibliothèques en parallèle : Leaflet (via react-leaflet) et MapLibre GL (via react-map-gl). Cette dualité suggère une transition en cours vers MapLibre pour bénéficier du rendu WebGL et des performances associées sur de grands volumes de marqueurs.

Composants et librairies notables

  • Radix UI : composants headless accessibles (accordéons, dialogues, menus déroulants, tooltips) — une base solide pour construire une UI accessible sans sacrifier la flexibilité stylistique.
  • TanStack Query : gestion du cache et des requêtes asynchrones côté client.
  • TanStack Virtual : virtualisation des listes, critique pour afficher des milliers de véhicules sans dégradation des performances.
  • React Hook Form + Zod : formulaires avec validation côté client, partageant les mêmes schémas que le serveur.
  • PostHog : analytics produit intégrées directement dans le client.
  • ts-pattern : pattern matching expressif, utilisé également côté serveur.

Progressive Web App

La configuration inclut vite-plugin-pwa pour transformer l'application en PWA installable sur mobile — un détail important pour une app de suivi de transport utilisée en mobilité.

Points remarquables et choix d'ingénierie

Contrat fort entre composants via Zod : le fait de centraliser les schémas dans une bibliothèque partagée garantit la cohérence des données à la frontière providers/serveur, avec validation runtime en plus de la vérification statique TypeScript.

Architecture pub/sub découplée : les providers ne connaissent pas l'existence du serveur. Cette indirection via Redis facilite l'ajout de nouveaux providers, la montée en charge horizontale, et la résilience (un provider peut tomber sans affecter les autres ni le serveur).

Temporal API : l'adoption de temporal-polyfill pour toutes les manipulations de dates est un choix technique courageux qui simplifie la gestion des fuseaux horaires, particulièrement épineuse avec l'ancienne API Date.

Typage bout-en-bout : TypeScript est utilisé dans chaque package du monorepo, avec le pattern ts-pattern pour le pattern matching exhaustif. L'inférence des types depuis les schémas Zod (z.infer<typeof schema>) évite toute duplication de définitions.

Gestion de l'obsolescence des données : le seuil de 10 minutes dans handleVehicleBatch pour ignorer les courses trop anciennes est une décision de design explicite qui garantit la fraîcheur des données affichées, au prix d'une éventuelle absence temporaire de certains véhicules lors d'interruptions réseau.

Conclusion

Bus Tracker 2 est un projet open source de bonne facture technique, qui illustre comment construire un système de données temps réel robuste avec un stack TypeScript homogène. L'architecture pub/sub découplée, le contrat de données centralisé via Zod, et l'organisation monorepo en font un bon exemple de patterns modernes appliqués à un cas d'usage concret : rendre les données de transport public accessibles au grand public, en temps réel, à l'échelle de dizaines de réseaux français.

Site : https://bus-tracker.fr/

Article rédigé à partir de l'analyse du code source disponible sur github.com/kevinbioj/bus-tracker-2 · Février 2026

Un projet en tête ?

Parlons-en. Premier échange sans engagement.

Nous contacter →