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

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?

  1. Each tab connected to your signaling server and got a peer ID.
  2. join("quickstart-room") made the first tab the host; the second tab discovered it and opened a direct data connection.
  3. broadcast(…, { topic: "chat" }) fanned the message out to every peer subscribed to "chat".
  4. If WebRTC had been blocked (VPN, strict NAT), delivery would have fallen back to TURN or the encrypted relay automatically.
Close the tab, watch the host migrate. Open three tabs, close the first one (the host), and watch the 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