--- tags: - explanation - beginner title: TerminusDB vs Neo4j nextjs: metadata: title: TerminusDB vs Neo4j — Document Graph Database Comparison description: A neutral, factual comparison of TerminusDB and Neo4j — data model, query language, version control, schema enforcement, and when to choose each. keywords: terminusdb vs neo4j, graph database comparison, neo4j alternative, document graph database, cypher vs woql, property graph vs rdf openGraph: images: https://assets.terminusdb.com/docs/technical-documentation-terminuscms-og.png alternates: canonical: https://terminusdb.org/docs/terminusdb-vs-neo4j/ media: [] lastUpdated: "2026-05-01" --- A factual comparison of TerminusDB and Neo4j for developers evaluating graph databases. Both are graph databases, but they serve different use cases with fundamentally different architectures. ## Executive summary **Neo4j** is a property graph database optimised for real-time graph traversal at scale — social networks, recommendation engines, fraud detection. It uses the Cypher query language and stores data as labeled nodes with typed relationships. **TerminusDB** is a document graph database with built-in version control — it stores JSON documents in a schema-enforced graph, tracks every change as an immutable commit, and supports branch, diff, merge, and time-travel. It uses the WOQL datalog engine for queries. The key architectural difference: Neo4j is a mutable database designed for high-throughput real-time queries. TerminusDB is an immutable database designed for collaborative data workflows with full audit history. --- ## Comparison table | Aspect | TerminusDB | Neo4j | |--------|-----------|-------| | **Data model** | JSON documents decomposed into RDF triples | Property graph (nodes + relationships + properties) | | **Schema** | Enforced on every transaction (closed-world) | Optional constraints (open-world by default) | | **Query language** | WOQL (Datalog with unification) | Cypher (declarative pattern matching) | | **Version control** | Built-in: branch, merge, diff, time-travel | Not built-in (requires external tooling) | | **Immutability** | Append-only delta layers, never mutates | Mutable in-place updates | | **ACID transactions** | Full ACID with snapshot isolation | Full ACID | | **Collaboration** | Clone, push, pull between instances | Clustering for HA, no data-level collaboration | | **Scalability model** | Single-node with delta compression | Clustering (Enterprise), sharding (Aura) | | **Deployment** | Docker container, ~120 MB | JVM-based, requires Java runtime | | **Licence** | Apache 2.0 | GPL (Community) / Commercial (Enterprise) | | **Primary interface** | HTTP REST API + client libraries | Bolt protocol + HTTP + client drivers | | **Graph traversal** | WOQL `path()` expressions | Cypher variable-length patterns | | **Audit trail** | Every commit has author, message, timestamp | Requires custom implementation | | **Schema migration** | Schema weakening (backward-compatible only) | Manual migration scripts | --- ## Data model ### Neo4j: property graph Neo4j stores data as **nodes** (entities) connected by **relationships** (edges). Both nodes and relationships carry **properties** (key-value pairs) and **labels** (type tags). ```cypher CREATE (p:Person {name: "Alice", age: 30}) CREATE (c:City {name: "London"}) CREATE (p)-[:LIVES_IN {since: 2020}]->(c) ``` The property graph model is intuitive for developers coming from object-oriented backgrounds — entities feel like objects, relationships feel like pointers. ### TerminusDB: document graph TerminusDB stores data as **JSON documents** that are decomposed into a typed graph of triples. The schema defines document types and their relationships: ```json {"@type": "Person", "name": "Alice", "age": 30, "lives_in": "City/London"} ``` Under the hood, this becomes RDF triples that can be traversed with WOQL. You interact with JSON documents; the graph is the engine: ```javascript WOQL.and( WOQL.triple("v:Person", "rdf:type", "@schema:Person"), WOQL.triple("v:Person", "name", "v:Name"), WOQL.triple("v:Person", "lives_in", "v:City"), WOQL.triple("v:City", "name", "v:CityName") ) ``` --- ## Query language ### Cypher (Neo4j) Cypher uses ASCII-art patterns that visually resemble the graph structure: ```cypher MATCH (p:Person)-[:LIVES_IN]->(c:City) WHERE c.name = "London" RETURN p.name, p.age ``` Cypher excels at expressing graph patterns intuitively. Variable-length path matching (`*1..5`) handles recursive traversal concisely. ### WOQL (TerminusDB) WOQL uses Datalog-style pattern matching with unification: ```javascript WOQL.and( WOQL.triple("v:Person", "lives_in", "v:City"), WOQL.triple("v:City", "name", WOQL.string("London")), WOQL.triple("v:Person", "name", "v:Name"), WOQL.triple("v:Person", "age", "v:Age") ) ``` WOQL's strength is composability — queries are programs that can be nested, abstracted, and combined. The `path()` operator handles recursive traversal. --- ## Version control This is where the databases diverge most significantly. ### TerminusDB: git for data Every write in TerminusDB creates an immutable commit. You can: - **Branch** — create isolated workspaces for changes - **Diff** — compare any two states at the field level - **Merge** — apply changes from one branch to another - **Time-travel** — query the database at any historical commit - **Clone** — replicate databases between instances This enables collaborative data workflows: multiple teams work on branches, review changes via diff, and merge when ready — exactly like Git for code. ### Neo4j: no built-in version control Neo4j mutates data in place. To achieve version control, you would need to: - Build a custom changelog system - Use external tools (e.g., Liquigraph for schema changes) - Implement your own branch/merge logic at the application layer For use cases that need audit trails or collaborative editing, this is significant additional engineering work. --- ## Schema enforcement ### TerminusDB: closed-world assumption TerminusDB **enforces schema on every transaction**. You cannot insert a document that violates its class definition. This provides strong guarantees: - Every document conforms to its type - Relationships are typed and validated - Schema changes follow the weakening principle (backward-compatible only) ### Neo4j: open-world by default Neo4j is schema-optional. You can add constraints (uniqueness, existence, node key) but the graph is open-world by default — any node can have any property, any label combination. Neo4j 5+ added type constraints and property type constraints, but enforcement is opt-in rather than default. --- ## When to choose Neo4j - **Real-time recommendation engines** — Neo4j's query planner is optimised for low-latency traversal at scale - **Social network analysis** — finding shortest paths, community detection, influence scoring - **Fraud detection** — pattern matching across transaction networks in real time - **Large-scale graph analytics** — when you need graph algorithms (PageRank, betweenness centrality) - **Existing Cypher expertise** — large ecosystem of tools, connectors, and community resources - **Clustering requirements** — Neo4j Enterprise provides multi-node clusters for HA ## When to choose TerminusDB - **Collaborative data workflows** — teams editing shared datasets with branch/merge workflows - **Regulated industries** — finance, healthcare, compliance where full audit trail is mandatory - **Schema-first development** — when you want the database to enforce data integrity, not the application - **Content versioning** — knowledge bases, documentation, reference data that evolve over time - **Data lineage and compliance** — proving what changed, when, and by whom - **Lightweight deployment** — single Docker container, no JVM, minimal infrastructure - **Distributed data collaboration** — clone and sync between instances (edge computing, partner data sharing) --- ## Worked example: tracking product changes Suppose you need to track product catalogue changes with full history and review workflow. ### In Neo4j You would need to model versioning explicitly: ```cypher CREATE (p:Product {id: "widget-1", name: "Widget", price: 9.99, version: 1}) CREATE (p)-[:SUPERSEDED_BY]->(:Product {id: "widget-1", name: "Widget Pro", price: 14.99, version: 2}) ``` History, diff, and rollback become application-level concerns. ### In TerminusDB Version control is built in: ```bash # Create a branch for the price change curl -X POST http://localhost:6363/api/branch/admin/catalogue/local/branch/price-update \ -d '{"origin": "admin/catalogue/local/branch/main"}' # Update the product on the branch curl -X PUT "http://localhost:6363/api/document/admin/catalogue/local/branch/price-update?author=alice&message=Update+widget+price" \ -d '{"@id": "Product/widget-1", "@type": "Product", "name": "Widget Pro", "price": 14.99}' # See exactly what changed curl -X POST http://localhost:6363/api/diff/admin/catalogue \ -d '{"before_data_version": "main", "after_data_version": "price-update"}' # Merge when approved curl -X POST http://localhost:6363/api/apply/admin/catalogue/local/branch/main \ -d '{"before_commit": "main", "after_commit": "price-update", "commit_info": {"author": "alice", "message": "Merge price update"}}' ``` The full history is preserved. You can time-travel to see the product at any point. Diff shows exactly what changed. No application-level versioning code required. --- ## Summary Neo4j and TerminusDB are both graph databases, but optimised for different problems: - **Neo4j** excels at real-time graph traversal, analytics, and pattern matching at scale - **TerminusDB** excels at collaborative, auditable data workflows with built-in version control They are not direct substitutes — they serve different architectural needs. Choose based on whether your primary challenge is **querying complex relationships in real time** (Neo4j) or **managing evolving data with history and collaboration** (TerminusDB).