Posted on

Le schéma du Cellier dans Supabase : les tables commandes, profils, lignes_commande, cuvees et domaines, et les liens qui les relient. Le schéma du Cellier : cinq tables et leurs liens. Le seul aperçu de l’intérieur de la valise de toute la série.

J’ai construit un backend complet avec Supabase — schéma, règles métier, authentification, paiement — sans frontend ni infrastructure à administrer. Je l’explore en profondeur, brique par brique, et je suis loin d’avoir fait le tour : ces posts sont mon carnet d’apprentissage, documenté au fil de l’eau.

Le terrain de jeu : Le Cellier, une cave à vin en ligne fictive mais crédible — elle pourrait tourner pour de vrai. 130 domaines, plus de 14 000 cuvées, des comptes clients, des commandes, un paiement Stripe.

Cet article clôt une série publiée sur deux semaines autour de Supabase. J’y reprends, jour après jour, le récit complet d’une expérience que j’ai partagée au fil des posts — chaque étape avec sa démo en vidéo.

Jour 1 — La valise numérique

J’ai commencé par poser le cadre, avant la moindre ligne de code. Construire vite, aujourd’hui, on sait faire : une interface, un workflow, une automatisation se montent en quelques heures et se remplacent sans douleur. Mais il y a des briques qu’on ne change pas tous les six mois — celles qui portent les données et les règles métier. Celles-là doivent tenir dans le temps.

D’où l’image qui m’a servi de boussole : une valise numérique. Autour d’elle, les outils changent et se multiplient ; à l’intérieur, le contenu évolue, mais dans un écosystème cohérent qui le garde lisible. Pour un projet, cette valise transporte non seulement des données, mais aussi les règles qui les gouvernent. Les enjeux montent d’un cran — sécurité, confidentialité — et l’exigence avec.

Et traverser le temps ne tient pas qu’à la robustesse : ça demande de la souveraineté. Des formats ouverts, exportables, des briques qu’on peut reprendre ailleurs le jour où l’outil qui les rassemble disparaît. Sinon, la valise est verrouillée chez quelqu’un d’autre.

C’est là qu’arrive Supabase — et ce qui m’a décidé, ce n’est pas l’outil en lui-même, mais les briques open source qu’il assemble, toutes reconnues dans leur domaine : PostgreSQL pour les données (référence mondiale depuis trente ans), PostgREST pour exposer la base en API REST, Auth pour l’authentification, Storage compatible S3 pour les fichiers — et d’autres encore. Supabase n’est pas la valise : c’est l’assemblage qui la forme. Le jour où je change d’assemblage, les briques restent. Mes données, mon schéma, mes règles aussi.

Le tableau de bord du projet Supabase : base de données, PostgREST, Auth, Realtime, Storage et Edge Functions, tous en état sain, sur une instance hébergée à Paris.

Jour 2 — Première requête : le catalogue répond

Le Cellier s’expose en API REST, un standard que presque tous les outils savent parler : un terminal, du code, n’importe quel créateur d’interface. Moi, je dialogue avec depuis Bruno — un client local et open source — donc tout reste sur ma machine.

Pour entrer, deux choses : l’adresse du projet et une clé publique. Cette clé n’est pas un mot de passe ; elle franchit juste la première porte — « je suis bien un client de ce projet ». Ce que je pourrai lire ou écrire ensuite, c’est la RLS — les règles d’accès ligne par ligne — qui le décide. La démo le montre : sans clé, refus ; avec, le catalogue répond — 14 160 cuvées et 130 domaines. Et pour les compter, une petite fonction dédiée plutôt que d’ouvrir toute l’API au comptage : n’exposer que ce dont on a besoin, rien de plus — dès le premier geste.

Jour 3 — Chercher, filtrer, paginer : tout dans l’URL

Aucune fonction écrite, aucune vue préparée : tout se joue dans la construction de l’URL. D’abord paginer — je demande les cuvées 100 par 100 et la base me renvoie le total ; avec ces deux nombres, tourner les pages se fait tout seul. Ensuite affiner : un cru entre 30 et 80 €, dans un millésime récent, du moins cher au plus cher. J’écris la demande morceau par morceau dans l’URL, et la base renvoie exactement ça. Ici, « cru » n’est pas un filtre prévu d’avance : c’est juste un mot que je cherche dans le nom de la cuvée et que je glisse dans l’adresse, comme dans une barre de recherche.

Enfin relier : je veux le domaine de chaque cuvée — son producteur, sa région. C’est une jointure ; j’ajoute un mot, « domaine », et chaque vin arrive avec le sien, sans avoir à préciser comment. La base sait déjà qui est lié à qui. Chercher, trier, borner, relier : tout est là, de série — sur plus de 14 000 lignes, en une fraction de seconde.

Jour 4 — Inscription et double authentification

L’inscription est étonnamment simple : un email, un mot de passe, et en retour un jeton — une sorte de bracelet d’entrée qui dit « c’est bien moi ». Plus tard, se réidentifier sera le même geste. Mais un mot de passe, même solide, reste un seul rempart. Alors je suis allé plus loin : la double authentification, le fameux code à six chiffres — et toute sa mécanique tient dans l’API.

Trois appels suffisent. L’enrôlement, où la base prépare le second facteur (elle me renvoie le secret, que je colle dans mon application d’authentification). Le défi, où je la préviens que je vais prouver mon identité. La vérification, où je tape les six chiffres affichés sur mon téléphone. En retour, un jeton de niveau supérieur (aal2) : c’est lui qui pourra ouvrir l’accès aux données protégées. Le tout en une minute, en direct dans Bruno, sans montage. Note sécurité : jetons et secret s’affichent en clair à l’écran — ce qu’on ne fait jamais en vrai — mais c’est un compte de test sans aucun privilège, et ces jetons sont de courte durée.

Jour 5 — Plusieurs niveaux d’accès

Une seule table, profils, un même utilisateur, trois comportements. Sans jeton, la lecture de mon profil ne renvoie rien. Avec mon jeton d’entrée (aal1), il apparaît, prénom et nom encore à remplir. Mais quand je tente de le modifier : refus. Mon jeton prouve qui je suis, la règle veut davantage pour écrire — un second facteur que je n’ai pas encore prouvé dans cette session. (Côté sécurité, ce refus, je pourrais le rendre muet face à quelqu’un qui sonde ; ici je l’affiche parce que je montre.)

Alors je passe au second facteur — défi, vérification, six chiffres — et la base me rend un jeton aal2, celui qui autorise l’écriture. Je rejoue ma modification : acceptée. Une règle qui change de comportement selon le niveau d’authentification, le tout dans la base, sans aucun middleware. Exiger ça pour un prénom, c’est exagéré, j’en conviens ; ce qui compte, c’est le mécanisme.

Jour 6 — Stockage de fichiers

Le Storage de Supabase, compatible S3, se pilote depuis l’API comme le reste. Sans jeton, déposer un fichier est refusé d’emblée. Je me connecte : avec mon jeton, l’envoi passe. Sophie dépose deux fichiers, Pierre — un autre compte — en dépose un. Trois fichiers dans le bucket.

Vient le moment que je trouve fort : je demande la liste de tous les fichiers. Avec le jeton de Sophie, deux ; avec celui de Pierre, un seul ; sans jeton, rien. La même requête, mot pour mot — aucun filtre, je ne demande pas « mes » fichiers, mais tous. C’est la RLS qui décide ce qui existe ou non pour chacun : le jeton change, la réalité avec lui. Un détail que je n’ai pas soigné dans la démo mais qui compte en vrai : ranger chaque fichier dans un dossier au nom de son propriétaire, pour que deux « photo.jpg » ne se télescopent jamais.

Jour 7 — Le rôle qui voyage dans le jeton

Les rôles. Dans Le Cellier, trois types d’utilisateurs : client, sommelier, administrateur. J’ai traité le sujet de deux façons.

La première, classique : un enum dans la table profils — client ou sommelier — protégé par un GRANT. Seul l’admin peut promouvoir, impossible de se proclamer sommelier.

La seconde m’a bien plus marqué : le rôle d’administrateur vit dans le jeton lui-même. Ce jeton est signé par Supabase, et une signature, n’importe qui peut la vérifier avec la clé publique. Dans la démo, deux utilisateurs se connectent — un client de la cave, un administrateur — et j’envoie leur jeton à un webhook : le premier est refusé, le second accepté. Ce qui reçoit, c’est un scénario n8n, une application totalement séparée du Cellier : elle récupère la clé publique du projet, vérifie le jeton, lit s’il porte le rôle admin, et tranche — sans jamais interroger la base.

Et c’est là tout l’intérêt : une application qui n’a rien à voir avec Le Cellier sait authentifier mes utilisateurs et lire leur rôle, juste en vérifiant une signature. L’authentification Supabase peut donc servir d’identité à d’autres applications que celle pour laquelle elle a été pensée — un rôle qui voyage comme une preuve signée, lisible partout. Ce qu’un simple enum en base ne permet pas.

Jour 8 — Passer commande

Le morceau le plus dense de la série. Passer commande, ce n’est pas ajouter une ligne dans une table : c’est un enchaînement de règles métier qui vivent toutes dans PostgreSQL. D’abord, être connecté — sans utilisateur authentifié, aucune commande. On crée une commande vide, dont le client doit être l’utilisateur connecté : pas de commande au nom d’un autre.

Une fonction acheter est ensuite la seule porte d’écriture des lignes de commande — impossible d’en glisser une à la main. Elle vérifie l’appartenance, le statut ouvert, le stock disponible. Elle décrémente le stock dans la foulée : pas de survente. Le prix est figé par la base, jamais reçu du client. Tout tient en trois paramètres : la commande, la cuvée, le nombre de bouteilles.

Petit bonus métier : un trigger offre automatiquement une bouteille dès que la commande passe 100 €. Personne ne l’a demandée — elle apparaît seule dans l’état de la commande, qu’on lit via une seconde fonction. Quant au total, il n’est jamais stocké mais recalculé à la volée : une copie figée finirait par diverger des lignes.

Jour 9 — Paiement Stripe sécurisé

La commande d’hier est restée en attente. Il est temps de payer. Au programme : un vrai paiement Stripe, depuis Bruno.

Un appel API déclenche le paiement. Côté serveur, une Edge Function — du code hébergé par Supabase — recalcule le montant (jamais envoyé par le client : impossible de payer 1 € une commande à 100 €) et demande à Stripe une page de paiement Checkout, dont elle me renvoie l’URL. J’ouvre la page, je paie avec une carte de test. Stripe me redirige vers une confirmation « Paiement réussi »… servie elle aussi par Supabase. Je rejoue la requête d’état : la commande est passée à « payée », sans que personne n’y ait touché à la main.

En coulisses, ce n’est pas ce retour qui valide — il est falsifiable. C’est Stripe qui notifie Supabase via un webhook, avec un message signé. La fonction vérifie la signature avant de toucher à la commande. Pas de signature valide, pas de statut « payée ». La clé secrète Stripe n’a jamais quitté Supabase.

Jour 10 — Conclusion

Dix posts, brique après brique. Inscription, double authentification, niveaux d’accès, recherche paginée, fichiers isolés par propriétaire, commandes avec garde-fous métier, rôles vérifiables hors Supabase, paiement Stripe complet. Le tout piloté par API, sans front, sans infrastructure à administrer, et sans qu’aucune clé sensible ne circule où elle ne devrait pas.

Je me suis dit plusieurs fois « ça, ça va être compliqué ». La double authentification, le webhook signé Stripe, le rôle portable vérifié par un outil externe. À chaque fois, ça tenait en quelques requêtes. La seule limite rencontrée : pas de page HTML rendue sur le domaine par défaut — ma confirmation s’est affichée en texte brut. Une protection anti-abus, compréhensible.

Et pour la première et seule fois de la série, un aperçu de l’intérieur de la valise : le schéma des tables reliées — en couverture — et, ici, la table des commandes : la première ligne est celle de la démo, passée à « payée » au post 9. Les données, le schéma, les règles métier : tout est exportable, comme promis au premier jour. La série s’arrête ici, la cave reste pleine.

La table des commandes dans l’éditeur Supabase : 101 lignes, la première au statut « payee ».

En complément — Le jeton JWT

On a beaucoup parlé de JWT dans cette série. À l’écran, ça ressemble à une longue chaîne de caractères — on imagine un truc bien chiffré. Pas du tout.

J’ai glissé une donnée bidon dans le jeton de l’admin — mon_secret — et je l’ai collé dans jwt.io. Le site affiche tout en clair : protocole de signature, adresse du projet, identité, rôle, et le « secret » ironique. Un JWT, c’est du base64 signé, pas chiffré. La signature prouve l’authenticité, mais le contenu est lisible par tout le monde. On n’y met donc jamais de secret.

Une nuance sur cette signature, parce qu’elle se comprend souvent de travers : pour la valider, jwt.io va chercher la clé publique à l’adresse inscrite dans le jeton. Ça prouve que le jeton est cohérent avec l’adresse qu’il affiche, pas qu’il vient du bon projet — c’est le jeton lui-même qui désigne où aller vérifier. En vrai, on contrôle toujours avec la clé qu’on détient déjà de son côté.


Une belle expérience, très formatrice : un backend qui pourrait être bien réel, monté brique par brique. Je n’ai fait qu’effleurer la surface des possibilités — il me reste énormément à explorer. Supabase m’a étonné par le peu de limites que j’y ai rencontrées, et c’est précisément ce qui m’impressionne autant que ça me plaît. À suivre, dans d’autres aventures.

Table of Contents