Aller au contenu
~/benhattab
Retour aux projets
En coursSaaS2025 — présent

Gerex Food

SaaS multi-tenant de gestion de restaurant

Plateforme SaaS multi-tenant pour la restauration : stocks, commandes, finances, alertes et reporting temps réel, avec des interfaces distinctes pour la salle et la cuisine. Une seule API sert le dashboard des restaurants et le back-office de la plateforme.

0
ligne servie sans tenant
3
applications
Rôle

Architecte & développeur — API multi-tenant, dashboard, superadmin

Stack
Laravel 13PHP 8.3Next.js 16React 19Laravel HorizonLaravel ReverbRedisDocker
Le problème

En SaaS multi-tenant, la fuite de données entre clients n'est jamais une décision — c'est un oubli. Il suffit d'une requête écrite sans le filtre `tenant_id`, dans un rapport ou un export ajouté six mois plus tard, pour qu'un restaurant voie le chiffre d'affaires d'un autre. Compter sur la discipline de chaque développeur sur chaque requête est une stratégie qui échoue toujours, tôt ou tard.

L'approche

J'ai rendu l'isolation structurelle plutôt que déclarative. Un middleware résout le tenant courant à chaque requête, un singleton le porte pour la durée du cycle, et un trait applique un scope global sur tous les modèles concernés en estampillant automatiquement `tenant_id` à la création. Le développeur n'a rien à se rappeler.

Le point clé est le comportement en cas d'absence de tenant : le scope n'ouvre pas l'accès, il le ferme. Sans tenant résoluble, la requête renvoie zéro ligne. Un bug de résolution produit une page vide — visible, corrigible — au lieu d'un déversement silencieux de toutes les données de la plateforme.

Le contournement existe, mais il est explicite : la couche d'analytics cross-tenant doit retirer les scopes globaux à la main et filtrer elle-même. Rendre le cas dangereux verbeux et le cas sûr automatique, c'est tout l'objectif.

Architecture
  1. 01

    Résolution du tenant

    Un middleware ajouté au groupe `api` lit l'en-tête `X-Tenant-Slug` (ou le paramètre de route), avec repli sur le `tenant_id` de l'utilisateur authentifié.

    TenantMiddleware

  2. 02

    Scope fail-safe

    Un scope global qui, sans tenant résoluble, renvoie zéro ligne. L'échec est fermé par défaut, pas ouvert.

    TenantScope

  3. 03

    Files & temps réel

    Laravel Horizon sur Redis pour les traitements de fond, et Laravel Reverb pour pousser commandes et alertes en direct vers la salle et la cuisine.

    Horizon · Reverb

  4. 04

    Dashboard restaurant

    Next.js 16, l'application quotidienne des équipes : stocks, commandes, finances, alertes.

    Next.js 16

  5. 05

    Superadmin plateforme

    Application séparée pour la supervision de tous les tenants, avec analytics cross-tenant explicitement déscopé.

    Next.js 16

Arbitrages

Le scope ferme au lieu d'ouvrir

L'implémentation naïve d'un scope multi-tenant ne filtre rien quand le tenant est absent — et livre donc tout. J'ai inversé la valeur par défaut : absence de tenant, absence de résultats. La seule exception est le modèle utilisateur, qui doit rester consultable pour que l'authentification puisse fonctionner avant même qu'un tenant soit connu.

Points clés
  • Isolation des tenants appliquée automatiquement par scope global, avec échec fermé par défaut
  • Stocks, commandes, finances et reporting temps réel dans une seule application
  • Interfaces distinctes pour les équipes en salle et en cuisine
  • Traitements de fond sous Horizon, temps réel via WebSockets Reverb
  • Une API unique servant le dashboard tenant et le superadmin plateforme