Skip to main content
Algoramming
HomeAbout
ProjectsBlogsCareersContact
Let's Talk
01Next move

Software that works quietly, every day —

Ready to build something people stick with?

Send the brief — bullet points are fine. We reply within one business day with a plain-English next step. NDA on request.

Start a projectBook a 30-min call
Studio signalAccepting briefs
Reply
≤ 1 business day
Discovery
Free 30-min call
Engagement
Fixed scope or retainer
Timezone overlap
6+ hours, any region
support@algoramming.comDhaka · GMT (UTC+6)
Reply in one business day
NDA on request
Plain-English scoping note
Senior team, end-to-end
Algoramming Systems Ltd.

An independent product studio in Dhaka, designing and engineering high-performing custom software, mobile, and web apps for ambitious teams worldwide.

Innovation in every step

Company

  • About us
  • Services
  • Projects
  • Blogs
  • Careers
  • Contact

Services

  • Custom software
  • Mobile apps
  • Web applications
  • UI/UX design
  • Product consultation
  • Tech partnership

Get in touch

  • House #12, Road #02, Dag #1677
    Merul Badda, Anandanagar
    Dhaka-1212, Bangladesh
    Open in Maps →
  • +880 1400 629698
  • support@algoramming.com

New posts, in your inbox

We send a short email whenever we publish a new field note or ship a studio update. No fixed schedule, no filler, unsubscribe in one click.

Working with teams in

  • DhakaBangladeshBST
  • DubaiUAEGST
  • DohaQatarAST
  • MansfieldUSAEST
  • Mexico CityMexicoCST
  • MonfalconeItalyCET
  • MelbourneAustraliaAEST
  • VarnaBulgariaEET

© 2026 Algoramming Systems Ltd.All rights reserved.

Privacy PolicyTerms and ConditionsSitemap
Home/Field notes/Local-First Web Apps: Why Sync Engines Beat Custom REST APIs
Field note

Local-First Web Apps: Why Sync Engines Beat Custom REST APIs

Discoverwhy building custom REST APIs for offline sync is a trap. Learn how sync engines like RxDB, Electric SQL, and Replicachedeliver zero-latency experiences.

Written by
Algoramming Systems Ltd.
May 21, 202616 min read3,443 words
  • react
  • supabase
  • ui design
  • postgres
  • rxdb
  • electric-sql
Local-First Web Apps: Why Sync Engines Beat Custom REST APIs

Local-First WebApps: Why Sync Engines Beat Custom REST APIs

Imagine you are on a train, working on a critical project update in a browser-based task manager. The train goes through a tunnel. Your connection drops. You click "Save" on your changes, and theinterface freezes. A loading spinner spins indefinitely, eventually failing with a generic network error. When your connection returns, you findthat your updates were lost, or worse, they overwrote a colleague's changes because the application had no way toreconcile the offline state.

This is the classic failure mode of the traditional web application. For decades, we have builtweb applications under the assumption that the network is a constant, reliable utility. We write frontend code that sends a REST request ora WebSocket message for every user action, waiting for the server to say "OK" before updating the screen. If the network isslow, the application is slow. If the network is down, the application is broken.

Building a modern local first web appchanges this paradigm completely. In a local-first architecture, the client-side database is the primary source of truth. Whena user makes a change, the application writes immediately to a local database running inside the browser. The user interface updates instantlywith zero network latency. In the background, a dedicated synchronization layer manages the complex task of uploading those changes to the serverand downloading updates from other clients.

Historically, developers attempted to build these synchronization systems themselves using custom REST endpoints or WebSocket polling. Today, specialized sync engines have made custom sync layers obsolete. Let us explore why building your own sync protocol is a dangeroustrap, and how modern engines like RxDB, Electric SQL, and Replicache offer a far superior path forward.

##The Traditional REST and WebSocket Bottleneck

In a traditional web application, every user interaction is a round-trip journey.When a user clicks a button to complete a task, the frontend dispatches an HTTP POST request to an API endpoint. Thedatabase processes the write, returns a success code, and the client updates the user interface to show the completed task.

Thismodel introduces a fundamental bottleneck: the network round-trip time (RTT). In ideal conditions on a high-speed fiber connection, this might take 50 milliseconds. On a mobile 4G or 5G connection with moderate congestion, thatlatency can easily spike to 300 milliseconds or more. To the human eye, any delay over 100milliseconds feels sluggish. If your application requires multiple sequential API calls to complete a workflow, the user experience rapidly degrades.

WebSockets are often proposed as a solution to this latency. By keeping a persistent TCP connection open, WebSockets eliminate the overheadof establishing HTTP connections for every request. However, WebSockets do not solve the fundamental problem of network dependence. If the clientis offline, the WebSocket connection drops. The application must then queue outgoing messages, manage reconnect logic, and handle the inevitableflood of backlogged data when the connection is restored.

traditional backends are designed for stateless operations. Theyreceive a request, validate it against the current database state, write the change, and forget about the client. When youtry to scale this to support real-time collaboration or offline queueing, your server-side logic must suddenly become state-aware. You must track which client has seen which version of the database, leading to complex tracking tables and ballooning databaseread operations.

What Does "Local-First" Actually Mean?

To understand why custom APIs fail at sync,we must first define what a true local-first architecture looks like. Local-first is not simply "offline support"bolted onto an existing web app. It is a complete inversion of how data flows through your system.

A true local-first applicationadheres to several core principles:

  1. Local Reads and Writes: The application never waits for the network to completea user action. All reads and writes go directly to an in-browser database, such as IndexedDB, SQLite in WebAssembly, or the Origin Private File System (OPFS).
  2. Immediate UI Feedback: Because the application reads fromand writes to local storage, the user interface updates in under 1 millisecond. There are no loading spinners for basic dataentry.
  3. Background Sync: Data synchronization is a background process. The application continues to function normally whether thesync process is actively connected, lagging, or completely offline.
  4. Conflict Resolution: The system must have a deterministicway to merge conflicting updates from multiple users who were offline or working simultaneously.

By shifting the primary data store to theclient, the network is downgraded from a critical runtime dependency to an asynchronous transport medium. This drastically simplifies the frontend rendering logic. Insteadof managing complex loading states, retry loops, and optimistic UI updates, your frontend components simply subscribe to a local database query. Whenthe database changes (either from a local write or a background sync pull), the UI automatically rerenders the new state.

##The Hidden Costs of Custom Sync Architecture

When teams decide to implement local-first capabilities, they often begin with the beliefthat synchronization is a simple problem. They plan to store updates in localStorage, send them to a /sync RESTendpoint when the network is available, and use an updated_at timestamp to determine which record is newer.This approach quickly falls apart under real-world conditions. Let us examine the technical hurdles that emerge when you attempt to builda custom sync architecture:

The Problem of Soft Deletes

In a standard database, you can delete a row using asimple DELETE statement. In a synchronized database, you cannot do this. If Client A deletes a row locally while offline, the server needs to know about that deletion during the next sync. If the server simply deletes the row from its database, Client B (who has not synced yet) will not know the row was deleted. When Client B syncs, theymight upload their local copy of the row, effectively resurrecting the deleted data. To prevent this, you must implement softdeletes using tombstone records (e.g., setting an is_deleted flag and a deleted_at timestamp), which complicates every single query in your system.

The Tracking of Sync State

To perform an efficient sync, theclient must ask the server only for data that has changed since the client's last successful sync. This requires tracking synctokens, transaction IDs, or high-water marks. If a sync session is interrupted halfway through, you must handle partialupdates. If you do not write transactional boundaries into your sync protocol, you will end up with inconsistent states, such asan invoice item existing without its parent invoice record.

Schema Migrations across Distributed Clients

In a traditional web app, schemamigrations are straightforward. You run a migration on your single cloud database, deploy the new backend code, and users get the new version ontheir next page refresh. In a local-first application, you have thousands of databases running on users' devices. Ifyou change your database schema, you must write migration scripts that run locally inside the user's browser, transforming their offlinedata to the new schema without losing their unsaved changes.

Sync Engines Explained: RxDB, Electric SQL, and Replicache

Instead of spending months building and debugging a custom synchronization protocol, modern teams use dedicated sync engines. These tools providethe client-side database, the server-side connectors, and the synchronization protocol out of the box. Three of the most prominent optionsin the ecosystem today are RxDB, Electric SQL, and Replicache.

RxDB (Reactive Database)

Choosinga sync engine rxdb provides a powerful, client-side database designed specifically for JavaScript applications. It is highly flexible,allowing you to use different underlying storage engines, such as IndexedDB, OPFS, or even memory-only stores. RxDB is built around RxJS observables, meaning your UI can easily subscribe to queries and automatically update when data changes. Forsync, RxDB uses a replication protocol that can connect to any CouchDB-compatible endpoint, or any custom GraphQL/REST endpoint that conforms to its replication interface.

Electric SQL

Integrating an electric sql database into your architecture provides a bridgebetween Postgres in the cloud and SQLite in the client browser. Electric SQL uses Postgres's native logical replication (the Write-Ahead Log, or WAL) to stream changes in real-time between your central database and an in-browser SQLite database running via WebAssembly.It handles the complex translation of relational data schemas and permissions, allowing you to write standard SQL queries on the client that stayperfectly in sync with your backend Postgres instance.

Replicache

Replicache takes a slightly different, mutation-based approach. It is not tied to a specific database technology. Instead, it acts as a client-side key-value store thattracks mutations (changes) locally. When the client is offline, mutations are queued. When the connection returns, Replicache plays those mutations back to your existing backend server via simple HTTP endpoints that you define. This makes Replicache incrediblyeasy to integrate into existing applications, as you do not need to replace your entire database infrastructure to use it.

ConflictResolution Demystified: CRDTs and Last-Write-Wins

The most difficult aspect of any distributed database system is conflict resolution. What happens when User A and User B both update the same task title while offline, and then both sync at the same time?

Custom sync engines usually rely on a naive Last-Write-Wins (LWW) strategy. They compare the timestamps ofthe incoming writes and keep the one with the latest timestamp. While simple, LWW has major drawbacks. Client clocks are notoriouslyunreliable and can be out of sync by minutes or hours. Even with accurate network time synchronization, LWW results in silentdata loss. If User A updates the description of a document and User B updates the tag of the same document a millisecond later,User A's changes are completely wiped out.

Professional sync engines solve this using more sophisticated mathematical models, primarily Conflict-free Replicated Data Types (CRDTs) and structured mutation playbacks.

Client A (Offline) --------> EditTitle -----------------\
                                                          \---> Merge (CRDT) -> Consistent State
Client B (Offline) --------> Edit Description ------------/

CRDTs are data structures that can be updated independently and concurrently without coordination. Whenreplicas receive different updates, they can resolve them mathematically to arrive at the exact same state, regardless of the order in which the updates arrived.

There are two main types of CRDTs:

  • State-based CRDTs (CvRDT): Replicas send their entire state to other replicas. The merge function is commutative, associative, and idempotent,meaning that merging states in any order yields the same result.
  • Operation-based CRDTs (CmRDT): Replicas send only the operations (the delta changes) across the network. This requires an underlying transport layer that guaranteesdelivery of operations without duplication, but it uses significantly less bandwidth.

Electric SQL uses state-based CRDTs under the hoodto handle conflict-free replication of relational tables. It translates your relational database schema into equivalent CRDT representations, ensuring that concurrentedits to different columns of the same row do not overwrite each other.

Replicache, on the other hand,avoids CRDTs by using client-side speculative execution and server-side mutation reordering. When a mutation is made,Replicache runs it immediately on the client's local cache. When syncing, it sends the raw mutation command (e.g., updateTaskTitle(id, newTitle)) to the server. The server runs the mutations sequentiallyin a single transaction, resolving conflicts using your standard backend business logic, and then streams the final, authoritative state back tothe client.

Hands-On with Sync Engine RxDB

To understand how clean a local-first codebase can be, letus look at how you initialize and query a local database using RxDB. In this example, we will set up a local databasewith an IndexedDB storage provider and define a reactive query that updates our UI whenever the data changes.

First, install the necessarypackages:

npm install rxdb rxjs rxdb-providers

Next, we initialize the database and createa collection for managing project tasks:

import { createRxDatabase, addRxPlugin } from 'rxdb';import { getRxStorageDexie } from 'rxdb/plugins/storage-dexie';

// Add theRxDB query builder plugin for mango queries
import { RxDBQueryBuilderPlugin } from 'rxdb/plugins/query-builder';
addRxPlugin(RxDBQueryBuilderPlugin);

// Define the schema for our tasks
const taskSchema= {
  title: 'task schema',
  version: 0,
  primaryKey: 'id',type: 'object',
  properties: {
    id: {
      type: 'string',maxLength: 100
    },
    title: {
      type: 'string'},
    isCompleted: {
      type: 'boolean'
    },
    updatedAt: {type: 'number'
    }
  },
  required:['id', 'title', 'isCompleted', 'updatedAt']
};

async function initializeDatabase() {
  // Create the database using Dexie (IndexedDB wrapper) as the storage engine
  const db = await createRxDatabase({
    name: 'taskdb',
    storage: getRxStorageDexie()
  });

  // Add the tasks collection
  awaitdb.addCollections({
    tasks: {
      schema: taskSchema
    }
  });

  return db;}

Once initialized, we can write data directly to our local database. Because this write is local, it completesalmost instantly:

async function addTask(db, title) {
  await db.tasks.insert({id: crypto.randomUUID(),
    title: title,
    isCompleted: false,
    updatedAt: Date.now()
  });
}

To display these tasks in our application, we do not need to makeAPI calls or manage state variables. We simply subscribe to a query. RxDB uses RxJS observables to push new data whenever theunderlying database changes:

function subscribeToTasks(db, callback) {
  const query = db.tasks.find({
    selector: {},
    sort: [{ updatedAt: 'desc' }]
  });

  const subscription = query.$.subscribe(tasks => {
    // This callback runs immediately, and then again
    // every time a taskis added, updated, or synced
    callback(tasks);
  });

  return subscription;
}```

If you configure RxDB's replication plugin to point to a server-side endpoint, the background sync process will automaticallypush local writes to the server and pull new tasks down. The subscription we wrote above will automatically trigger and update our UI withthe synced data, without a single line of manual update code in our application components.

## Bridging Postgres to theClient with Electric SQL Database

While RxDB works beautifully for document-centric workloads, many enterprise applications are built on relational databases, specifically PostgreSQL. This is where an electric sql database shines. Electric SQL allows you to treat your frontend as a thin,reactive replica of your server-side Postgres database.

The architecture of an Electric SQL installation consists of three main parts:

1.**PostgreSQL Database:** Your standard cloud-hosted database, which acts as the ultimate source of truth.
2. **Electric Sync Service:** A lightweight Elixir-based service that sits next to your database. It reads the Postgres logical replication stream andmanages WebSocket connections to client applications.
3. **Electric Client:** A client-side library that wraps a local WebAssembly-powered SQLite database running inside the user's browser.

+-------------+ Logical +------------------+ WebSockets +-----------------+ | Postgres | Replication stream | Electric Sync |(Sync Protocol) | Local SQLite | | Database | ----------------> | Service| <----------------> | (WASM/OPFS) | +-------------+ +------------------++-----------------+


To use Electric SQL, you write standard SQL schemas on your backend. You then usethe Electric CLI to generate client-side TypeScript bindings. This generation step inspects your Postgres schema and creates type-safe queryinterfaces for your frontend.

Here is what querying your synchronized SQLite database looks like using the Electric client:

```typescriptimport { electrify } from 'electric-sql/wa-sqlite';
import { makeElectricContext } from 'electric-sql/react';

// Initialize the Electric client with a local SQLite database file
const config = {url: 'https://your-electric-sync-service.com'
};

const conn = await db.open('local.db');
const electric = await electrify(conn, schema, config);

// Sync only thedata the current user has access to
const shape = await electric.db.tasks.sync({
  where: {userId: currentUser.id
  }
});

// Wait for the initial data shape to download from the server
await shape.synced;

// Query the local SQLite database reactively
const tasks = await electric.db.tasks.findMany({
  where: {
    isCompleted: false
  }
});

The concept of "shapes" is central to Electric SQL. A shape defines a subset of your database (a table, its relations, and specificfiltering criteria) that a particular client needs. This prevents the client from downloading your entire production database. Instead, they sync only theirspecific workspace, team data, or user records. The Electric Sync Service monitors Postgres for any changes that match this shape andstreams them to the client instantly.

Developer Experience and Performance Benchmarks

The difference in developer experience and application performance between a customREST/WebSocket architecture and a dedicated sync engine is stark. Let us break down these differences across key metrics.

DevelopmentVelocity

When building a custom sync system, every new feature requires coordinating changes across multiple layers of your stack: 1.You write a migration for your server database. 2. You build a REST endpoint to expose the new fields. 3.You update your API client on the frontend. 4. You write custom caching logic to store the new data in localStorage.5. You write conflict-resolution code to handle concurrent edits to those fields.

With a sync engine, you defineyour schema once. The sync engine handles the replication, local storage, schema validation, and conflict resolution automatically. Developers canfocus entirely on building user interfaces and business logic.

Read and Write Latency

In a standard web application, readingdata requires a network call, resulting in a latency of 50ms to over 1000ms. Writesrequire waiting for server confirmation before updating the UI, leading to a sluggish user experience.

With a local-first syncengine:

  • Read Latency: 0ms. All queries are executed against local memory or local disk (IndexedDB/SQLite).
  • Write Latency: 0ms. Writes are committed to the local database instantly, and the UI rerenders immediately. The network sync happens asynchronously in the background.
Metric Traditional REST App Local-First Sync Engine
Initial Page Load Query Needs network fetch (100ms - 2000ms) Instant from local cache (1ms - 10ms)
Data Modification Blocks UI until server confirms (200ms+) Immediate optimistic local update (<1ms)
Offline Capability Broken / "You are offline" screens 100% functional, queues updates
Sync Logic Complexity High manual overhead per endpoint Zero manual overhead (handled by engine)

Battery and Bandwidth Efficiency

Custom sync implementations often rely on pollingor constant WebSocket heartbeats, which drain mobile device batteries and consume unnecessary cellular data. Modern sync engines use highly optimized protocols like deltacompression and binary serialization formats. They only send the exact bytes that changed, and they automatically pause synchronization when the application goes to thebackground or when the device's battery is low.

When to Avoid Local-First Architecture

While local-first architecturesoffer incredible benefits for user experience and developer velocity, they are not a silver bullet. There are specific scenarios where a traditionalserver-centric REST architecture is still the correct choice.

1. Applications with Massive Datasets

Browser storage isnot infinite. IndexedDB and SQLite in WASM are typically limited by the browser's storage quota, which is usuallya percentage of the user's available disk space. If your application requires users to query terabytes of historical log data or searchthrough millions of global records, you cannot sync that data to the client. These applications must rely on server-side searchindexes and server-side query execution.

2. High-Security or Strict Compliance Data

In a local-firstapplication, data is decrypted and stored on the user's physical device. If you are building an application that handles highly sensitive financial data, medical records subject to strict HIPAA regulations, or classified government intelligence, storing that data locally on a personal laptop or mobilephone may violate security policies. While client-side databases can be encrypted, the risk of physical device compromise makes server-centric architectureswith strict session management much easier to secure.

3. Real-Time Inventory and Double-Booking Prevention

Certainbusiness logic requires absolute consistency across the entire system. For example, if you are building an airline ticket booking system, youcannot allow two users to book the same seat. If both users are offline and book the same seat, a local-first conflictresolution engine would resolve the conflict later, but one of the users would end up with a canceled ticket. For transaction-heavy systems wheredouble-booking is catastrophic, writes must go through a centralized coordinator (the server database) before they can be confirmed.## Choosing the Right Path for Your Next Project

If your application does not fall into those specific edge cases, adoptinga local-first architecture is one of the most impactful decisions you can make for your project's longevity and user satisfaction.When choosing between sync engines, consider your existing backend infrastructure:

  1. If you are building a greenfield project withPostgreSQL, Electric SQL offers the tightest integration and the cleanest developer experience by mapping your relational database directly to the client.2. If you have an existing backend service with REST or GraphQL APIs and want to add local-first capabilities withoutrewriting your server, Replicache or RxDB with a custom sync connector will allow you to adopt local-first incrementally.3. If you require a highly dynamic, document-oriented database schema or are targeting platforms beyond the browser (suchas React Native or Electron), RxDB is an incredibly mature and flexible choice.

Stop building custom REST synchronization endpoints. Stop debuggingrace conditions in your WebSocket connection handlers. Embrace the local-first movement, let a dedicated sync engine handle the heavy lifting ofdata replication, and build web applications that are instantly responsive, completely reliable offline, and a joy to use.

Key takeaways

  • Zero Latency: Local-first web apps read and write directly to an in-browserdatabase, eliminating network latency and providing sub-millisecond UI updates.
  • Avoid Custom Sync: Building a custom synchronizationprotocol using REST or WebSockets introduces massive development overhead, especially when handling soft deletes, schema migrations, and conflict resolution.
  • Leverage Modern Engines: Specialized sync engines like RxDB, Electric SQL, and Replicache handle the complexmath of synchronization and conflict resolution out of the box.
  • Choose by Infrastructure: Select Electric SQL for PostgreSQL-centric applications, Replicache for integration with existing APIs, and RxDB for highly reactive, document-based workloads.

Ifyou are planning a project like this and want to discuss how to implement a local-first architecture using modern sync engines, weare happy to talk it through.

Share this
Reply to this note
Working on something?

Have a project in mind?

We design and engineer software, mobile, and web products end-to-end. Send the brief, we will reply within one business day.

Start a project
New posts, in your inbox

Be first to read the next note.

We send a short email whenever we publish a new field note or ship a studio update. No fixed schedule, no filler.

Unsubscribe in one click. We never share your address.

Keep reading

More field notes like this.

All posts
Anatomy of an API Leak:Incident Response and Recovery01 · Related
May 21, 2026·15 min

Anatomy of an API Leak:Incident Response and Recovery

A step-by-step engineering case study of an API credential exposure and how modern product teams automate secret detection and rotation.

Read post
Beyond OpenAI API: Building Local LLM Pipelines for Privacy02 · Related
May 29, 2026·1 min

Beyond OpenAI API: Building Local LLM Pipelines for Privacy

Beyond OpenAI API: Building Local LLM Pipelines for Privacy Sending customer data to a third-party APIis a risk that many startups can no longer afford to take. Whether you are handling medical…

Read post
Why Product-Minded Engineers Outpace Pure Coders03 · Related
May 21, 2026·12 min

Why Product-Minded Engineers Outpace Pure Coders

Discover why developers who combine clean code with product thinking and UI/UX empathy rise fasterto technical leadership positions.

Read post
Liked this note?

Bring us a problem, not just a brief.

We will reply in plain English within one business day, NDA on request. Discovery call is free.

Start a conversationOr browse more field notes