Supabase self-hosted + React Native Expo
Contexte
Intégration d'une instance Supabase self-hosted comme backend d'une app React Native (Expo). Supabase tourne sur le même VPS que le serveur de dev Expo, exposé via un domaine avec SSL.
Limitations du self-hosted vs Supabase Cloud
| Fonctionnalité | Self-hosted | Cloud |
|---|---|---|
| API REST (PostgREST) | ✅ | ✅ |
| Auth | ✅ | ✅ |
| Storage | ✅ | ✅ |
| Realtime | ✅ | ✅ |
| Edge Functions | ✅ | ✅ |
| Multi-projets | ⚠️ manuel | ✅ |
| Backups automatiques | ❌ à configurer | ✅ |
| Support officiel | ❌ | ✅ |
| Monitoring avancé | ❌ | ✅ |
| Mises à jour | manuelles | automatiques |
Le self-hosted est pleinement fonctionnel pour une app en production. Les contraintes principales sont opérationnelles (backups, mises à jour, haute dispo) et non techniques.
Clés API — comprendre la différence
Supabase expose deux clés JWT :
anon key — clé anonyme, destinée au client (app mobile, frontend). Elle est intégrée dans le code de l'app et donc techniquement exposée. La sécurité repose sur les policies RLS, pas sur le secret de cette clé.
service_role key — clé admin qui bypass RLS complètement. Ne doit jamais apparaître dans le code client. Réservée aux scripts serveur et aux tâches d'administration.
Pour retrouver les clés sur un self-hosted :
cat ~/supabase/docker/.env | grep -E "ANON_KEY|SERVICE_ROLE"
Un token JWT Supabase commence toujours par eyJhbGci.... Pour vérifier le rôle d'un token, le décoder sur jwt.io — le payload doit contenir "role": "anon" ou "role": "service_role".
Créer une table
Dans le SQL Editor de Supabase :
create table colis (
id uuid default gen_random_uuid() primary key,
expediteur text not null,
destinataire text not null,
date_envoi date not null,
lieu text,
created_at timestamptz default now()
);
alter table colis enable row level security;
create policy "public access" on colis for all using (true) with check (true);
La policy public access autorise lecture et écriture sans authentification — correct pour du dev, à restreindre en prod selon les besoins.
Appels API depuis React Native
Supabase expose une API REST standard via PostgREST. Pas besoin du SDK officiel — un simple fetch suffit.
Helper générique
const SUPABASE_URL = 'https://TON_DOMAINE_SUPABASE'
const SUPABASE_ANON_KEY = 'TON_ANON_KEY'
async function sbFetch(path, opts = {}) {
const { prefer, ...fetchOpts } = opts
const res = await fetch(`${SUPABASE_URL}/rest/v1/${path}`, {
headers: {
apikey: SUPABASE_ANON_KEY,
Authorization: `Bearer ${SUPABASE_ANON_KEY}`,
'Content-Type': 'application/json',
Prefer: prefer || 'return=representation',
},
...fetchOpts,
})
if (!res.ok) throw new Error(await res.text())
return res.json()
}
Lire des données
const data = await sbFetch('colis?order=created_at.desc&limit=50', { prefer: '' })
Insérer une ligne
await sbFetch('colis', {
method: 'POST',
body: JSON.stringify({
expediteur: 'Alice',
destinataire: 'Bob',
date_envoi: '2026-05-03',
lieu: 'Lyon',
}),
})
Pattern complet dans un composant
import { useState, useEffect } from 'react'
import { ActivityIndicator } from 'react-native'
export default function App() {
const [entries, setEntries] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
const loadEntries = async () => {
try {
const data = await sbFetch('colis?order=created_at.desc&limit=50', { prefer: '' })
setEntries(data)
} catch (err) {
setError('Erreur de connexion à Supabase')
} finally {
setLoading(false)
}
}
useEffect(() => { loadEntries() }, [])
if (loading) return <ActivityIndicator />
// ...
}
Row Level Security (RLS)
RLS est la barrière de sécurité principale. C'est ce qui détermine ce que l'anon key peut faire.
-- Lecture publique, écriture authentifiée uniquement
create policy "lecture publique" on colis
for select using (true);
create policy "ecriture authentifiee" on colis
for insert with check (auth.role() = 'authenticated');
-- Restreindre à l'utilisateur propriétaire
create policy "acces proprietaire" on colis
for all using (auth.uid() = user_id);
Considérations production
Backups Postgres
Les backups ne sont pas automatiques en self-hosted. Mettre en place un cron :
# Dump quotidien vers un répertoire local
0 2 * * * pg_dump -U postgres nom_base | gzip > /backups/$(date +\%Y\%m\%d).sql.gz
# À coupler avec un envoi vers stockage distant (Backblaze, S3, etc.)
Haute disponibilité
Un VPS unique est un point de défaillance unique (SPOF). Acceptable selon le SLA cible. Pour une disponibilité critique, envisager un VPS de secours avec réplication Postgres (streaming replication).
Scalabilité
Le vrai goulot d'étranglement sur une app CRUD sera Postgres. Anticiper :
- Index sur les colonnes fréquemment filtrées
EXPLAIN ANALYZEsur les requêtes lentes- Connection pooling via PgBouncer (inclus dans Supabase)
Mises à jour Supabase self-hosted
cd ~/supabase/docker
docker compose pull
docker compose up -d
Consulter les release notes avant chaque mise à jour — certaines nécessitent des migrations manuelles.
Prochaines étapes
- Authentification utilisateurs (Supabase Auth + JWT)
- Policies RLS adaptées aux utilisateurs authentifiés
- Backups automatiques Postgres
- Génération APK Android via EAS Build
No comments to display
No comments to display