Changer le thème

Conception de base de données Supabase : schéma, relations et Row Level Security

Easton editorial illustration: database service control desk

Vous fixez l’avertissement rouge dans le Dashboard Supabase — « RLS not enabled ». Des questions vous traversent l’esprit : les articles des utilisateurs peuvent-ils fuiter ? Cette relation de clé étrangère est-elle correcte ? Comment modéliser une relation plusieurs-à-plusieurs ?

Au début avec Supabase, nous avons accumulé les pièges. Oublier d’activer RLS après la création d’une table — n’importe qui pouvait lire toutes les données. Clés étrangères mal conçues : suppression d’un utilisateur, ses articles restaient. Table de liaison pour une relation plusieurs-à-plusieurs ? Nous avons même tenté de stocker des tableaux — un désastre total.

Après plusieurs mois de tâtonnements, nous avons enfin saisi la logique de conception de base de données Supabase. Cet article rassemble ces enseignements.

1. Structure des tables : conventions de nommage PostgreSQL

1.1 Conventions de nommage : snake_case est la voie à suivre

PostgreSQL a une particularité : sans guillemets doubles, il convertit les identifiants en minuscules. Avec des guillemets, il respecte strictement ce que vous écrivez.

Qu’est-ce que cela implique ? Avec du camelCase (UserProfile), il faut ajouter des guillemets partout. Trop pénible.

La convention de la communauté PostgreSQL est donc : snake_case (séparateur underscore), noms de tables au pluriel, noms de colonnes au singulier.

-- ✅ Recommandé
CREATE TABLE users (
  id UUID PRIMARY KEY,
  email TEXT UNIQUE,
  created_at TIMESTAMPTZ
);

1.2 Choix des types de colonnes : ne restez pas bloqué dans la logique MySQL

Erreur 1 : utiliser VARCHAR au lieu de TEXT

Dans PostgreSQL, TEXT et VARCHAR ont exactement les mêmes performances. La seule différence : VARCHAR(n) impose une limite de longueur. Sauf besoin explicite de limiter, utilisez directement TEXT.

Erreur 2 : utiliser TIMESTAMP au lieu de TIMESTAMPTZ

TIMESTAMP ne stocke pas le fuseau horaire. Serveur aux États-Unis, utilisateurs en Chine — les horaires affichés deviennent incohérents. TIMESTAMPTZ gère automatiquement la conversion.

Erreur 3 : utiliser SERIAL au lieu de UUID

SERIAL est un entier auto-incrémenté. Parfait pour une application monolithique, mais source de conflits en système distribué. UUID garantit l’unicité globale.

2. Trois types de relations : un-à-un, un-à-plusieurs, plusieurs-à-plusieurs

2.1 Un-à-un : ajoutez UNIQUE

Cas le plus courant : utilisateur et profil.

CREATE TABLE profiles (
  id UUID PRIMARY KEY,
  user_id UUID UNIQUE REFERENCES users(id) ON DELETE CASCADE,
  bio TEXT
);

Point clé : user_id UUID UNIQUE. La contrainte UNIQUE garantit qu’un utilisateur ne possède qu’un seul profil.

2.2 Un-à-plusieurs : la clé étrangère classique

Auteurs et livres. Un auteur peut écrire plusieurs livres.

CREATE TABLE books (
  id UUID PRIMARY KEY,
  author_id UUID REFERENCES authors(id) ON DELETE CASCADE,
  title TEXT
);

Lors des requêtes, le SDK Supabase JS permet de récupérer directement les données associées par imbrication.

2.3 Plusieurs-à-plusieurs : la table de liaison est essentielle

Étudiants et cours. Un étudiant peut suivre plusieurs cours, un cours accueille plusieurs étudiants.

Solution : créer une table de liaison.

CREATE TABLE enrollments (
  student_id UUID REFERENCES students(id) ON DELETE CASCADE,
  course_id UUID REFERENCES courses(id) ON DELETE CASCADE,
  PRIMARY KEY (student_id, course_id)
);

3. Row Level Security : la base de données comme gardien

3.1 La philosophie du « refus par défaut » de RLS

La première fois avec Supabase, nous avons créé une table posts et interrogé le frontend avec la clé anon. Résultat — toutes les données sont revenues. Choc.

Supabase n’active pas Row Level Security (RLS) par défaut. Sans RLS, toute personne disposant de la clé anon peut lire et écrire toutes les données.

Première règle absolue : activez RLS immédiatement après la création d’une table.

ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

RLS activé — ce n’est pas fini. Sans politique, c’est « refuser tout accès ». Il faut créer au moins une politique.

3.2 Syntaxe des politiques : USING et WITH CHECK

  • USING : filtre les lignes existantes (SELECT, UPDATE, DELETE)
  • WITH CHECK : valide les nouvelles lignes (INSERT, UPDATE)

3.3 Quatre modèles de politiques courants

Modèle 1 : l’utilisateur accède à ses propres données

CREATE POLICY "Users manage own data"
ON posts FOR ALL
TO authenticated
USING (user_id = auth.uid());

Modèle 2 : données publiques + privées

Les articles publiés sont visibles par tous, les brouillons uniquement par l’auteur.

Modèle 3 : isolation multi-tenant

Les membres d’une équipe n’accèdent qu’aux données de leur équipe.

Modèle 4 : contrôle par rôles RBAC

Les administrateurs disposent de permissions spéciales.

4. Optimisation des performances RLS

4.1 Le tueur de performances : sous-requête exécutée sur chaque ligne

Une sous-requête dans une politique RLS s’exécute sur chaque ligne. 100 000 lignes, sous-requête vérifiant l’appartenance à une équipe — timeout de 3 minutes.

4.2 Optimisation 1 : ajouter des index

Les colonnes utilisées dans les politiques RLS doivent être indexées.

CREATE INDEX idx_posts_user_id ON posts(user_id);

Test officiel Supabase : sans index 450 ms, avec index 45 ms. Gain de 10×.

4.3 Optimisation 2 : fonctions SECURITY DEFINER

Encapsulez la sous-requête dans une fonction, exécutée une seule fois.

CREATE OR REPLACE FUNCTION user_teams()
RETURNS SETOF UUID
LANGUAGE SQL SECURITY DEFINER STABLE
AS $$ SELECT team_id FROM team_members WHERE user_id = auth.uid(); $$;

5. Cas pratiques

5.1 Système de blog : articles, catégories, tags

Implémentation complète incluant schéma de tables, politiques RLS et configuration des index.

5.2 SaaS multi-tenant : collaboration en équipe

Isolation des données par équipe, accès des membres, contrôle des permissions administrateur.

Résumé

Points essentiels :

  • Nommage snake_case
  • Clé primaire UUID, chaînes en TEXT, dates en TIMESTAMPTZ
  • RLS obligatoire
  • Index + fonctions SECURITY DEFINER pour l’optimisation

FAQ

Faut-il activer RLS immédiatement après la création d'une table ?
Oui, c'est une règle de sécurité absolue. Supabase n'active pas RLS par défaut : toute personne disposant de la clé anon peut lire et écrire toutes les données.
Pourquoi PostgreSQL recommande-t-il snake_case ?
PostgreSQL convertit les identifiants non quotés en minuscules. Avec du camelCase, il faut ajouter des guillemets doubles à chaque occurrence — trop contraignant.
Pourquoi éviter FOR ALL dans les politiques RLS ?
FOR ALL est moins performant que quatre politiques séparées. Les politiques distinctes permettent à PostgreSQL d'optimiser l'utilisation des index selon l'opération.
Comment vérifier l'état de RLS ?
Dans le Dashboard Supabase, page Database, consultez l'état RLS de chaque table. Le rouge indique que RLS n'est pas activé.

5 min de lecture · Publié le: 4 avr. 2026 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog