Basango
Un pipeline peu coûteux pour collecter et classer l’actualité des médias congolais

J’ai commencé Basango parce que suivre la guerre et la situation sécuritaire dans l’est de la RDC demandait de consulter de nombreuses sources à la main. Le projet collecte des articles auprès de médias congolais sélectionnés, les ramène à un même modèle de données, supprime les doublons, attribue des sujets et rend le résultat consultable.
Télécharger les pages est la partie simple. Les sources utilisent des systèmes de publication différents, leurs métadonnées sont inégales et un même sujet peut apparaître plusieurs fois. Basango traite ce travail comme un pipeline qui peut fonctionner en continu avec un budget d’exploitation limité.
Le problème
L’information existe. Trouver chaque jour quelques articles pertinents, publiés par des sources qui méritent d’être vérifiées, reste un travail manuel.
La République démocratique du Congo compte de nombreux médias en ligne, comptes sur les réseaux sociaux et canaux informels qui publient des informations politiques, économiques, sociales, culturelles, environnementales et sécuritaires. Certaines sources sont reconnues et régulières. D’autres sont peu fiables, politiquement orientées, mal structurées ou difficiles à évaluer.
Suivre manuellement un sujet précis, comme le conflit dans l’est de la RDC, impose de naviguer entre plusieurs sites d’actualité, fils Twitter/X et résultats de recherche. Chaque jour, il faut filtrer les éléments pertinents, comparer les sources, éliminer les doublons, repérer le bruit et déterminer quels articles sont suffisamment crédibles pour être utilisés.
Dans la pratique, rester informé devient un travail quotidien de recherche manuelle d’information.
Les lecteurs RSS et les agrégateurs de flux résolvent une partie du problème. RSS fournit aux applications un moyen standardisé de suivre les mises à jour des sites sans les consulter manuellement. Cependant, de nombreux médias congolais ne proposent pas de flux RSS exploitable, ne permettent pas de s’abonner par sujet ou publient un contenu trop large pour assurer un suivi ciblé.
Des outils généralistes comme Feedly peuvent agréger des sources, mais ils ne répondent pas de manière fiable à la question centrale de Basango :
Que se passe-t-il actuellement, d’après des sources crédibles, sur les sujets qui m’intéressent ?
C’est ce qui a transformé Basango, initialement pensé comme une simple application d’actualité, en système de recherche et de curation d’information.
Dans Basango, la recherche d’information consiste à collecter les articles de sources sélectionnées, à les normaliser dans une structure commune, à les classer par sujet et à permettre leur recherche, leur filtrage puis, à terme, leur recommandation.
Orientation du produit
Basango est parti d’une idée simple :
Construire un agrégateur d’actualités congolaises.
Cette idée s’est vite révélée trop limitée. Pour être utile, le système devait faire davantage que collecter des liens. Il devait :
- collecter des articles provenant de sources légitimes sélectionnées ;
- prendre en charge différentes structures de sites Web ;
- normaliser les données des articles dans un format cohérent ;
- classer les articles par sujet ;
- réduire les doublons et le bruit informationnel ;
- permettre la recherche et le filtrage ;
- préparer le jeu de données pour la recherche sémantique et la recommandation ;
- rester suffisamment économique pour fonctionner en continu ;
- rendre les actualités traitées accessibles dans des applications Web et mobiles.
Il fallait un pipeline peu coûteux capable de gérer plusieurs structures de sites sans coder tout le parcours pour chaque source. Les mêmes données devaient aussi servir à la recherche, aux rapports et à de futurs essais de recommandation.
Architecture du système
Basango utilise quatre parties :
- Collecteur : collecte et normalise les articles
- Base de données : stocke, déduplique, indexe et classe les données
- API : valide les entrées, authentifie les utilisateurs et expose les capacités du backend
- Clients : donnent accès aux informations sélectionnées depuis le tableau de bord et l’application mobile
Le collecteur gère les différences entre les sources. PostgreSQL stocke, déduplique, classe et indexe les articles. L’API valide les requêtes et applique l’authentification. Le tableau de bord et l’application mobile lisent les données traitées.
Moteur de collecte
Le collecteur est le premier composant technique majeur de Basango. Son rôle est de transformer un ensemble fragmenté de sites d’actualité congolais en un flux unifié d’articles structurés.
Les premières recherches ont montré qu’une seule stratégie d’extraction ne suffisait pas pour toutes les sources. Certains sites reposaient sur WordPress et exposaient des données JSON structurées au moyen de l’API REST WordPress, généralement disponible sous /wp-json. Les publications, dates, titres, liens et métadonnées pouvaient ainsi être récupérés dans un format lisible par machine.
D’autres sites ne proposaient aucune API exploitable. Pour ces sources, Basango devait analyser directement le HTML, extraire les liens depuis les pages de liste, visiter les pages détaillées et récupérer des champs comme le titre, le corps, la date de publication, les catégories et l’URL canonique.
Le téléchargement des pages était simple. La prise en charge des sources fondées sur une API et de celles fondées sur le HTML, sans dupliquer le collecteur pour chaque média, demandait un moteur commun.
Objectif de conception
Le collecteur suit une règle :
Les comportements propres à chaque source doivent résider dans la configuration, tandis que le moteur de collecte doit rester générique.
Au lieu d’écrire une classe de collecte spécifique pour chaque média, Basango définit un schéma de configuration des sources. Chaque source déclare son identifiant, son URL de base, son type, sa stratégie de pagination, son format de date, sa prise en charge des catégories, ses besoins de limitation du débit, son mode de récupération des détails et, pour les sources HTML, les sélecteurs CSS nécessaires à l’extraction des articles.
L’ajout ou la réparation d’une source modifie sa configuration. La séquence de collecte reste identique.
Abstraction des analyseurs
Basango utilise deux implémentations concrètes d’analyseur derrière un modèle de collecte commun :
-
HtmlCrawlersert pour les sites dont les articles doivent être extraits du HTML à l’aide de sélecteurs. -
WordPressCrawlersert pour les sites qui exposent des points de terminaison REST WordPress, où les listes d’articles et leurs métadonnées peuvent être récupérées au format JSON.
L’analyseur est sélectionné depuis la configuration de la source au moyen du champ sourceKind :
La valeur sourceKind sélectionne l’analyseur à l’exécution.
Collecte pilotée par la configuration
Le collecteur valide la configuration avec Zod avant l’exécution. Un sélecteur cassé, une URL invalide, un type de source inconnu ou un format de date incorrect arrête le démarrage au lieu d’altérer les articles.
Les sources HTML nécessitent des sélecteurs explicites, car chaque site possède sa propre structure :
{
"articleBody": ".field-name-body",
"articleCategories": ".views-field-field-cat-gorie a",
"articleDate": "head > meta[property=\"article:published_time\"]",
"articleLink": ".views-field-title a",
"articles": ".view-content > .views-row.content-row",
"articleTitle": "h1.page-header",
"pagination": "ul.pagination > li.pager-last > a"
}Par exemple, une source peut présenter ses cartes d’articles sous :
.view-content > .views-row.content-rowtandis qu’une autre peut utiliser :
.for_aitems > .article_other_itemLe collecteur n’a pas besoin de connaître ces détails. Il consomme uniquement la configuration normalisée.
Les sources WordPress nécessitent moins de configuration d’extraction, car leur API REST publique expose déjà un contenu structuré :
{
"sourceId": "example.com",
"sourceKind": "wordpress",
"sourceUrl": "https://example.com"
}L’API WordPress évite les sélecteurs propres à ces sources.
Flux de sélection de l’analyseur
Flux de collecte
La collecte d’une source se divise en trois étapes indépendantes :
- Découvrir les liens des articles
- Récupérer le détail des articles
- Conserver ou transmettre l’article normalisé
Les pages de liste découvrent les liens avec peu de requêtes. Les tâches de détail font une requête par article et analysent davantage de HTML. Une étape de persistance séparée peut relancer le stockage sans répéter la découverte.
Exécution synchrone et asynchrone
Basango prend en charge la collecte synchrone et asynchrone.
Le mode synchrone traite une source depuis la ligne de commande, sans worker Redis. Je l’utilise pour tester les sélecteurs, la pagination, les dates et les champs normalisés pendant l’ajout d’une source.
Le mode asynchrone permet de faire évoluer la collecte. Basango utilise BullMQ avec Redis pour répartir le processus en tâches d’arrière-plan.
La chaîne asynchrone sépare la découverte des listes de l’extraction du détail des articles :
Lorsque les requêtes de détail deviennent le goulot d’étranglement, le moteur peut ajouter des workers d’articles sans ajouter de workers de liste.
QueueManager n’expose que les opérations utilisées par le collecteur :
enqueueListing(payload)
enqueueArticle(payload)
iterQueueNames()
queueName(suffix)
close()Les détails d’implémentation propres à BullMQ restent ainsi invisibles au reste du collecteur.
Stratégie de persistance
Le collecteur conserve les articles au moyen d’une interface Persistor :
interface Persistor {
persist(record: Partial<Article>): Promise<void> | void;
close(): Promise<void> | void;
}La première implémentation concrète est JsonlPersistor, qui écrit les articles au format JSON délimité par des sauts de ligne.
JSON Lines permet d’ajouter un article à la fois et de relire le fichier sans charger tout le jeu de données en mémoire.
Chaque article est nettoyé avant son stockage :
- les espaces insécables sont normalisés ;
- les caractères de largeur nulle sont supprimés ;
- les fins de ligne sont normalisées ;
- les sauts de ligne répétés sont regroupés ;
- le titre, le corps et les catégories sont nettoyés.
Le collecteur génère aussi une empreinte stable à partir du lien de l’article :
hash: md5(data.link)Cet identifiant déterministe peut servir à la déduplication et aux traitements idempotents.
Après sa persistance locale, l’article est transmis à l’API backend de Basango. Cela apporte deux garanties utiles :
- les données collectées peuvent être stockées localement pour leur inspection, leur rejeu ou la génération de jeux de données ;
- les articles correctement normalisés peuvent être envoyés au backend principal par une frontière d’ingestion contrôlée.
Collecte incrémentale et fiabilité HTTP
La collecte d’actualités est à la fois un problème d’extraction de données et d’exploitation. Un collecteur qui récupère plusieurs fois le même contenu gaspille de la bande passante, du processeur, du stockage et des traitements en aval.
Basango accepte des contraintes comme une plage de pages, une période, une catégorie, les dates de mise à jour de la source ou le sens de mise à jour. Ces contrôles évitent au système de tout collecter aveuglément à chaque exécution.
Le client HTTP est également configurable pour un usage en production :
- délai d’expiration des requêtes ;
- nombre maximal de nouvelles tentatives ;
- temporisation progressive entre les tentatives ;
- prise en charge des redirections ;
- vérification SSL ;
- gestion de
Retry-After; - configuration de l’agent utilisateur ;
- rotation facultative des agents utilisateurs.
Ces précautions sont nécessaires, car les sites de médias ne sont pas des infrastructures stables. Ils peuvent être lents, temporairement indisponibles, mal configurés, protégés par des limites de débit ou répondre de manière irrégulière aux clients automatisés.
Certaines sources exigent aussi une collecte plus prudente. La configuration prend en charge :
requiresRateLimit: booleanLe collecteur peut ainsi traiter les sources sensibles de manière plus responsable au lieu d’appliquer le même comportement à tous les sites.
Effet de cette conception sur les sources
Le collecteur ajoute une source par configuration.
Ajouter une source fondée sur WordPress peut se résumer à ajouter une entrée de configuration :
{
"sourceId": "example.com",
"sourceKind": "wordpress",
"sourceUrl": "https://example.com"
}Une source HTML spécifique demande davantage de configuration, mais pas une nouvelle implémentation du collecteur :
{
"sourceId": "example.com",
"sourceKind": "html",
"sourceUrl": "https://example.com",
"paginationTemplate": "actualite",
"sourceSelectors": {
"articles": ".article-list .item",
"articleTitle": "h1",
"articleLink": "a",
"articleDate": "meta[property=\"article:published_time\"]",
"articleBody": ".article-body",
"pagination": ".pagination a:last-child"
}
}La variabilité des sources reste dans la configuration. Le moteur conserve la même séquence de découverte, de détail, de normalisation et de persistance. Le résultat est un moteur d’ingestion piloté par la configuration, qui prend en charge des sources d’actualité hétérogènes, les normalise dans une représentation commune et peut s’exécuter de façon synchrone en développement ou asynchrone pour une collecte à l’échelle de la production.
Couche de base de données
Une fois que le collecteur définit comment l’information entre dans Basango, le défi suivant concerne son traitement après la collecte.
PostgreSQL stocke chaque article normalisé, rejette les doublons, construit l’index de recherche et relie l’enregistrement aux sources, aux catégories, aux favoris et aux rapports.
Le code de la base de données se trouve dans un paquet distinct du monorepo. L’API importe ses requêtes. Le collecteur, le tableau de bord et l’application mobile passent par l’API.
Le dépôt regroupe l’API, le collecteur, le tableau de bord et l’application mobile avec des paquets partagés pour la base de données, le domaine, la journalisation, le chiffrement et l’interface.
Objectif de conception
La base de données suit une règle :
Stocker les articles comme des objets de connaissance structurés, et non comme de simples blocs de texte collectés.
Un agrégateur élémentaire pourrait stocker { title, body, url }. Basango conserve aussi les données nécessaires à l’analyse des sources, au classement des sujets, à la déduplication, à la recherche et aux rapports.
Basango modélise la base de données autour des principales entités d’un système de veille médiatique :
Source
Article
Category
User
Bookmark
Comment
FollowedSource
RefreshToken
VerificationToken
LoginHistoryCes enregistrements servent à l’ingestion, à la recherche, aux rapports, aux favoris, aux commentaires et au suivi des sources.
La base de données doit prendre en charge trois types de charge :
Choix technologique
Basango utilise PostgreSQL avec Drizzle ORM.
PostgreSQL convient bien, car les données sont fortement relationnelles. Les articles appartiennent à des sources et peuvent être classés dans des catégories. Les utilisateurs peuvent suivre des sources, ajouter des articles aux favoris et commenter leur contenu. Les rapports nécessitent des jointures, regroupements, tris, filtres et agrégations sur des périodes.
Le paquet de base de données utilise les définitions de schéma et les migrations de Drizzle. Le schéma est centralisé dans packages/db/src/schema.ts, tandis que le client est exposé depuis packages/db/src/client.ts. Il utilise un pool de connexions PostgreSQL et fournit une instance Drizzle typée au reste du backend.
Les paquets dépendent les uns des autres comme suit :
L’API importe le modèle de données. Le tableau de bord utilise les types partagés du backend. Le collecteur envoie les enregistrements au point d’ingestion au lieu d’écrire dans PostgreSQL.
Modèle de données principal
La table article se trouve au cœur de la base de données.
Chaque article conserve son texte, sa source, sa date de publication, ses catégories, sa tonalité, des métadonnées de crédibilité, des statistiques de tokens, son temps de lecture, une empreinte de déduplication, un index de recherche et les horodatages du collecteur.
Le schéma contient aussi des tables associées pour les sources, catégories, utilisateurs, favoris, sources suivies, commentaires, historiques de connexion, jetons de vérification et jetons d’actualisation.
Conceptuellement, le modèle se présente ainsi :
Les mêmes enregistrements servent au flux actuel, aux rapports du tableau de bord et aux futurs essais de recherche.
Par exemple, bookmarks et followed_sources préparent le système à la personnalisation. credibility permet d’évaluer la qualité des sources. token_statistics prépare le jeu de données aux traitements NLP et LLM. categories et category_id prennent en charge à la fois les libellés fournis par les sources et une classification normalisée à l’échelle de la plateforme.
L’article comme document canonique
L’article est l’enregistrement central de la base de données.
Le collecteur reçoit des données provenant de différents sites, mais la base les stocke selon un modèle canonique unique :
id
sourceId
categoryId
title
body
link
hash
publishedAt
crawledAt
categories
metadata
credibility
sentiment
readingTime
tokenStatistics
clustered
tsvC’est à ce niveau que la normalisation se concrétise.
Un éditeur peut classer ses articles avec des libellés tels que :
Politique
Actualité
Sécurité
Nord-Kivu
RDCBasango a toutefois besoin d’une taxonomie plus cohérente à l’échelle du produit :
Politics
Security
Economy
Society
Environment
Culture
International affairsBasango conserve donc les catégories brutes de l’article tout en lui attribuant une catégorie canonique normalisée.
Cette décision de conception préserve la façon dont l’éditeur d’origine décrit l’article, tandis que les catégories canoniques assurent la cohérence du produit entre les sources.
Sans cette séparation, l’application perdrait des métadonnées utiles ou présenterait aux utilisateurs un système de catégories désordonné.
Déduplication et idempotence
La collecte d’actualités est répétitive par nature.
Un collecteur peut récupérer plusieurs fois le même article en raison de changements de pagination, de nouvelles tentatives, de collectes de mise à jour, de pages de catégories ou de doublons propres à la source. La base de données doit donc garantir l’idempotence.
Basango génère une empreinte stable à partir du lien et l’utilise pour détecter les doublons avant l’insertion. Lors de la création d’un article, le système vérifie d’abord si la même empreinte existe déjà. Si c’est le cas, il renvoie la référence de l’article existant au lieu d’insérer un doublon.
L’empreinte permet de relancer le collecteur sans créer un second article.
Un collecteur doit pouvoir échouer, réessayer, redémarrer et retraiter des pages sans altérer le jeu de données. L’idempotence rend cela possible.
Stratégie de classification
La première version de la classification est volontairement pragmatique.
Au lieu de commencer par un classificateur coûteux fondé sur un LLM, Basango utilise une approche déterministe basée sur des catégories canoniques, des candidats, la normalisation et une mise en correspondance pondérée. Le classificateur normalise les libellés fournis par les sources, les compare à des candidats connus, attribue un score aux correspondances et choisit la meilleure catégorie canonique.
Cette approche convient à la phase actuelle du projet.
Les LLM pourront être utiles plus tard, notamment pour les articles ambigus ou une modélisation thématique plus riche. Dans un système d’ingestion continue, chaque décision automatisée a toutefois un coût. Une classification fondée sur des règles est moins chère, prévisible, facile à déboguer et suffisamment efficace comme première couche.
La stratégie de classification se compose de plusieurs couches :
Un LLM pourra traiter les correspondances peu sûres sans remplacer le parcours fondé sur les règles.
Recherche et consultation
Basango utilise la recherche plein texte de PostgreSQL comme première couche de recherche.
La recherche plein texte de PostgreSQL repère les documents en langage naturel qui correspondent à une requête et peut les classer par pertinence. Elle prétraite le texte en lexèmes, stocke des vecteurs de recherche et permet une recherche indexée avec tsvector et tsquery.
PostgreSQL convient donc bien à la première version du moteur de recherche de Basango.
Au lieu d’introduire immédiatement Elasticsearch, Meilisearch ou une base vectorielle, Basango commence avec les capacités déjà fournies par PostgreSQL :
GIN index on search vector
trigram indexes on title and link
source/date/id index for feed pagination
category index for filtering
hash uniqueness for deduplicationLe schéma comprend des index pour les catégories, le titre, le lien, le vecteur de recherche, la source, la date de publication et l’identifiant de l’article.
Cela correspond aux principaux parcours de lecture :
Latest articles
Articles by source
Articles by category
Articles by sentiment
Search by keyword
Publication graph
Source distributionLa base de données est optimisée pour les flux de travail dont Basango a réellement besoin, plutôt que de devenir un dépôt générique d’enregistrements collectés.
Stratégie de pagination
Les flux nécessitent une pagination, mais celle fondée sur un décalage devient coûteuse et instable lorsque les données changent fréquemment.
Basango utilise une stratégie de pagination par curseur fondée sur la date de publication et l’identifiant de l’article. La requête construit l’état de pagination, applique les filtres, trie par publishedAt et id, puis récupère un enregistrement supplémentaire pour déterminer s’il existe une page suivante.
Cette décision convient bien au backend d’un système d’actualité.
Dans un jeu de données en croissance continue, la pagination par décalage peut ignorer ou dupliquer des enregistrements à l’arrivée de nouveaux articles. La pagination par curseur est plus fiable, car elle s’appuie sur des champs stables et ordonnés.
Le modèle de requête accepte notamment les filtres suivants :
source
sentiment
category
search query
cursor
limitL’API utilise le même contrat de pagination pour le tableau de bord et le flux mobile.
Analyse et rapports
Basango est aussi conçu pour l’analyse.
La couche de requêtes expose des fonctions de rapport pour :
article publication graph
article source distribution
source publication graph
source category shares
dashboard overviewLe paquet de base de données gère ces requêtes agrégées. L’API expose leurs résultats par des procédures typées.
La couche de rapports calcule notamment le nombre total d’articles, d’utilisateurs et de sources, les sources actives sur une période, le volume de publications dans le temps et leur répartition en pourcentage par source ou catégorie.
Basango sert à la lecture, au suivi des sources et aux requêtes de recherche. La base doit donc fournir les articles et des rapports agrégés.
Le tableau de bord doit répondre à des questions opérationnelles :
Are crawlers producing data?
Which sources are active?
Which topics dominate a source?
How much content was collected this week?
Are some sources overrepresented?Ces questions nécessitent des agrégations, pas seulement des opérations CRUD.
Couche de requêtes de la base de données
Le paquet de base de données sépare les définitions du schéma des fonctions de requête.
Le paquet de base de données expose cette interface :
createArticle()
getArticles()
getArticleById()
getArticlesPublicationGraph()
getArticlesSourceDistribution()
createSource()
updateSource()
getSources()
getSourceById()
getSourcePublicationGraph()
getSourceCategoryShares()
getCategories()
getDashboardOverview()
getUserByEmail()
getUserById()Les gestionnaires de routes appellent les fonctions du paquet au lieu de contenir du SQL. Les tests de requêtes peuvent ainsi s’exécuter sans invoquer le transport HTTP.
Les mêmes fonctions de requête peuvent aussi être réutilisées depuis différents points d’entrée : points de terminaison REST d’ingestion, procédures tRPC, tâches d’arrière-plan, scripts CLI ou futurs workers.
Flux de la base de données
Comportement de la base face aux sources instables
La conception de la base de données renforce la résilience de Basango.
Le collecteur peut produire du bruit, les sites peuvent changer et leurs catégories manquer de cohérence, mais la base de données fournit un modèle stable au reste du système.
Les principales décisions d’ingénierie sont les suivantes :
- utiliser PostgreSQL comme couche principale de persistance et de recherche ;
- utiliser Drizzle pour conserver un schéma et des requêtes typés ;
- stocker les articles comme des documents structurés plutôt que comme des blocs de texte ;
- préserver les métadonnées brutes des sources tout en ajoutant les métadonnées canoniques de la plateforme ;
- utiliser des empreintes pour une ingestion idempotente ;
- exploiter la recherche indexée avant d’ajouter une infrastructure plus coûteuse ;
- conserver les requêtes analytiques près de la base de données ;
- modéliser assez tôt les fonctions destinées aux utilisateurs pour permettre une personnalisation ultérieure.
Le paquet de base de données gère le stockage, la recherche, les rapports, le suivi des sources et les sujets. Une future personnalisation pourra lire ces mêmes enregistrements sans modifier le collecteur.
Couche API
L’API reçoit les enregistrements du collecteur et sert les articles, les rapports et les données de compte aux clients.
L’API reçoit les articles normalisés du collecteur, valide les entrées, fournit un accès authentifié aux clients internes et expose au tableau de bord des points de terminaison pour les rapports.
Il ne s’agit pas seulement d’un ensemble de routes CRUD. C’est la frontière qui protège la base de données et organise les capacités du produit en procédures typées.
Basango utilise Hono avec des en-têtes sécurisés, la journalisation, la configuration CORS, des routeurs REST et un serveur tRPC monté sous /trpc/*.
L’API remplit ainsi deux rôles :
Le collecteur envoie les articles à un point HTTP. Le tableau de bord et l’application mobile appellent des procédures tRPC vérifiées par TypeScript.
Objectif de conception
L’API suit une règle :
Conserver une API produit typée, tout en gardant l’ingestion simple et explicite.
Basango possède plusieurs types de clients :
Crawler
Dashboard
Mobile app
Future external toolsLe collecteur n’a pas les mêmes besoins que le tableau de bord. Il lui faut un point d’ingestion stable. Le tableau de bord et l’application mobile ont besoin de requêtes typées, d’authentification, de pagination, de filtrage, d’analyse et de mutations sûres.
L’usage conjoint de REST et tRPC permet à chaque client d’utiliser l’interface adaptée.
Pourquoi tRPC
tRPC convient à Basango, car le projet est un monorepo TypeScript.
tRPC est une implémentation des appels de procédure distante pour les applications TypeScript. Au lieu d’appeler directement des URL et de maintenir manuellement des DTO, les clients appellent des fonctions typées exposées par le serveur.
Le tableau de bord et l’application mobile consomment la même API interne.
Le tableau de bord et l’application mobile ne doivent pas dupliquer manuellement les DTO du backend. Ils doivent consommer directement le contrat du serveur.
Avec tRPC, un routeur backend comme celui-ci :
articles.list
articles.create
articles.getPublications
sources.list
sources.update
reports.getDashboardOverview
auth.sessiondevient une API client typée.
Cette approche réduit les erreurs d’intégration. Si une entrée du backend change, le client échoue à la compilation plutôt qu’à l’exécution.
Structure des routeurs
L’API tRPC est organisée en routeurs par domaine.
Le routeur de l’application regroupe :
articles
auth
categories
reports
sourcesCes routeurs exposent les principales capacités du produit : ingestion et liste des articles, authentification, récupération des catégories, rapports du tableau de bord et gestion des sources. L’API est organisée autour des capacités exposées par le système, et pas seulement de dossiers techniques comme les contrôleurs et les gestionnaires.
Contexte, middleware et authentification
Toute API sérieuse a besoin d’un modèle de contexte.
Le contexte tRPC de Basango comprend :
database connection
session
geolocation contextL’API extrait un jeton d’accès de l’en-tête Authorization, résout la session, attache l’instance de base de données et déduit le contexte géographique de la requête. L’initialisation de tRPC définit aussi des procédures publiques et protégées. Ces dernières exigent un accès à la base de données et une authentification.
L’API dispose ainsi d’un modèle de sécurité cohérent.
Au lieu de vérifier manuellement l’authentification dans chaque procédure, Basango la centralise par composition de procédures :
publicProcedure
protectedProcedureL’authentification est exposée par des procédures de connexion, d’actualisation et de session. La connexion valide les identifiants, rejette les comptes verrouillés, vérifie le mot de passe et renvoie des jetons de session. L’actualisation valide un jeton d’actualisation et émet une nouvelle session. La procédure de session renvoie l’utilisateur authentifié aux clients protégés.
Basango propose aussi des favoris, des sources suivies, des commentaires, des flux personnalisés, l’accès au tableau de bord et l’administration des sources. Ces fonctions ont besoin du contexte d’un utilisateur authentifié.
Ces fonctions nécessitent une couche d’identité fiable.
Séparation entre l’API et la base de données
L’API délègue les opérations de base de données aux fonctions de requête de @basango/db.
Une procédure doit généralement rester légère :
Le routeur des articles appelle des fonctions telles que :
createArticle()
getArticles()
getArticlesPublicationGraph()
getArticlesSourceDistribution()Le routeur des sources appelle des fonctions telles que :
createSource()
updateSource()
getSources()
getSourceById()
getSourcePublicationGraph()
getSourceCategoryShares()Les fonctions de la base contiennent les requêtes. Les routeurs valident et autorisent les appels.
L’API ne doit pas se soucier des détails SQL. Le paquet de base de données ne doit pas savoir si l’appelant est tRPC, REST, un worker ou une commande CLI.
Cette frontière préserve la qualité de la base de code à mesure qu’elle grandit.
Stratégie de validation
L’API utilise les schémas de domaine partagés de @basango/domain.
La validation doit s’effectuer à la frontière. Le collecteur peut envoyer des données d’article mal formées. Le tableau de bord peut transmettre des filtres invalides. Un client mobile peut appeler une procédure avec un jeton expiré. Toute entrée du backend doit être validée.
Basango évite de dupliquer la logique de validation en conservant les modèles et schémas du domaine dans un paquet partagé. L’API importe ces schémas et les applique aux frontières des procédures.
Le principe est le suivant :
Le système reste ainsi cohérent de l’ingestion à la consultation.
Frontière REST pour l’ingestion
Le collecteur transmet les articles normalisés à l’API backend après leur persistance locale.
Il s’agit d’une décision de conception délibérée.
Le collecteur n’écrit pas directement dans la base de données. Il considère l’API comme la frontière d’ingestion.
Le système bénéficie ainsi de plusieurs avantages :
- la base de données reste privée ;
- l’ingestion peut être authentifiée ;
- la validation s’effectue à un seul endroit ;
- les défaillances du collecteur sont isolées de l’accès à la base ;
- de futurs clients d’ingestion peuvent réutiliser la même frontière.
Permettre à chaque composant interne d’écrire directement dans la base peut sembler tentant, mais cela crée un couplage étroit et une validation incohérente. Basango l’évite en faisant de l’API son point d’entrée contrôlé.
Flux d’exécution de l’API
Effet de l’API sur les intégrations
L’API constitue le seul point d’écriture pour les articles du collecteur.
Le collecteur peut évoluer indépendamment. Le schéma de la base peut changer derrière les fonctions de requête. Le tableau de bord et l’application mobile consomment des procédures fortement typées. L’authentification, la résolution des sessions, CORS, les en-têtes sécurisés et la journalisation des requêtes sont centralisés.
Les principales décisions d’ingénierie sont les suivantes :
- utiliser REST lorsque la simplicité de l’ingestion est prioritaire ;
- utiliser tRPC pour vérifier les appels des clients internes par le typage ;
- centraliser l’authentification dans les procédures protégées ;
- maintenir les requêtes de base de données hors des gestionnaires de routes de l’API ;
- réutiliser les schémas du domaine pour valider les entrées ;
- exposer les requêtes analytiques comme des capacités du produit ;
- conserver une API légère, typée et composable.
L’API regroupe l’ingestion, les rapports, l’authentification, la recherche et la future personnalisation.
Clients : tableau de bord web et application mobile
Une fois le collecteur, la base de données et l’API en place, Basango a besoin d’interfaces qui rendent les informations sélectionnées utilisables.
Les applications web et mobiles donnent accès aux articles retenus. Elles utilisent le même contrat d’API et adaptent l’affichage à leur usage.
Basango propose deux interfaces distinctes :
Le tableau de bord montre l’état du système. L’application mobile sert à consulter les articles.
Tableau de bord web
Le tableau de bord web sert aux opérations internes de Basango.
Il expose notamment les capacités suivantes :
total articles
total sources
active sources
publication trends
source distribution
category shares
article lists
source management
crawler outputsLe tableau de bord est une application Next.js qui utilise tRPC, TanStack Query, React Table, Recharts, les composants Shadcn UI et les paquets de domaine partagés.
Le tableau de bord utilise le même contrat d’API que les autres clients au lieu de lire les tables brutes.
Il utilise des procédures de rapport telles que :
reports.getDashboardOverview
articles.getPublications
articles.getSourceDistribution
sources.getPublications
sources.getCategorySharesLe tableau de bord utilise les procédures de rapport qui exposent aussi l’état du collecteur et l’activité des sources.
Le tableau de bord comme outil d’observabilité
Pour un système fondé sur la collecte, un tableau de bord d’administration n’est pas facultatif.
Les collecteurs peuvent échouer silencieusement s’ils ne sont pas surveillés. Une source peut modifier sa structure HTML. Une API WordPress peut cesser de répondre. Un format de date peut devenir invalide. Une source peut soudainement produire beaucoup moins d’articles que prévu.
Le tableau de bord aide à répondre à des questions opérationnelles :
Did the crawler collect articles today?
Which sources are active?
Which sources are overrepresented?
Are categories being assigned correctly?
Is a source producing mostly one kind of content?
Did publication volume change compared to the previous period?Le tableau de bord sert à diagnostiquer l’ingestion et à présenter les articles.
Application mobile
L’application mobile est l’interface de consultation destinée aux utilisateurs.
Son rôle diffère de celui du tableau de bord. Le tableau de bord aide à exploiter le système ; l’application mobile aide les utilisateurs à rester informés sans surconsommer l’information.
L’application mobile utilise Expo, React Native, Expo Router et React Navigation.
L’orientation du produit mobile est la suivante :
personalized news feed
topic-specific reading
source following
saved articles
article details
search
recommendations
notificationsLa base de données contient déjà les structures nécessaires à cette orientation, notamment les tables liées aux utilisateurs, favoris, sources suivies, commentaires et à l’authentification.
Le client mobile lit les articles, les catégories, les sources et les favoris existants.
Contrat d’API partagé
Les deux clients appellent Basango par l’API.
tRPC permet aux deux clients TypeScript d’appeler les procédures du backend avec des types partagés. Un changement de nom ou d’entrée échoue alors pendant la vérification des types dans le client concerné.
Les clients partagent ce parcours d’intégration :
api.articles.list()
api.sources.list()
api.reports.getDashboardOverview()
api.auth.login()
api.auth.session()Le routeur du serveur devient le contrat, sans qu’il soit nécessaire de dupliquer les types de requêtes et de réponses dans chaque client.
Cette approche réduit les erreurs d’intégration et sécurise les refactorisations. Si une entrée de l’API change, le tableau de bord et l’application mobile signalent des erreurs de typage pendant le développement.
C’est le mode d’échec attendu.
Architecture des clients
Les clients utilisent les contrats partagés suivants :
Le tableau de bord Web peut se concentrer sur les tableaux, graphiques, filtres et l’administration des sources.
L’application mobile peut se concentrer sur la lecture, les articles enregistrés, les préférences et les notifications.
Les deux clients reposent sur les mêmes concepts du backend :
Article
Source
Category
User
Session
Bookmark
ReportBasango conserve ainsi une cohérence fonctionnelle entre les plateformes.
Responsabilités du web et du mobile
Le tableau de bord ne doit pas chercher à devenir l’application mobile, ni l’application mobile le tableau de bord.
Cette séparation préserve la clarté du produit.
Le tableau de bord répond aux questions suivantes :
What is the system collecting?
Which sources are active?
How is the dataset evolving?
Are categories and sources healthy?L’application mobile répond aux questions suivantes :
What should I read now?
What happened about the topics I follow?
Which articles are relevant to me?
What should I save or revisit later?Cette distinction évite de transformer le produit en une interface unique et surchargée.
Les clients appellent les procédures de l’API. Les routeurs valident et autorisent les requêtes, puis appellent le paquet de base de données. Les fonctions de données renvoient les enregistrements sans connaître le client appelant.
Principales décisions d’ingénierie
Partir du besoin réel, pas de la technologie
Le projet est parti du travail nécessaire pour suivre l’actualité de la RDC, notamment le conflit dans l’est, sans consulter des dizaines de sources à la main.
Ce problème a façonné l’architecture.
L’objectif n’était pas de "construire un collecteur" ou d’"utiliser un LLM", mais de réduire le coût nécessaire pour rester informé.
C’est pourquoi le système comprend la collecte, la normalisation, la recherche, la classification, les rapports du tableau de bord et la consultation mobile.
Gérer la variabilité des sources par la configuration
Les sites d’actualité évoluent. Leurs structures HTML diffèrent. Certains exposent des API WordPress, d’autres non.
Un collecteur piloté par la configuration simplifie l’ajout et la maintenance des sources.
La configuration des sources change plus souvent que la séquence de collecte. Les sélecteurs et les paramètres de requête restent donc dans la configuration.
Conserver une ingestion idempotente
Le collecteur effectuera de nouvelles tentatives. Les pages se chevaucheront. Les sources dupliqueront certains articles.
Une stratégie de déduplication fondée sur une empreinte permet de relancer le collecteur sans altérer le jeu de données.
Utiliser une classification déterministe avant les LLM
La première couche de classification repose sur des règles et un ensemble de candidats.
Elle n’est pas moins élaborée, mais plus responsable.
Les LLM pourront améliorer plus tard la classification des cas ambigus, mais ils ne doivent pas constituer la première dépendance d’un système qui doit fonctionner en continu à faible coût.
Exploiter PostgreSQL avant d’ajouter une infrastructure de recherche
PostgreSQL fournit déjà la modélisation relationnelle, les index, les agrégations et la recherche plein texte.
Ajouter trop tôt Elasticsearch, Meilisearch ou une base vectorielle augmenterait les coûts d’exploitation et la complexité.
Le meilleur choix consistait à exploiter d’abord les capacités de PostgreSQL, puis à ajouter des systèmes spécialisés seulement lorsque le produit en démontrerait le besoin.
Séparer le transport API de la logique de données
L’API expose les capacités du produit.
Le paquet de base de données gère les requêtes.
Les gestionnaires de routes restent ainsi limités et le système évite d’accumuler du SQL dans les contrôleurs.
Utiliser tRPC pour les clients internes
Le tableau de bord Web et l’application mobile sont des clients TypeScript internes.
tRPC leur fournit un contrat typé sans génération de code ni duplication des DTO.
REST reste adapté aux intégrations externes et à l’ingestion.
Chaque client utilise l’interface qui correspond à son mode d’appel.
Utiliser le tableau de bord pour contrôler l’ingestion
Le tableau de bord sert à consulter les articles, à exploiter le collecteur et à examiner la qualité des données.
Sans ces rapports, un sélecteur cassé peut continuer à produire des tâches vides sans erreur visible dans le flux public.
Ce que j’ai appris
La principale leçon de Basango est que le téléchargement des pages est la partie facile. La difficulté commence lorsque le système doit relancer les requêtes en échec, comparer les doublons, normaliser des métadonnées inégales et montrer si la dernière collecte a vraiment produit des articles utiles.
La collecte doit être considérée comme un processus peu fiable. Les sites changent, les métadonnées manquent de cohérence, la pagination n’est pas toujours propre et les catégories des sources ne peuvent pas servir directement de taxonomie au produit.
J’ai aussi appris à attendre d’avoir un besoin précis avant d’ajouter de l’IA. Une classification par règles coûte moins cher et reste plus simple à examiner. Elle fournit aussi une base de comparaison pour mesurer un futur modèle.
Une fois la collecte en place, la consultation a demandé davantage de travail. La base doit dédupliquer les articles, normaliser les catégories, indexer le texte, filtrer les résultats et calculer les rapports.
La couche API est aussi devenue une frontière de conception majeure. En séparant l’ingestion REST de l’accès client par tRPC, Basango prend en charge les flux automatisés et le développement des applications internes sans imposer un seul style d’interface partout.
Le tableau de bord est aussi un outil d’exploitation. Il montre si les tâches s’exécutent, quand chaque source a produit des données pour la dernière fois et où une règle d’extraction ne fonctionne plus.
État actuel
Basango réunit désormais les principales pièces du pipeline de curation : adaptateurs par source, collecte asynchrone, ingestion idempotente, recherche PostgreSQL, classification thématique, API typées et clients web et mobile. La suite demande des mesures. Je dois évaluer la couverture des sources, les échecs d’extraction, le taux de doublons et la qualité de la classification avant d’ajouter la recherche sémantique ou des recommandations.
Le projet doit réduire le temps passé à réunir les articles et laisser davantage de temps pour comparer ce que des sources crédibles rapportent sur la RDC.