Quickstart
Get a Firemoot talking to a browser in about five minutes: boot the stack, mint a token from your backend, connect, send.
1. Boot the stack
Firemoot is one Docker image plus a Postgres. The reference Compose stack wires both together:
cd deploy/compose
export FIREMOOT_API_SECRET=$(openssl rand -hex 32)
export FIREMOOT_ADMIN_PASSWORD='choose-something' # optional, unlocks /admin
docker compose up -dCheck it is alive:
curl localhost:6668/healthz # liveness
curl localhost:6668/readyz # liveness + a Postgres pingThe stack pulls the published ghcr.io/firemoot/firemoot image. To run a locally-built server instead, mise exec -- sbt "server/Docker/publishLocal" (tags firemoot:latest) and set FIREMOOT_IMAGE=firemoot:latest.
FIREMOOT_API_SECRET is the one credential you must set. It signs your server's HMAC requests and mints the end-user JWTs the browser connects with, so keep it on your backend and never ship it to a client. Everything else has a usable default - see the configuration reference.
2. Provision from your backend
Your backend talks to Firemoot with the server SDK, authenticated by the API key and secret. Mint a token for a user and create a channel:
import { FiremootServer } from "@firemoot/client";
const server = new FiremootServer({
baseUrl: "http://localhost:6668",
apiKey: "firemoot", // FIREMOOT_API_KEY_ID
apiSecret: process.env.FIREMOOT_API_SECRET!,
});
await server.upsertUser({ id: "alice", name: "Alice" });
await server.upsertUser({ id: "bob", name: "Bob" });
await server.createChannel({ type: "messaging", id: "general", createdBy: "alice" }, [
{ userId: "bob", role: "member" },
]);
// Hand this token to the browser (e.g. from your /token route):
const token = await server.createToken("alice"); // HS256, one-hour default expiry3. Connect from the browser
The browser uses the user's JWT, never the API secret. The client opens the WebSocket, watches a channel, and sends optimistically:
import { FiremootClient } from "@firemoot/client";
const client = new FiremootClient({
baseUrl: "http://localhost:6668",
userId: "alice",
token, // from your backend
});
await client.connect();
const channel = client.channel("messaging", "general");
channel.on("message.new", (event) => console.log(event.data.userId, event.data.text));
await channel.watch();
await channel.sendMessage({ text: "hello, firemoot" });watch() loads the channel's recent history and members, then subscribes over the socket. The send appears immediately (optimistically) and is reconciled when the server confirms it - whichever of the REST response or the message.new event arrives first, and never duplicated.
For a long-lived session, pass tokenProvider instead of token so the client fetches a fresh JWT on every (re)connect rather than dying when the first one expires:
const client = new FiremootClient({
baseUrl: "http://localhost:6668",
userId: "alice",
tokenProvider: () => fetch("/api/chat/token").then((r) => r.text()),
});Next steps
- Configuration - every
FIREMOOT_*variable and what it does. - Authentication - how the two credentials (server HMAC, user JWT) fit together.
- Realtime protocol - the WebSocket frames and event vocabulary.
- Testing - boot a real Firemoot inside your test suite.
- Self-hosting - Compose, Caddy, Fly.io, and which platforms cannot host it.