A Graph Database with Git-like Version Control for Production Data

Open inAnthropic

TerminusDB is a git-like database: a graph database with version control built into its storage engine rather than layered on top. It stores production data as linked JSON documents, and every write — insert, update, delete, schema change — becomes an immutable commit with an author, timestamp and message. Branches, structural diffs, merges, time travel and clone/push/pull between servers all work the way a Git user expects, but on live application data. If you are evaluating database version control for production, this is the model implemented end to end.

What "Git-like version control" should mean for a database

Many tools claim version control for data; few deliver the parts that matter in production. A reasonable checklist:

  • Immutable commits — every change is appended, never overwritten, so history cannot be silently rewritten.
  • Cheap branches — a branch isolates in-flight work without copying the dataset.
  • Structural diff and merge — changes are compared at the field level and merged three-way, with genuine conflict detection.
  • Provenance — each commit records who changed what, when and why, which doubles as an audit trail.
  • Time travel — any earlier commit is directly readable, for reports, debugging and reproducible pipelines.
  • Distribution — databases clone, push and pull between instances the way Git repositories do.

TerminusDB implements all six — as properties of the storage engine, not application code or triggers.

How TerminusDB maps to Git

Git conceptTerminusDB equivalent
RepositoryDatabase — a versioned container of JSON documents
CommitImmutable commit on an append-only layer store
BranchNamed line of history, addressed in the API path
git diffStructural diff of documents and triples
git mergeapply — three-way merge with conflict reports
git logCommit log — queryable history per branch
git clone / push / pullClone, push and pull between TerminusDB instances

The one deliberate difference is granularity: Git versions lines in files, TerminusDB versions individual fields and graph edges. A diff tells you which fact changed, not which line moved.

A production workflow in seven calls

The examples below run unchanged against a local TerminusDB — the same operations your application would issue in production: schema, write, branch, change, review, merge.

1. Create a database

Loading…

Expected output:

{
  "@type": "api:DbCreateResponse",
  "api:status": "api:success"
}

2. Define a schema

Production data carries a schema: a Product class with typed fields. Schema writes are commits too — they appear in the same history and diff the same way.

Loading…

Expected output:

[
  "Product"
]

Merges need a schema

Three-way merge (apply, step 7) schema-checks the result. On a schemaless raw_json database the merge fails validation because documents have no class — define a schema before relying on merges in production.

3. Write data — every write is a commit

Loading…

Expected output:

[
  "terminusdb:///data/Product/SKU-001"
]

Each response carries a TerminusDB-Data-Version header with the resulting commit ID — capture it if you need a merge base later.

4. Branch before changing production data

Loading…

Expected output:

{
  "@type": "api:BranchResponse",
  "api:status": "api:success"
}

5. Change data on the branch — main is untouched

Loading…

Expected output:

[
  "terminusdb:///data/Product/SKU-001"
]

6. Review the diff before merging

Loading…

Expected output:

[
  {
    "@id": "Product/SKU-001",
    "price": {
      "@after": 179.99,
      "@before": 149.99,
      "@op": "SwapValue"
    }
  }
]

7. Merge the branch into main

apply performs a three-way merge. before_commit is the common ancestor — here main has not moved since the branch was created, so its head is the correct merge base. In a live application, capture the head commit at branch-creation time rather than re-reading main.

Loading…

Expected output:

{
  "@type": "api:ApplyResponse",
  "api:status": "api:success"
}

If two branches had changed the same field, apply would return HTTP 409 with a conflict report — TerminusDB never silently picks a winner.

Verify the merge landed on main:

Loading…
Example: JSON
{"@id":"Product/SKU-001","@type":"Product","name":"Mechanical Keyboard","price":179.99,"stock":42}

Same workflow in TypeScript

TYPESCRIPT
Run locally
// Schema — a commit like any other
  await client.addDocument(
    { "@type": "Class", "@id": "Product", name: "xsd:string", price: "xsd:decimal", stock: "xsd:integer" },
    { graph_type: "schema" },
    "",
    "Add Product schema",
  )

  // Write — an immutable commit on main
  await client.addDocument(
    { "@id": "Product/SKU-001", "@type": "Product", name: "Mechanical Keyboard", price: 149.99, stock: 42 },
    {},
    "",
    "Add product",
  )

  // Branch — isolate the change from production reads
  await client.branch("reprice")
  client.checkout("reprice")
  await client.updateDocument(
    { "@id": "Product/SKU-001", "@type": "Product", name: "Mechanical Keyboard", price: 179.99, stock: 42 },
    {},
    "",
    "Reprice SKU-001",
  )

  // Review — a structural, field-level diff
  const diff = await client.getVersionDiff("main", "reprice")
  console.log(JSON.stringify(diff, null, 2))

  // Merge — three-way apply onto main (main is the common ancestor here)
  client.checkout("main")
  await client.apply("main", "reprice", "Merge reprice into main")

  // Verify — main now serves the new price
  const product = await client.getDocument({ id: "Product/SKU-001" })
  console.log(product)

Clean up

Loading…

Built for production, not just demos

The version control model is backed by properties production systems need:

  • ACID transactions — a commit either lands in full or not at all; concurrent writers never produce partial states.
  • Schema when you want it — store schemaless JSON, or enforce classes, types and cardinality; schema changes are versioned commits too.
  • Queryable graph — the same documents form a knowledge graph you can traverse with WOQL, GraphQL or path queries.
  • Rollback and recovery — reset a branch to any earlier commit to undo a bad deploy or a bad batch.
  • Space-efficient history — content-addressed, deduplicated storage keeps long histories compact.

Where it runs

TerminusDB is open source and self-hostable — a single Docker container gives you the full versioned database locally or on your own infrastructure: see Docker installation. For a managed service, contact Gettier who host TerminusDB data products with the same API.

When it is not the right tool

Version control for data pays off when your JSON documents are connected, change over time, and carry audit or collaboration requirements. It is not a replacement for a blob store, a fast-moving metrics timeseries database beyond 10-20k docs per second, or a high-throughput append-only log — those workloads do not need this kind of history, branching or merges. We suggest InfluxDB and ClickHouse for time-series data, and S3 or similar for blob storage.

See also

Was this helpful?