Salta al contenuto principale

Electric SQL: Postgres-grade local-first

Articolo della serie Local-First Software Architecture. Pubblicazione prevista 22 July 2027.

Cosa ne pensi di questo articolo?

Electric SQL: Postgres-grade local-first
Local-First SoftwareFoto:Daniil KomovPexels

In Breve

Articolo della serie Local-First Software Architecture. Pubblicazione prevista 22 July 2027.

Cos'è ElectricSQL

ElectricSQL è una piattaforma open-source che sincronizza PostgreSQL con applicazioni client in real-time, creando un'esperienza local-first senza sacrificare la coerenza dei dati. A differenza degli approcci tradizionali client-server dove il database è una "fonte di verità remota", ElectricSQL permette ai client di avere una copia locale completa dei dati di loro interesse, operare offline senza dipendere dalla rete, e sincronizzarsi automaticamente con il server quando la connessione è disponibile. È costruito su PostgreSQL (non sostituisce), aggiungendo uno strato di sincronizzazione che gestisce conflitti, ordinamento cauzale e convergenza finale tra tutte le copie.

💡 Caratteristiche fondamentali

  • Sincronizzazione real-time: Push e pull bidirezionali tra client e server
  • Operatività offline: Lavora anche senza connessione, sync automatica al ripristino
  • Gestione conflitti: Risoluzione automatica con CRDTs e causalità logica
  • PostgreSQL nativo: Usa il database che già conosci, niente nuove tecnologie esotiche
  • Schema-driven: Definisci con SQL standard, il resto è automatico

Architettura di sincronizzazione

ElectricSQL si divide in tre componenti chiave: il server di sincronizzazione (Electric Server), il client JavaScript/TypeScript (ElectricClient), e il database PostgreSQL. Il flusso di sincronizzazione funziona così: quando il client modifica un record localmente, ElectricClient lo registra in una coda di sync. Periodicamente (o immediatamente se la rete è disponibile), la coda viene inviata a Electric Server. Il server applica le modifiche a PostgreSQL, verifica i conflitti, e rifioglia il risultato verso tutti i client interessati. I client ricevono gli aggiornamenti e aggiornano il database locale (SQLite o OPFS) in transazione ACID.

TypeScript - Architettura ElectricSQL
// Schema PostgreSQL (versione di verità)
CREATE TABLE items (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  title TEXT NOT NULL,
  completed BOOLEAN DEFAULT false,
  userId UUID NOT NULL REFERENCES users(id),
  createdAt TIMESTAMPTZ DEFAULT now(),
  updatedAt TIMESTAMPTZ DEFAULT now()
);

// Client TypeScript - Definisci lo schema locale
import {{ '{' }} ElectricDatabase {{ '}' }} from 'electric-sql';

const db = new ElectricDatabase('items_db');

// Sincronizza solo i record del current user
const {{ '{' }} items {{ '}' }} = await db.syncTable('items', {{ '{' }}
  where: `userId = '${{ '{' }}currentUserId{{ '}' }}'`
{{ '}' }});

// Crea un item localmente
const newItem = {{ '{' }}
  title: 'Implementare ElectricSQL',
  completed: false,
  userId: currentUserId
{{ '}' }};

await db.items.create(newItem);
// ✅ Disponibile SUBITO localmente, offline se necessario

Modello di dati locale

ElectricSQL sincronizza i dati su un database locale SQLite (browser/mobile) o OPFS (Origin Private File System) per applicazioni web. Questo database locale è una replica parziale e opportunistica del server: contiene solo i dati "abbonati" dal client. Grazie alla sincronizzazione selettiva (filtering con WHERE clause), puoi sincronizzare solo i record rilevanti—una todo-list personale per 1000 utenti non scaricherà le 1000 liste intere, ma solo quella dell'utente corrente.

TypeScript - Sincronizzazione selettiva
// Sincronizza solo i post del blog personale
const {{ '{' }} posts {{ '}' }} = await db.syncTable('posts', {{ '{' }}
  where: `authorId = '${{ '{' }}userId{{ '}' }}'`
{{ '}' }});

// Live query locale
const allPosts$ = posts.live();
allPosts$.subscribe(list => {{ '{' }}
  console.log('Post aggiornati:', list); // Re-render UI
{{ '}' }});

// Scrive offline
posts.create({{ '{' }}
  title: 'ElectricSQL Rocks',
  content: 'Lorem ipsum...',
  authorId: userId
{{ '}' }});
// ✅ Applicato ISTANTANEAMENTE nel database locale
// ⏳ Sync in background quando online

Gestione automatica dei conflitti

Uno dei problemi più spinosi nel local-first è cosa succede quando due dispositivi modificano lo stesso record offline. ElectricSQL affronta questo con CRDTs (Conflict-free Replicated Data Types) e ordinamento causale. Ogni operazione riceve un timestamp logico unico, e il server tiene traccia della causalità tra operazioni. Se due client modificano lo stesso campo, ElectricSQL applica una strategia di risoluzione (Last-Write-Wins di default, personalizzabile) e tutti i client convergono allo stesso stato finale senza perdere dati.

TypeScript - Conflitti e merge
// Client A (offline): modifica l'item
const item = await db.items.findUnique(itemId);
item.title = 'Task A - Online offline'; // Client A modifica
item.status = 'in_progress';
await db.items.update({{ '{' }}
  where: {{ '{' }} id: itemId {{ '}' }},
  data: item
{{ '}' }});

// Client B (online): modifica LO STESSO item
// (scena simultanea)
await fetch('/api/items/' + itemId, {{ '{' }}
  method: 'PATCH',
  body: JSON.stringify({{ '{' }}
    title: 'Task A - Online only', // CONFLITTO!
    status: 'completed'
  {{ '}' }})
{{ '}' }});

// Risultato: ElectricSQL CONVERGE automaticamente
// Strategia: Last-Write-Wins + merge campi
// title: 'Task A - Online only' (client B ha scritto dopo)
// status: 'completed' (tutti concordano)
// ✅ Zero data loss, zero conflitti visibili

Integrazione con le applicazioni

ElectricSQL fornisce client librerie per TypeScript, React, React Native e Svelte. L'integrazione è declarativa: usi un hook (React) o uno store (Svelte) per sottoscriverti ai cambiamenti dei dati, e il UI si aggiorna automaticamente. Non devi gestire fetch, caching, invalidazione— è tutto baked-in.

TypeScript + React - Integrazione completa
import {{ '{' }} useElectric {{ '}' }} from 'electric-sql/react';
import {{ '{' }} useState {{ '}' }} from 'react';

export function TodoList() {{ '{' }}
  const db = useElectric();
  const {{ '{' }} items {{ '}' }} = db.useQuery(
    db.items.findMany({{ '{' }} where: {{ '{' }} completed: false {{ '}' }} {{ '}' }})
  );
  const [title, setTitle] = useState('');

  const handleAdd = async () => {{ '{' }}
    await db.items.create({{ '{' }} title, completed: false {{ '}' }});
    setTitle('');
    // ✅ UI re-renders AUTOMATICAMENTE (live reactive query)
  {{ '}' }};

  return (
    <div>
      <input
        value={{ '{' }}title{{ '}' }}
        onChange={{ '{' }}(e) => setTitle(e.target.value){{ '}' }}
      />
      <button onClick={{ '{' }}handleAdd{{ '}' }}>Aggiungi</button>
      <ul>
        {{ '{' }}items?.map(item => (
          <li key={{ '{' }}item.id{{ '}' }}>{{ '{' }}item.title{{ '}' }}</li>
        )){{ '}' }}
      </ul>
    </div>
  );
{{ '}' }}

Performance e scalabilità

ElectricSQL è disegnato per scale a migliaia di client simultanei. Ogni client sincronizza solo i dati che gli servono (selective replication), il che riduce drasticamente banda di rete e storage locale. Il server utilizza PostgreSQL nativo (non replica speciale), quindi tutti gli indici, trigger e procedimenti standard SQL funzionano. Le performance di lettura locale (SQLite) sono nell'ordine dei microsecondi—centinaia di volte più veloci di una chiamata API remotale.

Confronto con altre soluzioni local-first

Altre piattaforme come Firestore, Realm, e CouchDB offrono sincronizzazione, ma hanno tradeoff diversi. Firestore è cloud-only e non standard SQL. Realm è un database mobile specializzato, non web-first. CouchDB è un documento store non relazionale. ElectricSQL si distingue per mantenere PostgreSQL come fonte di verità, permettendo di usare query SQL standard e l'intero ecosistema PostgreSQL (extension, full-text search, JSON, geospatial, ecc.). Non sei bloccato in un vendor proprietario.

Casi d'uso ideali

ElectricSQL brilla in scenari dove:

✅ Applicazioni offline-first — app di note, todo, calendario che funzionano perfettamente senza rete.
✅ Bassa latenza critica — le scritte sono istantanee nel database locale, non aspetti roundtrip al server.
✅ Banda limitata — mobile con 2G/3G, o ambienti dove la larghezza di banda è costosa.
✅ Collaborazione real-time — più utenti modificano gli stessi dati, ElectricSQL gestisce i conflitti.
✅ Applicazioni ibrida online/offline — passa da online a offline senza cambiare codice.

Best practice di implementazione

Quando usi ElectricSQL, considera questi principi:

1. Sincronizza selettivamente — Non abbonati a interi tabelle. Usa WHERE per filtrare solo i record rilevanti.
2. Comprendi la causalità — ElectricSQL preserva l'ordinamento causale, ma non l'ordinamento globale. Se A modifica X, poi B modifica Y, localmente potresti vedere Y prima di X se la latenza di rete varia.
3. Gestisci gli errori di sincronizzazione — La rete fallisce. Scrivi UI che comunica quando offline, e che riprova automaticamente.
4. Usa transazioni locali — ElectricSQL supporta transazioni ACID nel database locale. Raggruppale le operazioni correlate.

Limitazioni e considerazioni

ElectricSQL non è una soluzione universale. Limitazioni note:

❌ Richiede schema relazionale — Non funziona bene con dati gerarchici complessi o documenti annidati.
❌ Sincronizzazione opportunistica — Se il client rimane offline per giorni, il sync potrebbe essere lento al ripristino.
❌ Non supporta query join remoti — Sincronizzi tabelle, non risultati di join. Devi fare join localmente.
❌ Gestione memoria — Un database locale grande occupa memoria. Considera pagine o lazy loading.

Setup iniziale e configurazione

Partire con ElectricSQL è semplice. Installa il client npm, configura il server (self-hosted o cloud), e definirai lo schema con SQL standard.

Shell - Setup progetto ElectricSQL
# Installa il client TypeScript
npm install electric-sql

# Configura il server (Docker)
docker run -e DATABASE_URL=postgres://user:pass@postgres:5432/app \
  -e ELECTRIC_ALLOW_DIRTY_DEFAULT=true \
  electricsql/electric:latest

# Nel tuo progetto TypeScript
import {{ '{' }} ElectricDatabase {{ '}' }} from 'electric-sql';

const db = new ElectricDatabase('myapp.db');
await db.connect({{ '{' }}
  host: 'localhost',
  port: 5133,
  database: 'myapp'
{{ '}' }});

// ✅ Pronto a sincronizzare

Conclusione

ElectricSQL rappresenta un approccio maturo e pragmatico al local-first: mantiene PostgreSQL come fondamento (puoi usare tutto l'ecosistema SQL), sincronizza in real-time, risolve conflitti automaticamente, e permette ai client di operare offline senza compromessi sull'esperienza utente. Non è una soluzione universale (schema relazionale, sync opportunistica), ma per le applicazioni che rientrano nei suoi casi d'uso (offline-first, bassa latenza, banda limitata, collaborazione), è incredibilmente efficace. Se costruisci un'app moderna dove l'esperienza offline è critica e la coerenza dati è non-negoziabile, ElectricSQL merita di essere nella tua cassetta degli attrezzi.

🎯 Punti chiave

  • ElectricSQL sincronizza PostgreSQL con client locale in real-time
  • Operatività offline completa + sync automatica al ripristino
  • CRDTs e causalità logica per risoluzione conflitti zero-touch
  • Sincronizzazione selettiva: scarica solo dati rilevanti
  • Integrazione semplice con React, TypeScript, React Native
  • Ideale per offline-first, bassa latenza, collaborazione real-time

Domande frequenti

ElectricSQL richiede un database PostgreSQL dedicato?

Sì, ElectricSQL necessita di un server PostgreSQL. Non è un database nuovo, ma uno strato di sincronizzazione che si basa su PostgreSQL come fonte di verità. Puoi usare PostgreSQL self-hosted o cloud (Render, Railway, Supabase).

Cosa succede se due client modificano lo stesso record offline?

ElectricSQL usa CRDTs e ordinamento causale per convergere automaticamente. Applica una strategia di risoluzione (Last-Write-Wins di default), assicurando che tutti i client vedono lo stesso risultato finale senza conflitti visibili.

ElectricSQL funziona con SQLite nel browser?

Sì, ElectricSQL usa SQLite per il database locale in browser (via OPFS o emscripten). Offre ACID transazioni locali e performance di lettura nel range dei microsecondi.

Posso usare ElectricSQL con un'app React Native?

Sì, ElectricSQL supporta React Native. Il database locale usa SQLite nativo (via react-native-sqlite), e l'integrazione è con hook React standard.

Qual è la quantità di dati che ElectricSQL può sincronizzare?

ElectricSQL usa sincronizzazione selettiva, per cui sincronizzi solo i dati rilevanti per il client. Un'applicazione mobile con 1000 utenti non scaricherà l'intero database, ma solo i record dell'utente corrente. Lo storage locale è limitato da SQLite/browser (tipicamente GB)

Fonti e approfondimenti

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti