Eseguire SQLite nel Browser con OPFS e Web Workers
Scritto da Francesco Di Donato 12 marzo 2026 5 minuti di lettura
Viviamo in un mondo in cui il deployment di un database significa generalmente attivare un server, gestire stringhe di connessione, configurare ORM e accettare inevitabilmente la latenza di rete. Per molto tempo, questo è stato semplicemente il costo da pagare.
Ma come solo-founder, il mio obiettivo non è vendere tempo — è costruire asset. E per costruire asset in fretta, devo eliminare l’attrito. Più velocemente riesco a passare dall’idea a un MVP funzionante, meglio è. Ciò significa rimuovere tutto ciò che non è strettamente necessario.
Recentemente ho creato un boilerplate che elimina completamente il database backend dall’equazione, senza sacrificare la potenza del SQL. In questo post troverai una guida pratica per eseguire un motore SQLite ufficiale direttamente nel browser, sfruttando l’Origin Private File System (OPFS) e i Web Worker.
Zero server. Latenza in microsecondi. 100% locale e privato.
Vediamo come funziona, perché è una svolta per gli sviluppatori indie, e le trappole architettoniche da evitare.
1. Il Problema: Il Collo di Bottiglia del Thread Principale
Eseguire SQLite nel browser via WebAssembly (WASM) non è del tutto nuovo. La vera sfida è sempre stata la persistenza dei dati.
Lo storage standard del browser (LocalStorage, IndexedDB) è troppo lento, troppo limitato, o fondamentalmente incompatibile con il modo in cui un motore relazionale sincrono si aspetta di leggere e scrivere byte su disco.
Entra in scena OPFS (Origin Private File System). Offre un file system sandboxed e altamente ottimizzato con accesso sincrono a livello di byte — esattamente ciò di cui SQLite ha bisogno.
Il problema: l’accesso sincrono blocca il thread. Se eseguiamo SQLite con OPFS sul thread principale del browser, l’interfaccia si congelerà a ogni query complessa. È come fare lavori pesanti in mezzo a un’autostrada trafficata.
2. Il Blueprint
Per risolvere questo problema, dobbiamo isolare il database. Spostiamo il motore SQLite e il file system OPFS all’interno di un Web Worker.
Pensa al Web Worker come a una stanza di vetro sigillata dove il pesante processamento dei dati avviene in modo sicuro e silenzioso, completamente disaccoppiato dall’interfaccia.
Ecco il flusso:
In questo modo, l’interfaccia rimane fluida a 60FPS, indipendentemente da quanto stia lavorando il database.
3. L’Implementazione Passo per Passo
Vediamo il codice concreto.
Step 1: La Sala Macchine (worker.ts)
All’interno del worker, inizializziamo il pacchetto ufficiale @sqlite.org/sqlite-wasm. Creiamo un’istanza di OpfsDb, che collega automaticamente SQLite al file system ad alte prestazioni del browser.
import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
let db: any;
export async function initDb() {
if (db) return;
const sqlite3 = await sqlite3InitModule();
// La magia accade qui: usiamo OPFS per la persistenza
if (sqlite3.opfs) {
db = new sqlite3.oo1.OpfsDb("/my-database.sqlite3");
console.log("OPFS disponibile, database persistente creato.");
} else {
db = new sqlite3.oo1.DB("/my-database.sqlite3", "ct");
console.warn("OPFS non disponibile, database transiente creato.");
}
}Con questa configurazione, anche se l’utente aggiorna la pagina o chiude il browser, i dati sopravvivono.
Step 2: Il Bridge con Comlink
I Web Worker comunicano tramite postMessage, che può trasformarsi velocemente in un groviglio ingestibile di event listener.
Useremo invece Comlink di Google Chrome Labs. Comlink crea un bridge RPC (Remote Procedure Call) che espone le funzioni del worker al thread principale come normali funzioni asincrone.
// src/db.ts
import * as Comlink from "comlink";
import type { DbWorker } from "./worker";
// Istanzia il worker
const worker = new Worker(new URL("./worker.ts", import.meta.url), {
type: "module",
});
// Avvolgilo con Comlink per ottenere un'API tipizzata e asincrona
export const dbWorker = Comlink.wrap<DbWorker>(worker);Nel codice dell’interfaccia, interrogare il database diventa semplicemente:
await dbWorker.exec(
"CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)",
);Step 3: Il Buttafuori di Sicurezza (COOP/COEP)
È qui che la maggior parte degli sviluppatori si blocca. Per ottenere le massime prestazioni con OPFS, il modulo WASM di SQLite richiede una funzionalità chiamata SharedArrayBuffer.
I browser limitano severamente SharedArrayBuffer a causa di vulnerabilità di sicurezza come Spectre. Per sbloccarlo, il sito deve essere servito in un contesto cross-origin isolato.
Lo si ottiene applicando specifici header HTTP:
Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp
Se usi Vite per lo sviluppo, li aggiungi semplicemente al tuo vite.config.ts:
// vite.config.ts
export default defineConfig({
server: {
headers: {
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Embedder-Policy": "require-corp",
},
},
});La Trappola: E in produzione? Se effettui il deploy su un host statico come Cloudflare Pages o GitHub Pages, potresti non avere la possibilità di impostare header personalizzati facilmente.
La Soluzione: Nel mio boilerplate ho incluso un fallback chiamato coi-serviceworker.js. È un minuscolo Service Worker che intercetta le richieste del browser e inietta dinamicamente gli header COOP/COEP necessari. È un salvavita per l’hosting statico.
4. Perché Questo Conta
Affrontare l’architettura con calma e feroce dignità significa rifiutare la complessità non necessaria.
Spostando il database all’edge — letteralmente nel browser dell’utente — otteniamo diversi vantaggi:
- Zero Costi Cloud: Niente RDS, niente connection pooling, niente problemi di scaling.
- Privacy Totale: I dati dell’utente non lasciano mai il suo dispositivo.
- Offline First: L’app funziona perfettamente in aereo o in metropolitana.
Non è solo un esperimento divertente; è un blueprint pratico per costruire applicazioni local-first. Che tu stia costruendo un habit tracker, un editor markdown o un tool di AI workflow, avere un database SQL relazionale completo nel client ti permette di rilasciare un MVP in giorni, non mesi.
Smetti di modificare stringhe di connessione. Inizia a costruire.
Cosa ne pensi? Hai già sperimentato con OPFS e WASM? Condividi i tuoi pensieri o scrivimi su X.
Se vuoi usare questa architettura, puoi trovare il boilerplate completo (licenza MIT) nel mio repository.