Supabase n'est pas un "CRUD magique" qui cache une base de données quelque part. Derrière, tu as un vrai PostgreSQL, avec toute sa puissance et toutes ses responsabilités. Dans cette leçon, tu vas créer ta première table, écrire et lire des données via le SQL Editor, puis activer Row Level Security (RLS) pour que chaque utilisateur ne voie que ce qui lui appartient.
SQL en 2 minutes
Si tu as déjà touché au SQL, tu peux passer directement à la section suivante. Sinon, voici les quatre instructions dont tu vas avoir besoin dans cette leçon.
PostgreSQL est une base de données relationnelle : les données sont organisées en tables (lignes + colonnes), et le SQL est le langage pour les manipuler.
-- Créer une table "posts" avec trois colonnes
CREATE TABLE posts (
id bigint generated always as identity primary key,
user_id uuid references auth.users(id),
content text not null
);
-- Insérer une ligne
INSERT INTO posts (user_id, content)
VALUES ('123e4567-...', 'Mon premier post');
-- Lire toutes les lignes
SELECT * FROM posts;
-- Lire uniquement les posts d'un utilisateur précis
SELECT * FROM posts WHERE user_id = '123e4567-...';Quatre instructions, quatre cas d'usage. C'est tout ce dont tu as besoin pour démarrer. Un cours dédié couvrira SQL de façon plus complète :
Le SQL Editor de Supabase
Avant de créer la table, voyons où écrire ce SQL. Dans ton dashboard Supabase, le menu de gauche contient un item SQL Editor (icône de terminal ou de code). C'est un éditeur interactif connecté directement à ton instance PostgreSQL.
Tu peux y coller n'importe quelle instruction SQL et la lancer avec Run (ou Cmd+Enter / Ctrl+Enter). Les résultats s'affichent dans la moitié basse de l'écran.
Créer ta première table
Colle ce bloc dans le SQL Editor et exécute-le. Il crée une table posts avec un identifiant auto-généré, une référence vers l'utilisateur propriétaire, et un champ texte.
CREATE TABLE posts (
id bigint generated always as identity primary key,
user_id uuid references auth.users(id) on delete cascade,
content text not null,
created_at timestamptz default now()
);La colonne user_id pointe vers la table auth.users gérée par Supabase. La clause on delete cascade signifie que si un compte est supprimé, ses posts le sont aussi automatiquement.
Le problème sans RLS
Teste maintenant ce que n'importe quel client peut faire avec la clé publishable (anon key) que tu as copiée dans ton .env :
SELECT * FROM posts;Sans RLS activé, cette requête retourne tous les posts de tous les utilisateurs. Ton app frontend utilise la clé anon par défaut, et par défaut Supabase laisse passer tout le trafic vers les tables que tu as créées.
Ce n'est pas un bug, c'est le comportement attendu quand RLS est désactivé : Supabase fait confiance à ton code pour filtrer correctement. Le problème : un utilisateur mal intentionné peut appeler l'API directement avec ta clé anon et vider la table entière.
Activer RLS sur ta table
Une seule instruction SQL suffit :
Exécute-la dans le SQL Editor. Maintenant, relance SELECT * FROM posts; : tu obtiens zéro résultat, même si des lignes existent. Ce n'est pas une erreur. C'est le comportement attendu : RLS activé sans aucune policy = tout est bloqué par défaut.
C'est une approche "deny-first" : tu pars d'un accès zéro, puis tu ouvres explicitement ce que tu veux autoriser.
Écrire ta première policy SELECT
Une policy RLS est une règle SQL attachée à une table qui s'applique automatiquement comme une clause WHERE invisible. Tu en crées une avec CREATE POLICY.
Voici la policy qui autorise un utilisateur connecté à lire uniquement ses propres posts :
Décortiquons cette instruction :
ON posts FOR SELECT: s'applique aux lectures sur la tableposts.TO authenticated: uniquement pour les utilisateurs connectés (rôleauthenticated). Les visiteurs anonymes (rôleanon) ne sont pas concernés par cette policy et ne voient toujours rien.USING (...): la condition que chaque ligne doit satisfaire pour être retournée. Ici,auth.uid()renvoie l'UUID de l'utilisateur authentifié par le JWT dans la requête. Une ligne n'est retournée que si sonuser_idcorrespond.
Policies pour écrire, modifier, supprimer
La même logique s'applique aux autres opérations. Voici les trois policies complémentaires :
-- Insérer : l'utilisateur ne peut créer des posts qu'en son propre nom
CREATE POLICY "users can insert their own posts"
ON posts FOR INSERT
TO authenticated
WITH CHECK ((SELECT auth.uid()) = user_id);
-- Modifier : seulement ses propres posts, et sans changer le user_id
CREATE POLICY "users can update their own posts"
ON posts FOR UPDATE
TO authenticated
USING ((SELECT auth.uid()) = user_id)
WITH CHECK ((SELECT auth.uid()) = user_id);
-- Supprimer : seulement ses propres posts
CREATE POLICY "users can delete their own posts"
ON posts FOR DELETE
TO authenticated
USING ((SELECT auth.uid()) = user_id);Remarque la différence entre USING et WITH CHECK :
USINGs'applique aux lignes existantes : "est-ce que l'utilisateur a le droit d'accéder à cette ligne ?"WITH CHECKs'applique aux lignes nouvelles ou modifiées : "est-ce que la ligne telle qu'elle sera après l'opération respecte la règle ?"
Pour une UPDATE, les deux clauses ensemble garantissent que l'utilisateur possède la ligne avant de la modifier, et qu'il ne peut pas changer son user_id pour s'approprier un post qui ne lui appartient pas.
Tester l'isolation entre utilisateurs
Le SQL Editor te permet de simuler des requêtes en tant qu'un utilisateur précis. C'est utile pour vérifier que RLS fonctionne comme attendu avant de tester depuis ton app.
Dans le SQL Editor, tu peux définir le rôle et l'utilisateur courant via SET :
-- Simuler une requête d'un utilisateur authentifié (remplace l'UUID par un vrai)
SET role authenticated;
SET request.jwt.claims = '{"sub": "uuid-de-ton-utilisateur"}';
SELECT * FROM posts;Si tout est bien configuré, tu ne verras que les posts dont le user_id correspond à l'UUID fourni.
Sources
À côté
Concepts-ponts
De la theorie SQL (tables, lignes, cles, joins) a son application concrete dans Supabase (policies RLS, queries via JS client).
