Getting started
Quickstart
From zero to a working peer-to-peer chat in four steps: get a server endpoint, install the SDK, wire up a room, and open two tabs.
Prerequisites
- Node.js 18+ and a package manager (npm, pnpm, or bun).
- A Dendri server endpoint: hosted or local (step 1 covers both).
- A modern browser. WebRTC is supported everywhere Dendri runs.
Step 1: Get a server endpoint
The SDK always connects to an endpoint you provide. Pick one of two paths:
Option A: Hosted tier
Grab a plan on the pricing page; your API key arrives by email and can be managed in the dashboard. You'll connect with:
const store = createDendriStore({
url: "wss://signal.dendri.dev",
apiKey: "dk_your_key",
});
Option B: Local server (self-hosted)
The open-source server is a single Rust binary. It uses Redis for state, so start both containers:
# 1. Redis (state backend)
podman run -d --name dendri-redis -p 6379:6379 docker.io/redis:7
# 2. The signaling server on port 9876
podman run -d --name dendri --network host \
ghcr.io/afterrealism/dendri-server:latest \
--host 127.0.0.1 --port 9876 \
--redis_url redis://127.0.0.1:6379 \
--enable_relay --allow_discovery
Or run it from source with Cargo (Redis still required, default redis://127.0.0.1:6379):
git clone https://github.com/afterrealism/dendri-server
cd dendri-server
cargo run -- --host 127.0.0.1 --port 9876 --enable_relay --allow_discovery
Verify it's alive: curl http://127.0.0.1:9876/health. Full flags, TLS, and TURN are
covered in Self-hosting.
Step 2: Install the SDK
npm install @afterrealism/dendri-client
Starting a project from scratch? A Vite vanilla template is the fastest sandbox:
npm create vite@latest dendri-chat -- --template vanilla
cd dendri-chat
npm install @afterrealism/dendri-client
npm run dev
Step 3: Build the chat
Replace the generated index.html and main.js with the following.
<!doctype html>
<html lang="en">
<body>
<h1>Dendri chat</h1>
<p>Status: <span id="status">connecting…</span> · <span id="peers"></span></p>
<ul id="log"></ul>
<form id="form">
<input id="input" placeholder="Type a message…" autocomplete="off" />
<button>Send</button>
</form>
<script type="module" src="/main.js"></script>
</body>
</html>
import { createDendriStore } from "@afterrealism/dendri-client";
// Point at YOUR endpoint. Hosted: url + apiKey. Local: the server from step 1.
const store = createDendriStore({
url: "ws://127.0.0.1:9876",
fetchTurnCredentials: true, // use the server's TURN credentials if configured
enableRelay: true, // tier-4 encrypted relay fallback for hostile networks
});
const status = document.querySelector("#status");
const peers = document.querySelector("#peers");
const log = document.querySelector("#log");
const form = document.querySelector("#form");
const input = document.querySelector("#input");
// 1. React to connection / peer changes.
store.subscribe(() => {
const s = store.getSnapshot();
status.textContent = s.connectionState;
peers.textContent = `${s.peerCount} peer(s) — you are ${s.isHost ? "host" : "client"}`;
});
// 2. Receive messages published on the "chat" topic.
store.subscribe("chat", (data, peerId) => {
append(peerId.slice(0, 6), data.text);
});
// 3. Send messages to everyone in the room.
form.addEventListener("submit", (event) => {
event.preventDefault();
const text = input.value.trim();
if (!text) return;
store.broadcast({ text }, { topic: "chat" });
append("me", text);
input.value = "";
});
function append(from, text) {
const li = document.createElement("li");
li.textContent = `${from}: ${text}`;
log.appendChild(li);
}
// 4. Join the room. First tab becomes host; later tabs connect to it.
store.join("quickstart-room");
Step 4: Run it
Start the dev server (npm run dev) and open
http://localhost:5173 in two browser tabs. Each tab is an
independent peer. Type in one tab and the message appears in the other, delivered directly over a
WebRTC data channel.
What just happened?
- Each tab connected to your signaling server and got a peer ID.
join("quickstart-room")made the first tab the host; the second tab discovered it and opened a direct data connection.broadcast(…, { topic: "chat" })fanned the message out to every peer subscribed to"chat".- If WebRTC had been blocked (VPN, strict NAT), delivery would have fallen back to TURN or the encrypted relay automatically.
isHost flag flip in another tab: deterministic host
migration with no server-side coordination.
No build step? Use the CDN global
The package ships a browser global (IIFE) build for script-tag workflows:
<script src="https://unpkg.com/@afterrealism/dendri-client"></script>
<script>
const peer = new dendri.Dendri({ url: "ws://127.0.0.1:9876" });
peer.on("open", (id) => console.log("my peer id:", id));
</script>
The global build exposes Dendri and util on window.dendri. Prefer npm imports for framework apps.
Next steps
Configuration
All client options: transports, TURN, relay, auth, debugging.
Framework guides
The same app in React, Vue, and Svelte with the official adapters.
Building the examples
Cursors, presence, whiteboard, video, file transfer, and a game.
Self-hosting
Production deployment: TLS, TURN, Redis, and scaling.