Serverless P2P Multiplayer in Godot — No Backend, Just GenosDB
How I turned a single-player Godot game into real-time co-op with zero servers — two browser tabs, one shared world, powered by GenosDB.

Full Stack Developer - dWEB R&D
Try it first: open the live demo in two browser tabs → https://estebanrfp.github.io/godot-genosdb/ Each tab is a different player. Walk around, chop a tree — it falls in every window. Say hi in the chat. No server is running. Anywhere.

The challenge
Multiplayer usually means servers: a backend, sockets, authoritative state, deployment, scaling, cost. For a small game — or just a prototype — that's a lot of plumbing before the fun part.
What if the players' browsers just talked directly to each other, and the shared world simply… persisted, on its own?
That's what this demo does: a top-down, Stardew-style co-op farm built in Godot 4, exported to the Web, where every connected browser is another farmer in the same world — running on no backend at all. The secret is GenosDB.
What is GenosDB?
GenosDB is a decentralized graph database that runs entirely in the browser. Two parts matter here:
GenosRTC — peer-to-peer transport over WebRTC, with decentralized signaling via Nostr relays (no signaling server you run). Peers find each other and open direct data channels.
A reactive, persistent graph — you
putnodes andmapqueries, and changes sync to every peer and persist (OPFS). New peers automatically receive the current state.
No backend. No database server. No signaling server. It's all client-side.
The key idea: a hybrid model
Not all multiplayer state is equal, and GenosDB lets you treat it accordingly. The demo splits the world into two layers — and this is the pattern worth stealing:
| State | Example | How | Why |
|---|---|---|---|
| Ephemeral | Player positions & chat messages | a data channel (channel.send) |
fast, ~16 Hz, throwaway |
| Persistent | Chopped trees + the shared wood total | the graph (db.put / db.map / db.remove) |
synced and stored; late-joiners see the world as it is, and Reset wipes it for everyone |
That second row is the magic. When you chop a tree you don't shout "I chopped tree 3!" (only people currently listening hear it). You write it into a shared graph — and anyone who opens a new tab later gets the already-chopped world via GenosDB's initial event. Chops survive reloads. That's a database, working P2P, for free.
It's a real little game now — and every feature is a GenosDB call
The demo grew from a tech sketch into a cozy co-op game, and each mechanic still maps cleanly to GenosDB:
🪓 Chop trees with an axe — a 4-directional animated farmer. Felled trees regrow, and the shared wood counter (real felled trees, race-free) reads the same for everyone, derived straight from the synced graph.
💬 P2P chat — type a message and it pops as a speech bubble over your farmer in every window (the ephemeral data channel again).
♻️ Reset — one button wipes the shared world for all peers. That's
db.remove, propagated P2P.🎨 Cozy pixel-art world, sound effects and ambient music — and all art & audio are CC0 (credits in the repo).
All of it serverless. All of it P2P.
Connecting Godot to GenosDB
GenosDB is browser-only, and Godot reaches the browser through its Web (HTML5) export + JavaScriptBridge. So I built a small bridge and packaged it as a plugin — godot-genosdb — that auto-injects the JS bridge into your exported index.html. Enable it, export to Web, done. Nothing to bundle, no HTML to edit.
The API mirrors GenosDB
On purpose: learn the plugin and you've learned GenosDB.
func _ready():
Net.peer_join.connect(func(id): spawn_farmer(id))
Net.message.connect(_on_remote_state) # ephemeral (positions + chat)
Net.graph_changed.connect(_on_world) # persistent
Net.join("my-room") # = gdb("my-room", {rtc:true})
Net.map({}) # = db.map({}, cb)
func _physics_process(_d):
Net.send({ "x": position.x, "y": position.y }) # = channel.send(...)
func chop_tree():
Net.put({ "type": "tree", "hp": 0 }, "tree_3") # = db.put(node, id)
func reset_world():
Net.remove("tree_3") # = db.remove(id)
| GenosDB (JS) | Godot (Net) |
|---|---|
gdb(name, {rtc:true}) |
Net.join(name) |
channel.send(data) |
Net.send(data) → signal message |
db.put(node, id) |
Net.put(data, id) → signal graph_changed |
db.map(query, cb) |
Net.map(query) |
db.remove(id) |
Net.remove(id) |
room.on('peer:join' / 'leave') |
signals peer_join / peer_leave |
Try it / use it
▶ Live demo (two tabs!): https://estebanrfp.github.io/godot-genosdb/
Plugin + full source (MIT, assets CC0): https://github.com/estebanrfp/godot-genosdb
GenosDB: https://github.com/estebanrfp/gdb
Install is three steps: copy addons/godot_genosdb/, enable it in Project Settings → Plugins, export to Web. P2P is Web-only; on desktop the API is a safe no-op, so the same code runs as single-player in the editor.
Why this matters
For jams, prototypes, collaborative toys, or "presence" features, serverless P2P removes an entire category of work. And because GenosDB gives you a persistent reactive graph — not just a raw WebRTC pipe — your shared world is consistent, survives reconnects, and welcomes late-joiners with zero extra code.
Two browser tabs. One shared world. No backend. Just GenosDB.




