--- title: Sync Engines description: Comparison of sync engines for local-first apps including ElectricSQL, Zero, PowerSync, Replicache, LiveStore, and Triplit tags: [ sync, electric, zero, powersync, replicache, livestore, triplit, comparison, selection, ] --- ## Comparison Table | Feature | ElectricSQL | Zero | PowerSync | Replicache | LiveStore | Triplit | | ------------------- | ----------------------- | --------------- | --------------------- | --------------- | -------------------- | --------------------- | | DB backend | Postgres | Postgres | Postgres, MongoDB | Any | SQLite (client-only) | Built-in (Triplit DB) | | Sync model | Read-only shapes | Full read+write | Read sync + write API | Push/pull | Event-sourced | Full read+write | | Conflict resolution | Server-wins (your API) | Built-in | Built-in | Custom (server) | Event replay | Built-in (LWW) | | Client storage | In-memory / TanStack DB | IndexedDB | SQLite (WASM) | IndexedDB | SQLite (OPFS) | IndexedDB | | React integration | TanStack DB, hooks | Custom hooks | React hooks | React hooks | Framework-agnostic | React hooks | | License | Apache 2.0 | ISC | Apache 2.0 | BSL 1.1 | MIT | AGPL / Commercial | | Maturity | Production | Early | Production | Production | Early | Production | | Bundle size | Small (shapes client) | Medium | Medium (SQLite WASM) | Small | Medium (SQLite WASM) | Medium | ## ElectricSQL Postgres-native sync engine that streams partial replication (Shapes) from Postgres to clients. The read path syncs data to the client via Shapes. The write path is your own API — Electric does not sync writes back to Postgres. **Key differentiator:** Uses Postgres logical replication directly. No separate sync server to manage. Reads sync automatically; writes go through your existing API. **Best fit:** Apps with an existing Postgres backend that want to add real-time sync for reads while keeping server-authoritative writes. ```ts import { ShapeStream, Shape } from '@electric-sql/client'; const stream = new ShapeStream({ url: 'http://localhost:3000/v1/shape', params: { table: 'todos', where: 'completed = false', }, }); const shape = new Shape(stream); shape.subscribe((data) => { console.log('Todos:', [...data.values()]); }); ``` With TanStack DB integration: ```ts import { createCollection, createTanStackDB } from '@tanstack/db'; import { ElectricProvider } from '@tanstack/db/electric'; const todos = createCollection({ id: 'todos', schema: todoSchema, sync: { provider: new ElectricProvider({ url: 'http://localhost:3000/v1/shape', table: 'todos', }), }, }); const db = createTanStackDB({ collections: { todos } }); // Reads: local, reactive, instant const activeTodos = db.useQuery((q) => q.from('todos').where('completed', '=', false), ); // Writes: go through your API, sync back via Electric async function createTodo(todo: NewTodo) { db.mutate.todos.insert({ id: crypto.randomUUID(), ...todo, }); await fetch('/api/todos', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(todo), }); } ``` ## Zero Full-stack sync engine with both read and write sync. Uses Postgres as the backend and provides a custom query language for the client. Built by the team behind Replicache. **Key differentiator:** True read+write sync with built-in conflict resolution. Single system handles both directions. **Best fit:** New apps that want full local-first with minimal custom sync code and are comfortable with an early-stage project. ```ts import { Zero } from '@rocicorp/zero'; const z = new Zero({ userID: 'user-123', server: 'http://localhost:4848', schema, kvStore: 'idb', }); // Reads: reactive queries const todos = z.query.todo .where('completed', '=', false) .orderBy('createdAt', 'desc') .materialize(); todos.addListener((data) => { console.log('Todos:', data); }); // Writes: sync automatically await z.mutate.todo.insert({ id: crypto.randomUUID(), title: 'New todo', completed: false, }); ``` ## PowerSync Sync engine focused on Postgres (and MongoDB) with SQLite on the client. Strong focus on mobile (Flutter, React Native) with growing web support. **Key differentiator:** SQLite on the client gives full SQL query capability. Strong mobile-first focus with offline-first design. **Best fit:** Mobile-first apps (React Native, Flutter) that need reliable offline support with Postgres or MongoDB backends. ```ts import { PowerSyncDatabase, column, Schema, Table } from '@powersync/web'; const todosTable = new Table({ title: column.text, completed: column.integer, created_at: column.text, }); const schema = new Schema({ todos: todosTable }); const db = new PowerSyncDatabase({ schema, database: { dbFilename: 'app.db' }, }); await db.init(); // Reads: full SQL queries against local SQLite const todos = await db.getAll( 'SELECT * FROM todos WHERE completed = 0 ORDER BY created_at DESC', ); // Reactive queries db.watch('SELECT * FROM todos WHERE completed = 0', [], { onResult: (results) => console.log('Todos:', results.rows?._array), }); // Writes: local SQLite + upload queue await db.execute('INSERT INTO todos (id, title, completed) VALUES (?, ?, ?)', [ crypto.randomUUID(), 'New todo', 0, ]); ``` ## Replicache Client-side transactional cache that works with any backend. Uses a push/pull model where the client pushes mutations and pulls the latest state from a custom server endpoint. **Key differentiator:** Backend-agnostic. Works with any database and any server framework. Proven at scale (used by Linear, Figma-like apps). **Best fit:** Teams with existing non-Postgres backends or complex server logic that want local-first reads with server-authoritative conflict resolution. ```ts import { Replicache } from 'replicache'; const rep = new Replicache({ name: 'user-123', licenseKey: REPLICACHE_LICENSE_KEY, pushURL: '/api/replicache/push', pullURL: '/api/replicache/pull', mutators: { async createTodo(tx, todo: NewTodo) { const id = crypto.randomUUID(); await tx.set(`todo/${id}`, { id, ...todo, completed: false }); }, async toggleTodo(tx, { id }: { id: string }) { const todo = (await tx.get(`todo/${id}`)) as Todo; await tx.set(`todo/${id}`, { ...todo, completed: !todo.completed }); }, }, }); // Reads: subscribe to local data rep.subscribe( async (tx) => { const todos = await tx.scan({ prefix: 'todo/' }).values().toArray(); return todos as Todo[]; }, (todos) => console.log('Todos:', todos), ); // Writes: call mutators (local + queued for push) await rep.mutate.createTodo({ title: 'New todo' }); ``` Server push endpoint (simplified): ```ts import type { MutationV1 } from 'replicache'; export async function handlePush(req: Request) { const push = await req.json(); for (const mutation of push.mutations as MutationV1[]) { switch (mutation.name) { case 'createTodo': await db.insert('todos', mutation.args); break; case 'toggleTodo': await db.update('todos', mutation.args.id, { completed: db.raw('NOT completed'), }); break; } } return new Response('OK'); } ``` ## LiveStore SQLite-based reactive store that uses event sourcing under the hood. Framework-agnostic with OPFS for persistence. All state is derived from an append-only event log. **Key differentiator:** Event-sourced architecture gives full audit trail, undo/redo, and time-travel debugging. Uses SQLite WASM on OPFS for high-performance persistence. **Best fit:** Apps that benefit from event sourcing (audit trails, undo/redo) and want a framework-agnostic reactive store. ```ts import { createLiveStore } from '@livestore/livestore'; import { makeSqliteDeps } from '@livestore/wa-sqlite'; const store = await createLiveStore({ deps: makeSqliteDeps(), schema: { events: { todoCreated: { id: 'string', title: 'string' }, todoToggled: { id: 'string' }, }, state: { todos: { select: `SELECT * FROM todos WHERE completed = 0`, }, }, migrations: [ `CREATE TABLE IF NOT EXISTS todos ( id TEXT PRIMARY KEY, title TEXT NOT NULL, completed INTEGER DEFAULT 0 )`, ], }, }); // Reads: reactive queries derived from event log store.query$.todos.subscribe((todos) => { console.log('Todos:', todos); }); // Writes: dispatch events (appended to event log, projected to state) store.dispatch('todoCreated', { id: crypto.randomUUID(), title: 'New todo', }); ``` ## Triplit Full-stack database with built-in sync, schema definition, auth, and real-time queries. Can run as cloud-hosted or self-hosted. **Key differentiator:** Full-stack DB that handles both client and server storage with built-in auth and real-time sync. Schema-defined with TypeScript. **Best fit:** Teams that want an all-in-one solution without stitching together separate database, sync, and auth layers. ```ts import { TriplitClient } from '@triplit/client'; import { schema } from './schema'; const client = new TriplitClient({ schema, serverUrl: 'http://localhost:6543', token: AUTH_TOKEN, }); // Reads: reactive queries const query = client.query('todos').where('completed', '=', false).build(); client.subscribe(query, (results) => { console.log('Todos:', [...results.values()]); }); // React hook function useTodos() { const { results } = useQuery( client, client.query('todos').where('completed', '=', false), ); return results ? [...results.values()] : []; } // Writes: sync automatically await client.insert('todos', { title: 'New todo', completed: false, }); ``` Schema definition: ```ts import { Schema as S } from '@triplit/client'; export const schema = { todos: { schema: S.Schema({ id: S.Id(), title: S.String(), completed: S.Boolean({ default: false }), createdAt: S.Date({ default: S.Default.now() }), }), }, }; ``` ## Selection Criteria Use this decision matrix to narrow your choice: | If you need... | Consider | | ---------------------------------- | ---------------------------------- | | Postgres read sync + own write API | ElectricSQL | | Full read+write sync with Postgres | Zero, Triplit | | Mobile-first with SQLite on client | PowerSync | | Any backend, proven at scale | Replicache | | Event sourcing with audit trail | LiveStore | | All-in-one DB + sync + auth | Triplit | | TanStack DB integration | ElectricSQL | | Open source (permissive license) | ElectricSQL, PowerSync, LiveStore | | Production-proven maturity | ElectricSQL, Replicache, PowerSync | **Decision flow:** 1. **Do you have Postgres?** If yes, start with ElectricSQL (read sync) or Zero (full sync). If no, consider Replicache or Triplit. 2. **Do you need write sync?** If reads-only, ElectricSQL is simplest. If full sync, evaluate Zero, PowerSync, or Triplit. 3. **What client platform?** Web-only favors ElectricSQL or Zero. Mobile needs PowerSync or Replicache. 4. **Team size?** Small teams benefit from all-in-one solutions (Triplit). Larger teams can stitch together components. 5. **Maturity requirement?** Production-critical apps should lean toward ElectricSQL, Replicache, or PowerSync.