# GUN: a graph sync engine for offline-first, peer-to-peer apps

> GUN is a JavaScript graph data synchronization engine that keeps peers in sync without a central server. It is a good fit for local-first apps and a poor fit for teams that need transactional SQL or documented rollback.

**amark/gun** — An open source cybersecurity protocol for syncing decentralized graph data.

- Repository: https://github.com/amark/gun
- Website: https://gun.eco/docs
- Stars: 19,142 · Forks: 1,239
- Language: JavaScript
- License: NOASSERTION
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/amark-gun

## The problem GUN targets: shared state without a server in the middle

Most applications that need shared state reach for a hosted backend: a database behind an HTTP API, with the client holding a copy in memory. GUN inverts that. The README describes it as "an ecosystem of tools that let you build community run and encrypted applications", and the package description in package.json calls it "a realtime, decentralized, offline-first, graph data synchronization engine". The unit of work is a graph node addressed by key, not a row addressed by primary key.

The target user is a JavaScript developer building something where connectivity is unreliable or where a central operator is undesirable. The README lists chat, video conferencing, social feeds and collaborative documents among the things people have built. Each of those needs the same primitive: two clients that edit the same object should converge without a round trip to an authority. GUN is aimed at that primitive rather than at reporting, aggregation or analytical queries. If your workload is mostly reads over large tables, this is the wrong shape of tool.

## How the graph, the wire and the merge actually fit together

A GUN node is an object with a key. Calling `gun.get('mark')` returns a reference to that node; `.put()` writes fields into it; `.on()` subscribes to changes; `.once()` reads a value and stops listening. The README notes that "partial updates merge with existing data", so writing `{name: "Mark"}` does not erase an existing email field. That merge behaviour is the core of the model.

Nodes can point at other nodes, which is what makes it a graph rather than a document store. The README's circular-reference example sets `mark = {boss: cat}` and `cat.slave = mark`, writes the object once, then traverses `gun.get('mark').get('boss').get('slave')` to get back to the start. Tables are built on the same primitive: `gun.get('list').set(...)` adds an item, and `.map()` iterates the set. There is no schema and no join planner. Traversal is chain resolution.

On the wire, peers exchange updates rather than issuing queries against a shared master. The README points at separate documentation pages for the conflict resolution algorithm, the mesh networking layer and the routing algorithm, and lists a CPU-scheduled JSON parser (lib/yson.js) whose stated purpose is to avoid blocking the UI thread. The repository also contains sea.js and a sea/ directory, which correspond to the security and encryption layer the README links to as SEA. Storage backends are pluggable: the package keywords name localstorage and S3, and the examples directory includes an express server, a hapi server and a plain http server.

## Installing GUN and writing your first synced node

The README gives two entry points. The browser route needs no build step: a script tag from jsDelivr, then a few lines of JavaScript. The README's own example writes a record and subscribes to it, then updates a field on an interval to show the subscription firing. The `// import GUN from 'gun'` and `// GUN = require('gun')` lines are shown in the same block as alternatives for ESM, Node and React.

```html
<script src="https://cdn.jsdelivr.net/npm/gun/gun.js"></script>
<script>
gun = GUN();

gun.get('mark').put({
  name: "Mark",
  email: "mark@gun.eco",
});

gun.get('mark').on((data, key) => {
  console.log("realtime updates:", data);
});
</script>
```

The Node route is an npm install followed by the bundled examples. The README states that `npm install gun` and then `cd node_modules/gun && npm start` takes about five minutes for an average developer. The start script in package.json is `node --prof examples/http.js`, so what you get is a local relay plus the example apps.

```bash
npm install gun
cd node_modules/gun && npm start
```

The README also warns that if the npm command line fails you may need to `mkdir node_modules` first, or use `sudo`. There is a Dockerfile in the repository that builds on `node:lts-alpine`, runs `npm ci --only=production` in a builder stage, copies node_modules into a fresh image, and exposes ports 8080 and 8765 with `CMD ["npm","start"]`. The README does not document a docker command, so treat the Dockerfile as the source of truth for the image layout rather than the docs. Once the relay is running, open the examples in two browsers and write to the same key from both; the `.on()` callback should fire in both windows.

## Where GUN stops: consistency, rollback and query shape

The merge model is the limitation as much as the feature. Because writes merge field by field and peers converge over time, there is no transaction that spans several nodes and no way to make a multi-step write atomic. The README does not document rollback, snapshots or point-in-time recovery, and the repository's top-level files include no migration tooling. If a bad write propagates, you correct it by writing the correct value, not by reverting a log.

Query capability is equally narrow. You can traverse references and iterate sets, but there is no query language in the README, no index definition, and no aggregation. Anything resembling a report has to be assembled in application code as you walk the graph. Teams coming from SQL or from a document database with secondary indexes will find that the work does not disappear; it moves into JavaScript.

The release history is worth reading before you commit. The most recent release listed is 0.2019.413 from 2019-04-15, and its own note says it is "out of date, use npm or cdn for latest". The package.json version is 0.2020.1239. That gap means the release page is not a reliable version reference; npm is. Version numbering in this project does not follow semver conventions you can reason about automatically. The last push to the repository was on 2026-08-01, so the codebase is still being touched, but the release channel and the version string are not the same thing.

## GUN compared with a CRDT library or a hosted backend

The closest conceptual alternative is a CRDT library such as Automerge or Yjs. The difference is scope. A CRDT library gives you a mergeable data type and leaves transport, storage and identity to you. GUN ships the transport and the storage adapters as part of the same package: the README describes the stack as "a collection of independent and modular tools" spanning conflict resolution, encryption, serialization, mesh networking and routing. You get a running relay from `npm start` without wiring a sync server yourself. The cost is that the merge semantics are GUN's, and you adopt them wholesale rather than composing your own.

The other comparison is a hosted backend such as Firebase, which the README itself invokes when it describes GUN as "like an Open Source Firebase". The practical difference is where the authority sits. A hosted backend owns the canonical state and the client is a cache. GUN has no canonical copy; peers hold the state and reconcile. That is what makes offline writes work, and it is also why you cannot ask a server for a consistent global view of the data at a point in time. If your application logic depends on a single authoritative read, the decentralized model is working against you.

## Maintenance, licensing and what upgrading costs you

The licence field in package.json reads `(Zlib OR MIT OR Apache-2.0)`, so you may choose among those three. The repository's LICENSE.md is the file to read for the exact terms, and the GitHub API reports the licence as NOASSERTION, which means automated detection did not settle on a single identifier. If your organisation has a policy against copyleft or requires a specific identifier in a manifest, check LICENSE.md directly rather than relying on the metadata. Nothing here is legal advice.

Upgrading is the part to plan for. Because the release page is stale and the version string is unusual, pinning to a caret range and expecting predictable breaks is unwise. The dependencies are light: package.json lists `ws` as a dependency and an optional dependency beginning with `@pec` that is truncated in the file. The runtime requirement is `node >= 0.8.4`, which is permissive enough that old deployments will not be forced forward. The practical upgrade path is to pin an exact npm version, read CHANGELOG.md and RELEASE.md in the repository before moving, and re-run the mocha suite, which package.json defines as `test` and which begins with a prompt asking whether you ran the PANIC distributed tests. That prompt is a hint that the interesting failure modes only appear with multiple peers under load.

## Reading the repository before you adopt it

The README is long and enthusiastic, and it spends more words on what people have built with GUN than on operational behaviour. The documentation lives at gun.eco/docs rather than in the repository, so the README is an entry point, not a reference. The types are shipped: index.d.ts, gun.d.ts and sea.d.ts sit at the top level, and package.json points `types` at index.d.ts with a `tsd` directory of `types`. TypeScript users get signatures without writing declarations, though the README does not claim they are exhaustive.

The examples directory is the most useful part of the repository for evaluating fit. It contains express.js, hapi.js, http.js, an angular example, a react example, a react-native example and a relay-sqlite example. Reading examples/http.js tells you what `npm start` actually launches. The SECURITY.md file is where the project puts its vulnerability reporting process, and the .github directory holds the CI workflow referenced by the build badge. Between those files and the SEA documentation, you can form a view of the security model before writing application code.

## Conclusion

Adopt GUN when your app needs realtime peer sync, offline writes and a graph-shaped model, and when you can tolerate eventual consistency. Do not adopt it when you need multi-row transactions, a mature query planner or documented rollback, because the README does not describe any of those. Verify first what your relay peers actually persist: run `cd node_modules/gun && npm start`, open the examples, kill the server and confirm what survives in browser storage before you commit to it.

## FAQ

### What is GUN?

GUN is a JavaScript graph data synchronization engine described in package.json as "a realtime, decentralized, offline-first, graph data synchronization engine". The README presents it as an ecosystem of tools for building community-run and encrypted applications, with realtime peer-to-peer state synchronization and a graph data model.

### How do I install GUN?

Run `npm install gun`, or load the browser build from the jsDelivr CDN with a script tag as the README shows. The README also gives `cd node_modules/gun && npm start` to run the bundled examples.

### Does GUN store data in tables like a relational database?

No. Data lives in a graph of nodes addressed by key, and you build table-like structures with `gun.get('list').set(...)` and iterate them with `.map()`. The README's circular-reference example shows traversal across references rather than joins.

## Sources

- [amark/gun on GitHub](https://github.com/amark/gun)
- [Issues](https://github.com/amark/gun/issues)
- [Project website](https://gun.eco/docs)
- [README](https://github.com/amark/gun/blob/master/README.md)
- [Releases](https://github.com/amark/gun/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/amark-gun
