Skip to main content

PouchDB + CouchDB: sync replica multi-device

How to synchronize data in real-time across multiple devices using PouchDB and CouchDB. Complete guide with offline-first, conflict resolution, and multi-device architecture for local-first applications.

What do you think about this article?

PouchDB + CouchDB: sync replica multi-device
Local-First SoftwarePhotos:panumas nikhomkhaiPexels

At a Glance

How to synchronize data in real-time across multiple devices using PouchDB and CouchDB. Complete guide with offline-first, conflict resolution, and multi-device architecture for local-first applications.

PouchDB + CouchDB: sync replica multi-device

The biggest challenge in modern development is not building an application that works online. It's building an application that works even when there's no connection, and that synchronizes data automatically when the connection is restored. PouchDB and CouchDB were built precisely for this problem.

In this guide you'll learn how to implement a local-first architecture where every device (web, mobile, desktop) maintains a local copy of the data and synchronizes changes in real-time with a central server. We don't just cover the technical setup: we also address how to handle conflicts, optimize performance on slow networks, and build applications that remain responsive even during replication.

What You'll Learn

  • How CouchDB document replication works at a conceptual level
  • Setting up PouchDB in the browser and integrating with CouchDB
  • Implementing bidirectional synchronization (bi-directional sync)
  • Handling conflicts automatically and manually
  • Building offline-first patterns your user won't notice
  • Scaling multi-device without memory crashes
  • Monitoring and debugging replication in production
  • Comparing PouchDB/CouchDB with Yjs, Automerge and alternatives

1. What are PouchDB and CouchDB?

CouchDB is an original NoSQL database, developed by Apache, that embraces "replication" as a first-class citizen. Every document has a unique _id and a _rev (revision) that changes every time the document is modified. This mechanism is the foundation of reliable synchronization.

PouchDB is an implementation of CouchDB that runs in the browser (or in Node.js via SQLite/LevelDB). It's essentially a local database that replicates CouchDB protocols. When you're online, it synchronizes changes; when you're offline, it continues to work normally, accumulating modifications for when the connection returns.

The beauty of this design is that you don't have to write synchronization code. It's integrated into the data model. When you have two PounchDB instances replicating each other (or a CouchDB), conflicts are handled automatically using a revision tree system.

2. Local-First Architecture with PouchDB + CouchDB

In a local-first architecture, the sequence is:

  1. User reads/writes to the local database (PouchDB) — instantly
  2. Changes are synchronized to the server (CouchDB) — in the background
  3. When other devices synchronize, they receive the changes — without perceptible lag
  4. If two devices modify the same document offline, conflicts are resolved deterministically

This means the user interface is ALWAYS responsive, network connectivity is a "bonus" and not a critical dependency.

3. Basic Setup: PouchDB + CouchDB

Let's start with a minimal setup. For this article we'll use a local CouchDB server and a web application with PouchDB.

3.1 CouchDB Installation

The easiest way is Docker:

docker run -d \
  -p 5984:5984 \
  -e COUCHDB_USER=admin \
  -e COUCHDB_PASSWORD=password \
  --name couchdb \
  couchdb:latest

Access Fauxton (CouchDB's web UI) at http://localhost:5984/_utils. Create a test database: todo_sync.

3.2 PouchDB Installation in the Browser

npm install pouchdb pouchdb-find

In your Angular/React/Vue component:

import PouchDB from 'pouchdb';
import PouchDBFind from 'pouchdb-find';

// Enable the find plugin
PouchDB.plugin(PouchDBFind);

// Create a local database
const localDb = new PouchDB('todo_sync');

// Connect to the remote server
const remoteDb = new PouchDB('http://admin:password@localhost:5984/todo_sync');

// Start bidirectional synchronization
const sync = localDb.sync(remoteDb, {
  live: true,
  retry: true,
  back_off_function: (delay) => Math.min(delay * 2, 60000)
});

4. Bidirectional Synchronization

The .sync() method of PouchDB is the heart of everything. It's equivalent to running .replicate.to() and .replicate.from() simultaneously.

// Alternative explicit (same thing):
const pushSync = localDb.replicate.to(remoteDb, {
  live: true,
  retry: true
});

const pullSync = localDb.replicate.from(remoteDb, {
  live: true,
  retry: true
});

With live: true, synchronization remains active until the `sync` object is cancelled. Replication listens to changes on the source database and sends them to the target.

5. Handling Conflicts

When two devices modify the same document offline, CouchDB + PouchDB resolve the conflict using the revision tree. The version with the highest revision wins. If revisions are equal, the version with the higher deterministic id wins.

However, the "losing" version is not discarded: it's marked as a conflict. You can resolve it manually:

// Check if a document has conflicts
localDb.get('todo_1').then(doc => {
  if (doc._conflicts) {
    console.log('Conflicts present:', doc._conflicts);

    // Retrieve conflicting versions
    Promise.all(doc._conflicts.map(rev =>
      localDb.get('todo_1', { rev: rev })
    )).then(conflictDocs => {
      // Apply your resolution logic
      // E.g.: merge fields, take the most recent, etc.
      const resolved = mergeConflicts(conflictDocs);

      // Save the resolved version and delete conflicts
      localDb.put({
        ...resolved,
        _rev: doc._rev
      });
    });
  }
});

6. Offline-First Pattern: Local QuerySets

An effective pattern is to keep a subset of documents synchronized, not the entire database:

// Filter only active documents (not archived)
const activeDocsSync = localDb.replicate.from(remoteDb, {
  live: true,
  filter: '_design/filters',
  query_params: { status: 'active' }
});

// In CouchDB, create a design doc with the filter:
// _design/filters with a function that accepts active status

This reduces client disk space and replication bandwidth, keeping only relevant data.

7. Practical Case: Multi-Device Todo Application

Let's build a todo app that synchronizes across three devices: laptop, tablet, phone. The user goes offline on their phone and creates two tasks. They perform an action on their laptop while offline. When the connection is restored, the devices automatically synchronize.

export class TodoService {
  private localDb: any;
  private remoteDb: any;
  private sync: any;

  constructor() {
    this.localDb = new PouchDB('todos_local');
    this.remoteDb = new PouchDB('http://admin:pwd@server/todos');
  }

  async initSync() {
    // Start sync in background
    this.sync = this.localDb.sync(this.remoteDb, {
      live: true,
      retry: true,
      batch_size: 500
    });

    // Listen for errors
    this.sync.on('error', (err) => {
      console.error('Sync error:', err);
      // Notify UI that synchronization is paused
    });

    this.sync.on('complete', (info) => {
      console.log('Sync completed:', info.docs_read);
    });
  }

  async addTodo(task: string) {
    // Write immediately to local database
    const doc = {
      _id: `todo_${Date.now()}`,
      task,
      done: false,
      createdAt: new Date().toISOString()
    };
    await this.localDb.put(doc);
    // Replication syncs it to the server in background
  }

  async toggleTodo(id: string) {
    const doc = await this.localDb.get(id);
    doc.done = !doc.done;
    doc.updatedAt = new Date().toISOString();
    await this.localDb.put(doc);
  }

  getTodos() {
    // Use _design/query for structured queries
    return this.localDb.find({
      selector: { done: { $eq: false } },
      sort: [{ createdAt: 'desc' }]
    });
  }
}

8. Performance and Optimization

For large scales (thousands of documents), consider:

8.1 Batch Size

Increase batch_size to reduce round-trips:

sync = localDb.sync(remoteDb, {
  live: true,
  batch_size: 1000,  // Default is 500
  batches_limit: 10   // Number of batches in parallel
});

8.2 Database Compaction

Over time, PouchDB accumulates revisions. Compact periodically:

// Compact every hour
setInterval(async () => {
  const info = await localDb.compact();
  console.log('Compacted:', info);
}, 3600000);

9. Security and Authentication

Don't put credentials in the client. Use a proxy or JWT token:

// ❌ Don't do:
const remoteDb = new PouchDB('http://admin:password@server/db');

// ✅ Do this:
const remoteDb = new PouchDB('https://api.example.com/db', {
  fetch: (url, opts) => {
    opts.headers = opts.headers || {};
    opts.headers.Authorization = `Bearer ${getToken()}`;
    return fetch(url, opts);
  }
});

10. Comparison with Alternatives

PouchDB/CouchDB vs Yjs: Yjs is focused on real-time collaborative operations. PouchDB is focused on durable document replication. You can use both together.

PouchDB/CouchDB vs Automerge: Automerge offers embedded CRDTs. PouchDB uses OT-like revision trees. Automerge is better suited for collaborative editing; PouchDB for traditional CRUD apps.

PouchDB/CouchDB vs Firebase Realtime: Firebase is serverless but closed. CouchDB is open-source and self-hosted. Firebase is faster for minimal setup; CouchDB is more flexible long-term.

11. Best Practices

  • Always use revisions: Don't ignore _rev when updating, or you'll lose your offline changes.
  • Monitor conflicts: Unresolved conflicts mean users see inconsistent data.
  • Validate on the server: The client might synchronize corrupted data. Use CouchDB design docs to validate.
  • Incremental backups: Use CouchDB's _changes feed for efficient backups.
  • Test discontinuous connectivity: Don't assume online/offline is binary. Use circuit breaker on sync errors.

12. FAQ - Frequently Asked Questions

Q: How do I manage storage on a mobile device with limited storage?

A: Use database filtering (filter + query_params) to synchronize only the relevant data subset. For example, synchronize only documents from the last 30 days on mobile, but full history on desktop.

Q: What happens if the CouchDB server is offline for days?

A: Documents remain in the local PouchDB. When the server comes back, replication resumes automatically. It might take time to synchronize thousands of documents, but it remains reliable.

Q: Can I use PouchDB with a relational database (PostgreSQL)?

A: Not technically, because PostgreSQL doesn't have CouchDB's replication model. However, you can use Supabase with a PouchDB adapter, or use Postgres.js with WebSocket to push updates.

Q: Is it safe to use PouchDB in a sensitive app (banking, healthcare)?

A: PouchDB itself is safe. But you must implement: end-to-end encryption, server validation, audit log, and compliance with regulations (HIPAA, PSD2). Always use HTTPS and token authentication.

Q: How do I monitor the health of replication in production?

A: Listen to sync events: change, error, complete. Log them to a monitoring service. If replication remains "stalled" for more than an hour, notify your operators.

Conclusion

PouchDB + CouchDB allow you to build applications that embrace the "offline-first" principle: the application works offline by default, and uses the server only for synchronization. This not only improves user experience, but also reduces server load and makes apps more robust to unreliable connections.

The learning curve is real (the revision tree model isn't intuitive at first), but the payoff is huge: you don't have to manage server queries, caching, invalidation. The data flow is unidirectional and predictable.

If you're building an app that requires reliable multi-device synchronization, PouchDB + CouchDB are an excellent choice. And if you decide to self-host them on a VPS, the operational cost remains very low.

Commenti

Caricamento commenti...

Sign in to comment

Discuss this article in the community

Join the Telegram group to discuss with other developers, ask questions and share your experiences.

Did you like this article?

Explore other content on the blog or discover my projects