Production2025

Basango

Vers un système évolutif et intelligent de curation de l’actualité congolaise

Illustration de Basango

Basango est né d’un besoin personnel : suivre de manière plus responsable l’actualité essentielle de la République démocratique du Congo, en particulier la guerre et la situation sécuritaire dans l’est du pays. Le projet a évolué vers un système d’information piloté par le backend pour automatiser la collecte, la normalisation, la classification, la recherche et l’analyse des actualités, avec une perspective de recommandation.

L’objectif n’était pas simplement de construire un agrégateur d’actualités supplémentaire. Il s’agissait surtout de réduire le travail manuel nécessaire pour trouver des informations crédibles, pertinentes et non dupliquées dans un écosystème médiatique fragmenté.

Basango est conçu comme une chaîne allant de l’ingestion à la consultation. Il associe conception de systèmes, architecture backend, modélisation des données, exploration du Web, recherche d’information et préparation à l’usage de l’IA.

Le problème

Le problème à l’origine de Basango n’était pas l’accès à l’actualité, mais la recherche fiable d’informations ciblées, avec peu de bruit.

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.

Le défi d’ingénierie consistait à concevoir une chaîne de curation d’actualités économique et extensible, capable d’explorer des sites de médias congolais hétérogènes, d’extraire leur contenu au moyen d’adaptateurs propres à chaque source, de normaliser les données dans un modèle commun, de classer les articles par sujet et de préparer le jeu de données pour la recherche, l’analyse, la recommandation et les traitements textuels ultérieurs.

Architecture du système

Basango s’organise autour de quatre couches principales :

  1. Collecteur : collecte et normalise les articles
  2. Base de données : stocke, déduplique, indexe et classe les données
  3. API : valide les entrées, authentifie les utilisateurs et expose les capacités du backend
  4. Clients : donnent accès aux informations sélectionnées depuis le tableau de bord et l’application mobile

Chaque couche a une responsabilité claire.

Le collecteur gère l’hétérogénéité des sources. La base de données assure la persistance, la déduplication, la classification, la recherche et l’analyse. L’API prend en charge la validation, l’authentification, l’orchestration et les accès typés. Les clients rendent les informations sélectionnées exploitables dans des interfaces opérationnelles et destinées aux utilisateurs.

Mermaid

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 véritable défi ne consistait pas simplement à télécharger des pages. Il fallait concevoir un moteur de collecte commun, capable de prendre en charge les sources fondées sur une API comme celles fondées sur le HTML, sans dupliquer la logique pour chaque média.

Objectif de conception

Le collecteur a été conçu autour d’une idée centrale :

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.

Cette approche sépare clairement les règles d’extraction propres aux sources du flux de collecte générique.

Abstraction des analyseurs

Basango utilise deux implémentations concrètes d’analyseur derrière un modèle de collecte commun :

  1. HtmlCrawler sert pour les sites dont les articles doivent être extraits du HTML à l’aide de sélecteurs.

  2. WordPressCrawler sert 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 :

TypeScript
type AnySourceOptions = HtmlSourceOptions | WordPressSourceOptions;

type SourceKind = "html" | "wordpress";

Le moteur peut ainsi résoudre dynamiquement une source et déléguer l’extraction à l’analyseur approprié.

Collecte pilotée par la configuration

La configuration du collecteur est validée avec Zod. Le moteur dispose ainsi d’une frontière typée entre la configuration externe et l’exécution.

C’est important, car une configuration de collecte est fragile. Un sélecteur cassé, une URL de source invalide, un type de source non pris en charge ou un format de date incorrect peut altérer silencieusement les données extraites s’il n’est pas détecté assez tôt.

Les sources HTML nécessitent des sélecteurs explicites, car chaque site possède sa propre structure :

JSON
{
  "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 :

CSS
.view-content > .views-row.content-row

tandis qu’une autre peut utiliser :

CSS
.for_aitems > .article_other_item

Le 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é :

JSON
{
  "sourceId": "example.com",
  "sourceKind": "wordpress",
  "sourceUrl": "https://example.com"
}

Cette découverte importante pendant la phase de recherche a réduit le travail d’extraction spécifique nécessaire pour les médias fondés sur WordPress.

Flux de sélection de l’analyseur

Mermaid

Flux de collecte

La collecte d’une source se divise en trois étapes indépendantes :

  1. Découvrir les liens des articles
  2. Récupérer le détail des articles
  3. Conserver ou transmettre l’article normalisé

Cette séparation est importante, car chaque étape possède des caractéristiques de performance différentes.

Les pages de liste sont légères et servent surtout à la découverte. Les pages détaillées coûtent davantage, car elles nécessitent des requêtes HTTP et des analyses supplémentaires. La persistance est isolée afin qu’une défaillance du stockage ou du backend ne fasse pas échouer toute l’opération de collecte.

Exécution synchrone et asynchrone

Basango prend en charge la collecte synchrone et asynchrone.

Le mode synchrone a été mis en place pour le développement et les itérations. Il permet de collecter immédiatement une source depuis la ligne de commande, sans workers Redis ni orchestration de files. Il sert à tester la configuration d’une nouvelle source, corriger des sélecteurs cassés, valider la pagination, vérifier l’analyse des dates et inspecter les champs extraits.

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 :

Mermaid

Cette séparation facilite la mise à l’échelle horizontale. Des workers supplémentaires peuvent être ajoutés lorsque la récupération des articles devient le goulot d’étranglement.

Mermaid

La couche de files reste volontairement limitée. QueueManager n’expose que les opérations nécessaires au collecteur :

TypeScript
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 :

TypeScript
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 convient bien à cet usage : il facilite l’ajout de données, leur traitement en flux, leur inspection et les flux de travail centrés sur les jeux de données.

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 :

TypeScript
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 :

TypeScript
requiresRateLimit: boolean

Le collecteur peut ainsi traiter les sources sensibles de manière plus responsable au lieu d’appliquer le même comportement à tous les sites.

Pourquoi la conception du collecteur est importante

La principale qualité du collecteur est son extensibilité.

Ajouter une source fondée sur WordPress peut se résumer à ajouter une entrée de configuration :

JSON
{
  "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 :

JSON
{
  "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"
  }
}

C’est la décision d’ingénierie essentielle : la variabilité des sources est gérée de manière déclarative dans la configuration, tandis que le moteur d’exécution reste réutilisable. 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.

La couche de base de données transforme les actualités brutes collectées en une base d’information durable, interrogeable, dédupliquée, consultable, classifiable et analysable.

Le collecteur produit des enregistrements normalisés, mais ceux-ci ne deviennent utiles qu’une fois dédupliqués, indexés, recherchables, classés, filtrables et reliés aux sources, catégories, utilisateurs, favoris et vues de rapports.

Basango implémente la couche de base de données dans un paquet autonome du monorepo, distinct de l’API, du collecteur, du tableau de bord et des applications mobiles. Cette décision d’architecture est importante : le modèle de données est une capacité partagée du backend, et non du code privé enfoui dans une application.

Le dépôt s’organise autour d’applications comme l’API, le collecteur, le tableau de bord et l’application mobile, ainsi que de paquets partagés pour la base de données, le domaine, la journalisation, le chiffrement et l’interface utilisateur.

Objectif de conception

La base de données a été conçue autour d’une idée centrale :

Stocker les articles comme des objets de connaissance structurés, et non comme de simples blocs de texte collectés.

Un agrégateur naïf pourrait stocker chaque article sous la forme { title, body, url } et s’en tenir là. Cela suffirait pour afficher un flux élémentaire, mais pas pour atteindre les objectifs réels de Basango : analyser les sources, classer les sujets, dédupliquer, rechercher, recommander, alimenter des tableaux de bord et permettre de futurs travaux de recherche.

Basango modélise la base de données autour des principales entités d’un système de veille médiatique :

Plain text
Source
Article
Category
User
Bookmark
Comment
FollowedSource
RefreshToken
VerificationToken
LoginHistory

Cette structure montre que le projet n’est pas seulement un collecteur, mais le socle d’une plateforme.

La base de données doit prendre en charge trois types de charge :

Mermaid

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.

Le projet dispose ainsi d’une frontière de données claire :

Mermaid

L’API ne redéfinit pas le modèle de données. Le tableau de bord ne recrée pas manuellement les types du backend. Le collecteur n’écrit pas directement dans PostgreSQL. Cette séparation facilite la maintenance du système.

Modèle de données principal

La table article se trouve au cœur de la base de données.

Un article n’est pas seulement stocké sous forme de texte. Il inclut 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 :

Mermaid

Cette structure prend en charge le produit actuel comme ses futures extensions.

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’objet le plus important 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 :

Plain text
id
sourceId
categoryId
title
body
link
hash
publishedAt
crawledAt
categories
metadata
credibility
sentiment
readingTime
tokenStatistics
clustered
tsv

C’est à ce niveau que la normalisation se concrétise.

Un éditeur peut classer ses articles avec des libellés tels que :

Plain text
Politique
Actualité
Sécurité
Nord-Kivu
RDC

Basango a toutefois besoin d’une taxonomie plus cohérente à l’échelle du produit :

Plain text
Politics
Security
Economy
Society
Environment
Culture
International affairs

Basango 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.

Cette décision permet de relancer le collecteur sans risque.

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 :

Mermaid

Cette approche laisse la porte ouverte à l’IA sans en rendre tout le système dépendant dès le premier jour.

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 :

Plain text
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 deduplication

Le 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 :

Plain text
Latest articles
Articles by source
Articles by category
Articles by sentiment
Search by keyword
Publication graph
Source distribution

La 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 :

Plain text
source
sentiment
category
search query
cursor
limit

L’API dispose ainsi d’un socle flexible pour le tableau de bord, le flux mobile et les futures interfaces de recommandation.

Analyse et rapports

Basango est aussi conçu pour l’analyse.

La couche de requêtes expose des fonctions de rapport pour :

Plain text
article publication graph
article source distribution
source publication graph
source category shares
dashboard overview

Ces requêtes ne relèvent pas de la logique d’interface. Elles appartiennent à la couche de base de données, car elles concernent l’accès aux données et leur agrégation. L’API se contente de les exposer 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.

C’est important, car Basango n’est pas seulement une application d’actualité destinée au public. C’est aussi un outil de suivi et de recherche.

Le tableau de bord doit répondre à des questions opérationnelles :

Plain text
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 reste du projet dispose ainsi d’une interface claire :

Plain text
createArticle()
getArticles()
getArticleById()
getArticlesPublicationGraph()
getArticlesSourceDistribution()

createSource()
updateSource()
getSources()
getSourceById()
getSourcePublicationGraph()
getSourceCategoryShares()

getCategories()
getDashboardOverview()
getUserByEmail()
getUserById()

Cette organisation est préférable à l’écriture de SQL directement dans les gestionnaires de routes de l’API.

L’API doit orchestrer les requêtes. Le paquet de base de données doit gérer la persistance et le comportement des requêtes. Le backend reste ainsi modulaire et plus facile à tester.

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

Mermaid

Pourquoi la conception de la base de données est importante

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.

La couche de base de données ne se limite donc pas au stockage. Elle constitue le socle de la recherche, de l’analyse, du suivi des sources, de la classification thématique et de la future personnalisation.

Couche API

Une fois les articles collectés et transformés en objets de connaissance structurés par la base de données, l’API devient la couche de coordination qui contrôle la manière dont les données entrent, sortent et circulent dans le système.

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 :

Mermaid

Cette séparation est pragmatique. Le collecteur peut transmettre les articles par un simple point de terminaison HTTP, tandis que le tableau de bord et l’application mobile utilisent tRPC pour bénéficier d’un développement avec typage de bout en bout.

Objectif de conception

L’API a été conçue autour d’une idée centrale :

Conserver une API produit typée, tout en gardant l’ingestion simple et explicite.

Basango possède plusieurs types de clients :

Plain text
Crawler
Dashboard
Mobile app
Future external tools

Le 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.

C’est important pour Basango, car la même API est consommée par des clients internes.

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 :

Plain text
articles.list
articles.create
articles.getPublications
sources.list
sources.update
reports.getDashboardOverview
auth.session

devient 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 :

Plain text
articles
auth
categories
reports
sources

Ces 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 :

Plain text
database connection
session
geolocation context

L’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 :

Plain text
publicProcedure
protectedProcedure

L’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.

C’est nécessaire, car Basango n’est pas seulement un flux public d’articles. Le système propose aussi des fonctions propres aux utilisateurs, comme les favoris, les sources suivies, les commentaires, les flux personnalisés, l’accès au tableau de bord et l’administration des sources.

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 :

Mermaid

Le routeur des articles appelle des fonctions telles que :

Plain text
createArticle()
getArticles()
getArticlesPublicationGraph()
getArticlesSourceDistribution()

Le routeur des sources appelle des fonctions telles que :

Plain text
createSource()
updateSource()
getSources()
getSourceById()
getSourcePublicationGraph()
getSourceCategoryShares()

Cette séparation maintient la logique métier d’accès aux données hors de la couche de transport.

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 :

Mermaid

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

Mermaid

Pourquoi la conception de l’API est importante

L’API fournit à Basango une frontière d’intégration solide.

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 lorsque le typage des clients internes est important ;
  • 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 obtenue ne se limite pas à une couche CRUD. Elle constitue la frontière backend pour 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 constituent la couche de consultation de Basango. Elles ne représentent pas le cœur du défi d’ingénierie, mais restent essentielles : un système de curation n’a de valeur que si les personnes peuvent réellement utiliser les informations retenues.

Basango propose deux interfaces distinctes :

Mermaid

Cette distinction est importante.

Le tableau de bord sert à comprendre le système. L’application mobile permet d’en tirer parti.

Tableau de bord Web

Le tableau de bord Web est l’interface interne et opérationnelle de Basango.

Il expose notamment les capacités suivantes :

Plain text
total articles
total sources
active sources
publication trends
source distribution
category shares
article lists
source management
crawler outputs

Le 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.

La décision d’ingénierie importante est que le tableau de bord ne se contente pas d’afficher les tables brutes de la base de données. Il consomme le même contrat d’API que les autres clients.

Il utilise des procédures de rapport telles que :

Plain text
reports.getDashboardOverview
articles.getPublications
articles.getSourceDistribution
sources.getPublications
sources.getCategoryShares

Le tableau de bord devient ainsi une véritable interface produit construite sur les capacités du backend.

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 :

Plain text
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 fait ainsi partie du système d’ingénierie et ne se limite pas à une couche frontend.

Un projet backend solide a besoin de boucles de retour. Le tableau de bord en constitue une.

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 est développée avec Expo et React Native, en utilisant Expo Router et React Navigation.

L’orientation du produit mobile est la suivante :

Plain text
personalized news feed
topic-specific reading
source following
saved articles
article details
search
recommendations
notifications

La 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.

C’est important, car le modèle de données anticipe l’orientation du produit au lieu de traiter le mobile après coup.

Contrat d’API partagé

Les applications Web et mobiles sont toutes deux conçues pour consommer Basango par la couche API.

Pour les clients internes, tRPC simplifie le développement en permettant d’appeler les procédures du backend avec des types TypeScript partagés. Conçu pour les applications TypeScript, tRPC permet aux clients de se concentrer sur les appels de procédures au lieu de maintenir manuellement les contrats des routes HTTP.

Les clients bénéficient ainsi d’un modèle d’intégration plus clair :

Plain text
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

La couche cliente s’organise autour de contrats partagés :

Mermaid

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 :

Plain text
Article
Source
Category
User
Session
Bookmark
Report

Basango conserve ainsi une cohérence fonctionnelle entre les plateformes.

Mermaid

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 :

Plain text
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 :

Plain text
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.

Il en résulte une architecture produit où chaque couche possède une responsabilité claire. Les clients ne portent pas la logique métier ; ils consomment les capacités exposées par l’API. L’API ne porte pas la complexité des requêtes ; elle la délègue au paquet de base de données. La base ignore si ses données servent au tableau de bord, à l’application mobile, au collecteur ou à de futurs outils de recherche.

Principales décisions d’ingénierie

Partir du besoin réel, pas de la technologie

Le projet est parti d’un problème d’information concret : suivre avec précision l’actualité de la RDC, notamment le conflit dans l’est du pays, sans consulter manuellement des dizaines de sources.

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.

Cette abstraction convient, car la variabilité se trouve dans les sources et non dans le flux de collecte principal.

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.

C’est l’une de ces petites décisions backend qui distinguent un prototype d’un véritable système.

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.

Utiliser les deux n’est pas incohérent : chaque client reçoit l’interface qui lui convient.

Faire du tableau de bord une boucle de retour d’ingénierie

Le tableau de bord n’est pas seulement destiné aux utilisateurs. Il facilite l’exploitation du collecteur et l’évaluation de la qualité des données.

C’est essentiel pour un système d’ingestion de données.

Un collecteur sans surveillance peut échouer silencieusement.

Ce que j’ai appris

La principale leçon tirée de Basango est qu’un système d’agrégation sérieux ne consiste pas à télécharger des pages.

La difficulté réside dans la construction d’un parcours fiable entre des informations publiques fragmentées et une connaissance structurée, digne de confiance, consultable et exploitable.

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 que l’IA doit être introduite avec prudence. Il est tentant d’utiliser immédiatement des appels à des LLM pour résoudre la classification, la recommandation et la recherche sémantique, mais le système peut alors devenir coûteux et difficile à déboguer. Une meilleure approche consiste à construire d’abord des couches déterministes, à préserver les métadonnées, à collecter suffisamment de données, puis à introduire l’IA là où elle apporte une valeur mesurable.

La base de données a pris plus d’importance que prévu. Une fois les articles collectés, le principal défi est devenu leur consultation : déduplication, indexation, filtrage, normalisation des catégories, rapports et future personnalisation. Le projet est ainsi passé d’un collecteur à un système d’information.

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.

Enfin, le tableau de bord n’est pas une couche cosmétique. Pour les systèmes qui dépendent de tâches d’arrière-plan et de sites externes, il fait partie de l’ingénierie de la fiabilité. Il indique si la chaîne fonctionne, si les données sont à jour et si la stratégie de sélection des sources est efficace.

Conclusion

Basango est un système évolutif de curation des actualités des médias congolais.

Sa principale valeur d’ingénierie ne réside pas dans la collecte des sites d’actualité, mais dans l’architecture qui l’entoure : abstraction des sources, ingestion idempotente, modélisation typée des données, classification à faible coût, consultation indexée, frontières d’API, analyse et évolution du produit vers la recherche sémantique et la recommandation.

Le système transforme un écosystème médiatique fragmenté en une chaîne structurée où les articles peuvent être collectés, normalisés, dédupliqués, classés, recherchés, analysés puis recommandés.

C’est l’idée centrale de Basango : faciliter le suivi, l’évaluation et la consultation des actualités congolaises essentielles.

Autres projets

Tout voir

2026

Leganews Pro

Conception d’une plateforme structurée de veille juridique pour le marché congolais

Voir

2025

PHP Packages Graph

Explorer l’écosystème PHP à partir des paquets publiés sur packagist.org

Voir

2025

Points of Interest

Cartographie collaborative des zones d’activité par contributions anonymes

Voir