PeerBasket

A hassle-free, lobby-based PeerJS discovery server.

Problem & Solution

WebRTC (via PeerJS) enables direct P2P connections, but only after peers exchange their Peer IDs. There is no built-in discovery protocol.

PeerBasket solves this signaling gap. You POST your Peer ID under a shared room name (a basket), and PeerBasket returns the Peer IDs of everyone else currently active in it.

Hosted Instance: peerbasket.bittu.dev

Want to run your own? Self-host via GitHub

Why PeerBasket?

PeerBasket is a lightweight lookup table. It intentionally skips authentication to keep setups instant and friction-free. See the Security Considerations section below for more details.

Inactive peer heartbeats are automatically pruned after 30 seconds to clean up zombie rooms. It is free, open source, and backed by a fast in-memory Redis cache.

Quickstart

Two peers post to the same basket string, then instantly connect to any active peer IDs returned in the response.

const peer = new Peer(); // from peerjs

peer.on('open', async (myId) => {
  const res = await fetch('https://peerbasket.bittu.dev/basket/my-basket-id', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ peer_id: myId })
  });
  const { peers } = await res.json();

  peers.filter(id => id !== myId).forEach(id => peer.connect(id));
});

Polling Tip: Call this fetch every 10 seconds to stay alive in the pool and catch incoming peers.

POST peerbasket.bittu.dev/basket/<basket_id>?limit=100
limit optional, default: 100

Send your PeerJS peer ID in the request body. PeerBasket registers that peer ID under the basket_id given in the URL, which can be any string you choose, and returns the peer IDs of everyone else who is currently registered under that same basket_id.

Request Body
{
  "peer_id": "peerjs-abc-123"
}
Response
{
  "basket_id": "room-101",
  "peers": ["peerjs-id-001", "peerjs-id-002"],
  "total_peers": 2,
  "peers_returned": 2
}
  • The peers list in the response includes your own peer ID. Filter it out on the client side before connecting, so that you do not try to connect to yourself.
  • A peer is automatically dropped from its basket if it has not posted to that basket in the last 30 seconds.
  • "Active" means a recent heartbeat, not a connection that is open right now. A peer that crashed a few seconds ago can still appear in the list for up to 30 seconds. Treat the moment a PeerJS connection actually opens as the real liveness check, and make sure your code handles the case where it never opens.
  • Rate limit: PeerBasket allows 20 requests per minute. If you poll no faster than every 10 seconds, you will stay comfortably inside that limit. Exceeding it returns an HTTP 429 response and you will be blocked for the next 10 seconds.
  • Basket IDs cannot be reserved. Anyone who knows a basket ID can join that basket and see everyone else who is in it.
  • Use a long, random, unguessable string as your basket_id for anything other than a throwaway demo, the same way you would treat an unlisted URL.

Security Considerations

PeerBasket does no authentication, by design. A basket ID is the only thing that stands between a basket and the public. Treat a basket ID like an unlisted URL rather than a password, and pick something unguessable for anything that is not a throwaway demo, as recommended above.

This also means that PeerBasket cannot vouch for who is behind any peer ID it returns. Anyone who knows your basket ID can register their own peer into it, and your app will see that peer as just another member of the basket. If you are building anything beyond a quick prototype, verify identity at the application layer instead of trusting the raw peer list on its own. Two simple ways to do that are a shared secret exchanged out of band, or a handshake performed once the PeerJS connection actually opens.

PeerBasket Operational Grid Structure Layout Map