docs-src/docs/articles/tanstack-db/tanstack-db-p2p-webrtc.md
import {Steps} from '@site/src/components/steps';
TanStack DB is an in-memory reactive client store with live queries and optimistic mutations. Persistence and networking come from the collection implementation, and the official @tanstack/rxdb-db-collection package puts RxDB underneath as described in the TanStack DB + RxDB overview. Because replication is configured on the RxDB side, you can pick any plugin of the Sync Engine, including the WebRTC replication plugin that syncs data peer-to-peer between devices without a central database server. This page explains how P2P sync works under TanStack DB, what it does and does not give you, and walks through a runnable two-peer todo example.
The setup forms the same two loops as every other guide in this series, only the sync target changes:
There is no master and no slave. Every peer hosts its own full copy of the data, and all peers with the same topic string replicate with each other. When a document arrives from another peer, RxDB writes it to the local storage and the change streams into the TanStack DB collection automatically. Your useLiveQuery components re-render without any extra wiring.
The plugin was formerly called replication-p2p and has been renamed to replication-webrtc. Old links redirect to the current page.
Peer-to-peer sync is a strong fit for collaboration between a user's own devices, local network apps, and privacy-sensitive tools where data should never touch a third-party server. But there is no free lunch. Be aware of these tradeoffs before you commit:
wss://signaling.rxdb.info/ for tryouts, but it is not reliable and might be offline at any time. In production you must run your own.live: false option of other replication plugins does not exist here.The following example wires a TanStack DB todo collection to RxDB and replicates it peer-to-peer. Open the app on two devices (or two browsers) with the same topic and watch the todos appear on both. The database and collection setup is the same as in the hub article, so the shared parts are kept short.
<Steps>The WebRTC replication plugin ships with the rxdb package, no extra install is needed for it.
npm install rxdb rxjs @tanstack/react-db @tanstack/rxdb-db-collection
import { createRxDatabase } from 'rxdb/plugins/core';
import { getRxStorageLocalstorage } from 'rxdb/plugins/storage-localstorage';
const db = await createRxDatabase({
name: 'p2ptododb',
storage: getRxStorageLocalstorage()
});
await db.addCollections({
todos: {
schema: {
title: 'todos',
version: 0,
type: 'object',
primaryKey: 'id',
properties: {
id: { type: 'string', maxLength: 100 },
text: { type: 'string' },
completed: { type: 'boolean' }
},
required: ['id', 'text', 'completed']
}
}
});
Call replicateWebRTC() on the RxCollection. The topic works like a room name: all clients with the same topic replicate with each other. In most cases you want one topic per user, prefixed with an identifier for your app so your users do not connect with other apps that also use the RxDB P2P replication.
import {
replicateWebRTC,
getConnectionHandlerSimplePeer
} from 'rxdb/plugins/replication-webrtc';
const replicationPool = await replicateWebRTC({
collection: db.todos,
// Room name: same topic = same replication pool.
topic: 'my-todo-app|user-123',
connectionHandlerCreator: getConnectionHandlerSimplePeer({
// Demo server for tryouts only.
// In production, run your own signaling server.
signalingServerUrl: 'wss://signaling.rxdb.info/'
}),
pull: {},
push: {}
});
// Log errors from any peer connection.
replicationPool.error$.subscribe(err => console.error('WebRTC error:', err));
Notice that replicateWebRTC() returns a replicationPool instead of a single replication state. The pool contains the replication states of all connected peers, and you can stop everything with replicationPool.cancel().
import { createCollection } from '@tanstack/react-db';
import { rxdbCollectionOptions } from '@tanstack/rxdb-db-collection';
const todosCollection = createCollection(
rxdbCollectionOptions({
rxCollection: db.todos,
startSync: true
})
);
Every mutation persists to the local RxDB storage first and is then pushed to all connected peers. Documents arriving from other peers stream into the live query automatically.
import { useLiveQuery, eq } from '@tanstack/react-db';
function TodoList() {
const { data: openTodos } = useLiveQuery((q) =>
q
.from({ todo: todosCollection })
.where(({ todo }) => eq(todo.completed, false))
);
return (
<ul>
{openTodos.map((todo) => (
<li
key={todo.id}
onClick={() =>
todosCollection.update(todo.id, (draft) => {
draft.completed = true;
})
}
>
{todo.text}
</li>
))}
</ul>
);
}
// Insert on peer A, and the todo shows up on peer B.
todosCollection.insert({
id: 'todo-' + Date.now(),
text: 'sync me over WebRTC',
completed: false
});
For production you host your own signaling server. A basic one that works with getConnectionHandlerSimplePeer() takes a few lines:
import {
startSignalingServerSimplePeer
} from 'rxdb/plugins/replication-webrtc';
const serverState = await startSignalingServerSimplePeer({
port: 8080
});
For real deployments you want to add authentication and your own logic on top. To keep unwanted clients out of a replication pool, pass a custom isPeerValid() function to replicateWebRTC() that returns true for valid peers and false for invalid ones. Details are on the WebRTC replication page.
Peers do not have to be browsers. Node.js lacks the WebRTC and WebSocket APIs, so a Node.js peer needs polyfills: wrap the node-datachannel/polyfill package with createSimplePeerWrtc() and pass the ws WebSocket as webSocketConstructor, as shown in the known problems section.
P2P sync means every peer stores a full copy of the shared data on its device. When that data is sensitive, combine the WebRTC replication with the RxDB encryption plugins so documents are encrypted before they are written to disk. The setup for TanStack DB is described in Encrypting Local Data in TanStack DB with RxDB.
Yes, for the data itself. With the RxDB WebRTC replication underneath, documents travel directly between peers and no server stores them. You still need a small signaling server for peer discovery, because WebRTC clients cannot find each other without one.
</details> <details> <summary>Do peers have to be online at the same time to sync?</summary>Yes. WebRTC data channels only transfer data while both peers are connected, and there is no server that buffers changes for absent peers. Each peer keeps a durable local copy in RxDB, so the missing changes are exchanged the next time both peers are online together.
</details> <details> <summary>What happens when two peers edit the same document in TanStack DB?</summary>No peer has authority, so the conflict is detected and resolved locally by the RxDB conflict handler. The default handler resolves to one of the conflicting states. For field-level merges you set a custom conflictHandler on the RxCollection, as described in Conflict Resolution in TanStack DB with RxDB.
No. The server at wss://signaling.rxdb.info/ exists for demonstration purposes and tryouts, it is not reliable and might be offline at any time. In production you run your own signaling server, for example with startSignalingServerSimplePeer() from the WebRTC replication plugin.
Yes. Node.js does not ship the WebRTC and WebSocket APIs, so you install node-datachannel, wrap its polyfill with createSimplePeerWrtc(), and pass a WebSocket constructor from the ws package. The exact setup is shown on the WebRTC replication page.