High-performance, local-first decentralized database built on Rust and Iroh
GuardianDB is a decentralized, local-first database for apps that need peer-to-peer synchronization, offline-first operation, and high performance. Every node keeps a full local replica: reads and writes are local (no server round-trip), and changes converge across peers automatically over Iroh.
It began as a Rust port of OrbitDB but is no longer "OrbitDB in Rust". The legacy IPFS/CID/libp2p stack has been removed in favor of Iroh's QUIC transport, BLAKE3 hashing, and Willow range-based set reconciliation.
Why Iroh
- Direct, encrypted connections — Iroh's Magicsock handles NAT traversal, hole punching, and roaming (Wi-Fi ⇄ 5G without dropping). No global DHT.
- QUIC transport — one encrypted UDP socket multiplexes blobs (data), docs (state), and gossip (signals) per peer.
- Identity is the address — each peer is an Ed25519 public key (
NodeId, 32 bytes). - Range-Based Set Reconciliation (Willow) — peers transfer only the diff between them, not full record lists, syncing millions of records in milliseconds.
- Gossip for real-time signals — iroh-gossip's epidemic broadcast trees fan out ephemeral messages with low latency and redundancy.
Coming from IPFS/OrbitDB
| Concept | Legacy (IPFS / OrbitDB) | GuardianDB (Iroh) |
|---|---|---|
| Identity | PeerID (Multihash) | EndpointID (Ed25519, 32 bytes) |
| Content ID | CID (SHA-256 + codecs) | Hash (BLAKE3) |
| Network | libp2p swarm (TCP/WS) | Iroh Endpoint (QUIC) |
| Discovery | Kademlia DHT (global) | Pkarr/DNS + mDNS, direct |
| Data format | IPLD DAG (JSON) | Binary (Postcard) |
| Sync | Bitswap (block-by-block) | Willow (range-based) |
How the stores map to Iroh
- KeyValueStore / DocumentStore run on Iroh-Docs (Last-Write-Wins CRDT), syncing via Willow range-based reconciliation.
- EventLogStore keeps a causal DAG (ipfs-log lineage) for strict ordering and auditability — but with no IPFS, no JSON, and 32-byte BLAKE3 links instead of CIDs.
guardian-db/
├── guardian/ # GuardianDB facade (mod.rs) + core impl (core.rs)
├── stores/ # EventLogStore, KeyValueStore, DocumentStore, BaseStore
├── p2p/
│ ├── network/client.rs # IrohClient
│ ├── network/core/ # IrohBackend: blobs, docs, gossip, connection_pool, metrics, cache
│ ├── network/config.rs # ClientConfig / NetworkConfig / StorageConfig / GossipConfig
│ └── messaging/ # DirectChannel, OneOnOneChannel
├── access_control/ # Guardian (signature), Iroh, and Simple (open) controllers
├── log/ # CRDT log: Entry, Identity, LamportClock, ACL
├── cache/, data_store.rs, keystore.rs, db_manifest.rs, address.rs
└── odm/ # Optional ODM layer (feature = "odm")
Quick Start
[]
= "0.17"
= { = "1", = ["full"] }
use GuardianDB;
use NewGuardianDBOptions;
use IrohClient;
async
GuardianDB::new takes an IrohClient plus optional NewGuardianDBOptions. Use
IrohClient::development() for local work, or IrohClient::new(config) with a tuned
ClientConfig (see Configuration) for production.
Scaling
A common question: how does a local-first P2P database scale? GuardianDB has no central server, so you don't scale a bottleneck — you add peers and tune how they connect and sync.
Reads & writes are local. Each node answers queries from its own replica with no network round-trip, so read throughput scales linearly with the number of nodes. Writes are applied locally and propagated asynchronously.
Sync cost is proportional to the diff, not the dataset. Willow range-based reconciliation exchanges only what two peers are missing, so steady-state sync stays cheap even as total data grows. BLAKE3 + QUIC keep hashing and transport fast.
Discovery & connectivity (in ClientConfig):
enable_discovery_mdns— find peers on the same LAN automatically.enable_discovery_n0— global discovery via Pkarr/DNS (n0.computer), for peers across the internet.known_peers/config.add_known_peer(node_id)— bootstrap against specific nodes.db.connect_to_peer(node_id).await?— force a direct connection and sync with one peer.
Share a node's identity so others can reach it:
let client = development.await?;
let node_id = client.id.await?.id; // an Ed25519 NodeId — share this with peers
let db = new.await?;
Tuning knobs that matter as you grow (all in ClientConfig):
| Concern | Field | Default → Production |
|---|---|---|
| Concurrent peers | network.max_peers_per_session |
100 → 1000 |
| Connection timeout / keepalive | network.connection_timeout, network.keepalive_interval |
30s / 60s → 60s / 120s |
| Blob cache | storage.max_cache_size |
100 MB → 1 GB |
| Largest object | storage.max_blob_size |
10 MB → 100 MB |
| Gossip throughput | gossip.message_buffer_size, gossip.max_topics |
1000 / 100 → 10000 / 1000 |
The connection pool does circuit breaking and load balancing across these connections automatically.
Topology guidance:
- Small groups (a handful of peers): let mDNS / n0 discovery form a full mesh — no extra setup.
- Larger or internet-spanning deployments: run one or more always-on "super peers" with
enable_discovery_n0 = trueand a fixedportas stable rendezvous/sync points, and list them in every client'sknown_peers. - Many independent workloads: partition by database name and gossip topic so unrelated peers don't sync data they don't need.
Start from a preset and adjust: ClientConfig::production() already raises peer limits,
cache sizes, and gossip buffers for you.
Optional ODM and Collection API
GuardianDB now includes an optional TypeORM/Mongoose-inspired ODM layer for applications that want higher-level modeling primitives without giving up local-first replication. The ODM sits above DocumentStore, so documents still synchronize through Iroh Docs/Willow while application code can use schemas, collections, indexes, and CRUD helpers.
Enable the Rust ODM explicitly:
[]
= { = "0.17", = ["odm"] }
Rust model definitions
use Model;
use ;
let employees = db..await?;
employees.insert_one.await?;
Supported derive attributes include #[primary_key], #[unique], #[index], #[model(collection = "...")], #[model(timestamps)], #[model(flexible)], and schema versions. Runtime schemas are available through ModelSchema, FieldDefinition, and Collection::new for dynamic collections.
JavaScript/TypeScript SDK shape
The TypeScript SDK in sdk/typescript exposes the collection API requested in the ODM RFC. The included process-local transport is for SDK development and tests; production Node/WASM/mobile bindings should implement GuardianTransport against the Rust/Iroh backend.
import GuardianDB from "guardiandb";
import Iroh from "iroh";
const iroh = await ;
const guardiandb = await ;
console.log;
const collection = await guardiandb.;
await collection.;
const employee = await collection.;
const updatedEmployee = await collection.;
Collections support insertOne, batch insert, findOne, find, findById, and first-match update with MongoDB-style operators such as $set, $unset, and $inc.
Consistency boundary
ODM writes validate field types, primary keys, uniqueness, required/nullability rules, immutable primary keys, and strict schemas before persistence. Primary, unique, and secondary indexes are rebuilt from the current local document view and used for equality-query narrowing.
The implemented guarantees are intentionally local to a collection instance. GuardianDB remains decentralized and eventually convergent; disconnected peers can still create conflicting unique values that must be reconciled after replication. TransactionContext and ConsistencyLevel reserve the API shape for future replicated transaction coordination, while unsupported replicated transactions are rejected explicitly instead of implying global ACID semantics.
See docs/odm.md for the full design notes and caveats.
Store Types
use GuardianDB;
use NewGuardianDBOptions;
use ;
use IrohClient;
async
use GuardianDB;
use NewGuardianDBOptions;
use KeyValueStore;
use IrohClient;
async
use GuardianDB;
use NewGuardianDBOptions;
use ;
use IrohClient;
use json;
async
use IrohClient;
use ClientConfig;
use AsyncReadExt;
async
Configuration
The simplest path is a preset on IrohClient. Each preset tunes networking, storage, and
gossip together:
| Preset | Discovery | Storage | Use for |
|---|---|---|---|
IrohClient::development() |
mDNS only | persistent, GC off | local dev |
IrohClient::production() |
mDNS + n0 (global) | 1 GB cache, GC on | deployments |
IrohClient::new(ClientConfig::testing()) |
none | in-memory | tests |
IrohClient::new(ClientConfig::offline()) |
none | persistent | no networking |
For full control, build a ClientConfig and pass it to IrohClient::new:
use GuardianDB;
use NewGuardianDBOptions;
use IrohClient;
use ;
use Duration;
async
Development
Prerequisites: Rust 1.95+ (edition 2024) and Git.
# Quality
# Optional ODM layer (feature-gated)
# P2P/integration tests are sensitive to ordering — run single-threaded:
Community & Support
GuardianDB is open source and welcomes contributions from anyone interested in decentralized systems, Iroh, and Rust. Join the Discord to ask questions and share what you build, and follow updates on Twitter and LinkedIn.
- Issues — bugs and feature requests: GitHub Issues
- Discussions — Q&A and design: GitHub Discussions
- Code — see CONTRIBUTING.md
Status
GuardianDB is in active development and there will be breaking changes. Resulting issues are usually easy to fix, but there are no stability guarantees at this stage.
License
Dual-licensed under the MIT and Apache 2.0 licenses. Opening a pull request is assumed to signal agreement with these terms.
Acknowledgments
- Iroh — QUIC-based P2P data synchronization.
- ipfs-log-rs — CRDT log foundation, MIT © EQLabs. GuardianDB builds on it with significant enhancements for decentralized apps.
GuardianDB - A secure, performant, and fully decentralized peer-to-peer database for the modern Web.