Skip to main content

RxDB as a GUN (gundb) Alternative for JavaScript Apps

Developers who reach for GUN usually want one thing: a JavaScript database that syncs data peer-to-peer without depending on a central backend. GUN delivers on that promise, and it pairs the graph model with extras like the SEA module for cryptography and authentication. The trouble starts once you move past the first demo. Getting basic features running often takes days of trial and error, the schema story is informal, and the source code is dense enough that tracking down a sync bug can stall a project for a week.

This guide walks through where GUN came from, where it falls short for production JavaScript apps, and how RxDB covers the same offline-first and peer-to-peer use cases with a typed API, JSON Schema validation, and well-documented replication plugins.

RxDB JavaScript Database

A Short History of GUN

GUN was started around 2014 by Mark Nadal as an experiment in building a fully decentralized graph database for the web. The library is dual licensed under ZLIB and Apache 2.0 and ships as a small JavaScript module that runs in browsers, Node.js, and React Native. Peers connect through WebSocket relays or WebRTC and exchange small graph deltas, which the library merges using a conflict resolution scheme based on a Hypothetical Amnesia Machine algorithm.

On top of the core graph, the project ships SEA (Security, Encryption, Authorization), a module that adds public key identities, signed updates, and end-to-end encryption. The community around GUN has stayed active on GitHub and Discord, with a steady stream of issues and a smaller pool of regular contributors than larger database projects. Maintenance is concentrated around a single primary author, which is part of why some long standing issues stay open for a long time.

What is RxDB?

RxDB (Reactive Database) is a local-first NoSQL database for JavaScript. It runs in the browser, in Node.js, in Electron, and in React Native, persists data through a pluggable storage layer, and exposes documents and queries as RxJS observables for reactivity. The query language follows the MongoDB style and validates documents against JSON Schema. Replication is handled by a small generic protocol that already has plugins for HTTP, GraphQL, CouchDB, Firestore, WebRTC.

Where GUN Falls Short

GUN solves a hard problem and gets a lot right at the protocol level. The pain points show up once an application grows beyond a small prototype.

Hard to Debug Source Code

The core source files use terse variable names, heavy use of nested callbacks, and unconventional control flow. When sync breaks or a write does not propagate, stepping through the code to find the cause is slow even for experienced JavaScript developers. Stack traces often point at internal callbacks rather than user code, which makes issue reports hard to write and harder to fix.

Opaque CRDT Internals

GUN merges concurrent writes using its own algorithm rather than a documented CRDT family like LWW-Element-Set or RGA. The behavior is deterministic in many cases, but the rules around tombstones, deletion, and graph traversal are not described in a way that maps cleanly onto a formal model. Teams that need to reason about merge outcomes for compliance or correctness checks end up reading source code instead of specifications.

Weak Schema and Types

Documents in GUN are loose JSON graphs with no enforced shape. There is no schema validation, no required fields, and no migration tooling. A typo in a property name silently writes a new field rather than failing fast. For larger codebases this turns into shape drift across clients and versions.

Weak Query Language

GUN exposes a chainable graph traversal API. It works for fetching nodes by key and walking edges, but it does not support range queries, sorting, compound indexes, or aggregation. Anything that resembles a SQL WHERE with multiple conditions has to be implemented by hand on top of .map() and manual filtering.

Limited Tooling

There is no official devtools panel, no schema explorer, and no migration runner. Logging is verbose by default and hard to filter. Test setups for sync code usually involve spinning up real relay peers, which slows feedback loops.

No First-Class TypeScript Story

GUN ships informal type definitions through community packages. The graph traversal API is dynamic enough that type inference rarely catches mistakes. Developers used to typed end-to-end pipelines lose that safety net the moment they touch GUN code.

Where RxDB Helps

RxDB targets the same set of use cases (offline reads, real time updates, peer-to-peer sync) and addresses the points above directly.

  • Typed API: All collections, documents, and queries are typed. The schema feeds TypeScript types so query results infer correctly.
  • JSON Schema validation: Documents are checked against a JSON Schema. Required fields, enums, and string lengths are enforced at write time.
  • MongoDB-style queries: Use $gt, $in, $regex, sorting, and compound indexes through the RxQuery API. Queries return observables that re-emit when matching data changes.
  • CRDT plugin: For collaborative apps that need formal merge semantics, the CRDT plugin provides documented operations on counters, sets, and lists.
  • Encryption: The encryption plugin encrypts selected fields at rest using AES.
  • WebRTC P2P replication: The WebRTC replication plugin syncs collections directly between browser peers without a central data server.
  • Conflict handling: Custom conflict resolution is configured per collection through the revisions and conflict handler API.

Code Sample: Schema and Reactive Query

import { createRxDatabase } from 'rxdb/plugins/core';
import { getRxStorageLocalstorage } from 'rxdb/plugins/storage-localstorage';
 
const db = await createRxDatabase({
  name: 'notesdb',
  storage: getRxStorageLocalstorage()
});
 
await db.addCollections({
  notes: {
    schema: {
      title: 'note schema',
      version: 0,
      primaryKey: 'id',
      type: 'object',
      properties: {
        id: { type: 'string', maxLength: 100 },
        title: { type: 'string' },
        body: { type: 'string' },
        tags: { type: 'array', items: { type: 'string' } },
        updatedAt: { type: 'number' }
      },
      required: ['id', 'title', 'updatedAt']
    }
  }
});
 
// Reactive query: re-emits whenever matching documents change.
const recent$ = db.notes
  .find({
    selector: { tags: { $in: ['inbox'] } },
    sort: [{ updatedAt: 'desc' }],
    limit: 20
  })
  .$;
 
recent$.subscribe(notes => {
  console.log('inbox notes:', notes.map(n => n.title));
});

The schema enforces shape at write time, and the query result is a stream that any UI layer can subscribe to.

Code Sample: Peer-to-Peer Replication via WebRTC

The WebRTC replication plugin gives you the same serverless P2P sync that draws people to GUN, with an explicit configuration and clear error events.

import {
  replicateWebRTC,
  getConnectionHandlerSimplePeer,
  createSimplePeerWrtc
} from 'rxdb/plugins/replication-webrtc';
 
const replicationPool = await replicateWebRTC({
  collection: db.notes,
  topic: 'notes-room-42', // peers sharing a topic sync with each other
  connectionHandlerCreator: getConnectionHandlerSimplePeer({
    signalingServerUrl: 'wss://signaling.rxdb.info/',
    wrtc: createSimplePeerWrtc(),
  }),
  pull: {},
  push: {}
});
 
replicationPool.error$.subscribe(err => {
  console.error('P2P sync error:', err);
});

Peers join a topic, the signaling server pairs them, and from there the data exchange runs directly between browsers. For a transport that does not require running your own signaling server, the Nostr replication plugin routes updates through public Nostr relays.

FAQ

Does RxDB support P2P like GUN?

Yes. The WebRTC replication plugin syncs collections directly between browser peers. Both run without a central data server, and both reuse the same RxDB sync protocol used for HTTP and GraphQL backends.

Can RxDB run without a central server?

Yes. RxDB stores data locally in IndexedDB, OPFS, SQLite, or memory, and any replication is optional. With the WebRTC or Nostr plugins, multiple clients can sync directly with each other and never contact a backend you operate.

How do I migrate data from GUN?

Export your GUN graph to a JSON file by walking the root nodes you care about and serializing each subgraph. Then map the flat documents onto an RxDB collection schema and bulk insert them with collection.bulkInsert(docs). Because GUN graphs use references between nodes, denormalize linked nodes into embedded fields or split them across collections that match your query patterns.

Does RxDB have built-in user accounts like SEA?

RxDB does not bundle a full identity module. It pairs with any auth system you already use (JWT, OAuth, custom tokens) by passing credentials into the replication handler headers. For data confidentiality, the encryption plugin encrypts selected fields with AES, and signed payloads can be added on top in the replication layer when needed.

How does RxDB handle conflicts in P2P sync?

Each collection has a conflict handler. The default keeps the newer revision, and you can replace it with a custom function that merges fields, picks a winner based on metadata, or runs CRDT operations through the CRDT plugin. The full model is described in transactions, conflicts and revisions.

Comparison Table

TopicGUN (gundb)RxDB
Data modelJSON graph of linked nodesJSON documents organized into typed collections
SchemaNone, fields are free-formJSON Schema with validation and migrations
TypeScriptCommunity types, dynamic APIFirst-class types inferred from schemas
Query languageChainable graph traversalMongoDB-style queries with sort, limit, and indexes
ReactivitySubscriptions on nodesRxJS observables on documents and queries
Conflict resolutionBuilt-in HAM merge, opaque rulesPluggable handler plus optional CRDT plugin
EncryptionSEA moduleEncryption plugin, AES on selected fields
P2P transportBuilt-in WebSocket and WebRTC peersWebRTC plugins
Server-based syncOptional relay peersHTTP, GraphQL, CouchDB, Firestore, and custom backends
Storage backendsIndexedDB, file, in-memoryIndexedDB, OPFS, SQLite, Dexie, LocalStorage, Memory, and more
ToolingMinimal, source-level debuggingDevtools, logger, schema validator, migration runner
LicenseZLIB and Apache 2.0Apache 2.0 with paid premium plugins

Follow Up

If GUN attracted you because of peer-to-peer sync but the debugging cost is slowing the project down, RxDB covers the same ground with a typed schema, documented merge semantics, and dedicated plugins for WebRTC replication. Start with the RxDB Quickstart, pick a storage that fits your runtime, and add a replication plugin once your local data model is stable.

More resources: