Open a websocket to any URL you like. Everything you send is delivered to every other client on that same URL — and to nobody else. No accounts, no SDK, no schema, no setup.
It is the smallest useful piece of realtime plumbing: a channel is just a name, a client is just a websocket, and a message is just bytes you chose the meaning of.
The one rule: a message goes to every listener on the channel except the sender. That single asymmetry is what makes peers able to talk to each other without echoing themselves.
Three real websockets, open right now from this page to a freshly-generated channel on this server. Send from one and watch the other two receive it — while the sender does not.
connecting…
POSTed to the channel URL reaches
all listeners — there is no sender socket to exclude.
There are two verbs. Connect a websocket to listen and send, or POST to broadcast from anything that can make an HTTP request.
const ws = new WebSocket(
"wss://router.metapage.io/my-secret-channel"
);
ws.onmessage = (e) => console.log("peer said", e.data);
// goes to every other listener, never back to us
ws.onopen = () => ws.send(JSON.stringify({ hello: "world" }));
curl -X POST \
https://router.metapage.io/my-secret-channel \
-d '{"temperature": 21.4}'
# every listener on the channel receives the body verbatim,
# on whichever server instance they happen to be connected to
This is what the router is best at, and it needs no app, no pairing code and no account. Open a channel URL in a browser and the page renders a QR code of its own URL. Point a phone camera at it, and the phone is on the channel.
https://router.metapage.io/kitchen-7f3a91. The
page that loads shows a QR code for exactly that address.
const ws = new WebSocket(
"wss://router.metapage.io/kitchen-7f3a91"
);
addEventListener("deviceorientation", (e) => {
if (ws.readyState !== WebSocket.OPEN) return;
ws.send(JSON.stringify({ a: e.alpha, b: e.beta, g: e.gamma }));
});
?hidden=true to a channel URL to render the page
without the QR code, for once the devices are paired or when the
page is embedded in something else.
Anywhere two things need to talk and you do not want to stand up a backend to introduce them.
A gamepad, a slide clicker, a motion sensor or a remote for whatever is on the big screen. One scan and it is wired up.
Two browser tabs, two iframes, two metaframes — a channel is a pipe between components that were never designed to know about each other.
A device or a script POSTs readings to a channel; any number of dashboards subscribe and render them live.
Give a script on your laptop and a page on the public internet the same channel name and they are connected, no tunnel or port forwarding.
Cursors, selections, "who's here" — the kinds of small collaborative touches that are not worth a whole realtime platform.
CI finished, a job completed, a webhook fired: POST once and every open tab reacts immediately.
Identical stateless instances, joined by redis pub/sub. That is the entire design — there is no broker, no queue and no stored state anywhere in the path a message takes.
No leader, no sharding, no instance that owns a channel. Whichever one your socket happens to land on can serve any channel, so scaling is just running more of them.
Pub/sub and nothing else — no key is written and nothing is persisted. An instance holds exactly one subscription per channel however many local sockets that channel has, which is what keeps delivery to exactly once.
A message goes straight to the sender's local peers, then out to redis tagged with the originating instance's id. Every instance receives it: the origin recognises its own tag and discards it, the rest strip the tag and fan out to their sockets.
It is a router, not a database and not a platform. Knowing the edges up front is what makes it pleasant to build on.
This runs as public infrastructure. A channel is a name — there is nothing to sign up for, no account to be locked out of, and no per-seat tier waiting further down the road.
A lot of realtime glue never gets built, because standing up a server is not worth it for one figure, one instrument or one workshop. The work stays on somebody's laptop, or it ends up inside a platform that outlives the grant that paid for it.
So everything needed to run your own copy is in the repository,
MIT licensed: a redis and a single process. If
router.metapage.io disappears tomorrow,
change the hostname and what you built keeps working.
git clone https://github.com/metapages/websocket-router
cd websocket-router
# anything that speaks redis will do
REDIS_URL=redis://localhost:6379 \
deno run -A src/serve.ts
# now every snippet on this page works
# against your host instead of ours