Graph Databases: Neo4j and Cypher Query Language
Graph databases store data as nodes and relationships, where the connections between entities are as important as the entities themselves. For connected data — social networks, recommendation engines, fraud detection, knowledge graphs — graph databases can be orders of magnitude faster than relational databases for multi-hop traversals.
Graph Data Model
A graph database has three fundamental building blocks:
- Nodes — entities (people, places, products, accounts)
- Relationships — connections between nodes (FRIENDS_WITH, PURCHASED, OWNS)
- Properties — key-value attributes on both nodes and relationships
(:Person {name: "Alice"}) -[:FRIENDS_WITH]-> (:Person {name: "Bob"})Unlike relational databases where relationships are inferred through foreign keys and JOINs, graph databases store relationships as first-class entities with their own properties. This makes traversing connections fundamentally different: a JOIN operation in SQL requires lookup tables and complex query planning, while a graph traversal follows pointers directly from one node to the next.
When to Use a Graph Database
| Good Fit | Poor Fit |
|---|---|
| Highly connected data (social networks) | Simple CRUD with few relationships |
| Multi-hop traversals (fraud rings, paths) | High-volume transactional data |
| Recommendation engines | Time-series data |
| Knowledge graphs | Large binary blobs |
| Identity and access management | Simple key-value lookups |
| Network and IT operations | Full-text search |
The litmus test: if your SQL queries have five or more JOINs and are becoming unmanageable, or if you need to traverse relationships of variable depth (e.g., “find all accounts within 3 hops of this flagged user”), a graph database will likely outperform a relational database significantly.
Neo4j
Neo4j is the most widely deployed graph database. It implements the property graph model and uses Cypher as its query language.
Key Concepts
- Label — categorizes nodes (e.g.,
:Person,:Product) - Relationship type — names the connection (e.g.,
:FRIENDS_WITH,:BOUGHT) - Indexes — speed up node lookups by property
- Constraints — enforce uniqueness and property existence
// Create an index for fast lookup
CREATE INDEX person_name_index FOR (n:Person) ON (n.name);
// Create a uniqueness constraint
CREATE CONSTRAINT unique_email FOR (n:User) REQUIRE n.email IS UNIQUE;Cypher Query Language
Cypher is Neo4j’s declarative query language, designed to be visually intuitive. Patterns are written in ASCII-art style.
Basic CRUD
// Create nodes
CREATE (alice:Person {name: "Alice", age: 30});
CREATE (bob:Person {name: "Bob", age: 32});
// Create a relationship
MATCH (a:Person {name: "Alice"}), (b:Person {name: "Bob"})
CREATE (a)-[:FRIENDS_WITH {since: 2020}]->(b);
// Query with traversal
MATCH (a:Person {name: "Alice"})-[:FRIENDS_WITH]->(friends)
RETURN friends.name;
// Update properties
MATCH (a:Person {name: "Alice"})
SET a.age = 31;
// Delete
MATCH (a:Person {name: "Alice"})
DETACH DELETE a;Pattern Matching
The power of Cypher comes from expressive patterns:
// Find friends of friends
MATCH (alice:Person {name: "Alice"})-[:FRIENDS_WITH]->()-[:FRIENDS_WITH]->(fof)
RETURN DISTINCT fof.name;
// Variable-length paths — find all nodes within 3 hops
MATCH (alice:Person {name: "Alice"})-[:FRIENDS_WITH*1..3]-(connected)
RETURN DISTINCT connected.name;
// Shortest path between two people
MATCH p = shortestPath(
(alice:Person {name: "Alice"})-[:FRIENDS_WITH*]-(bob:Person {name: "Bob"})
)
RETURN p;Aggregation
// Count friends per person
MATCH (p:Person)-[:FRIENDS_WITH]->(friend)
RETURN p.name, count(friend) AS friend_count
ORDER BY friend_count DESC;
// Average age of friends
MATCH (a:Person {name: "Alice"})-[:FRIENDS_WITH]->(friend)
RETURN avg(friend.age) AS avg_friend_age;Real-World Example: Recommendation Engine
// Recommend products based on what friends of friends bought
MATCH (user:User {id: "user123"})
MATCH (user)-[:FRIENDS_WITH]->()-[:FRIENDS_WITH]->(fof)
MATCH (fof)-[:BOUGHT]->(product:Product)
WHERE NOT EXISTS {
MATCH (user)-[:BOUGHT]->(product)
---
RETURN product.name, count(*) AS mutual_purchases
ORDER BY mutual_purchases DESC
LIMIT 10;This single query replaces a multi-query application code path in a relational database. The graph database follows pointers from user to friends to friends-of-friends to products, counting unique purchases along the way — all in a single, declarative, index-assisted traversal.
Real-World Example: Fraud Detection
Fraud detection is one of the strongest use cases for graph databases. Consider identifying suspicious banking activity:
// Find accounts connected to a flagged account within 3 hops
MATCH path = (flagged:Account {account_id: "FLAGGED-001"})-[:TRANSFERRED_TO*1..3]-(account)
RETURN path
// Find rings — accounts that form cycles (money laundering pattern)
MATCH (a1:Account)-[:TRANSFERRED_TO]->(a2:Account)-[:TRANSFERRED_TO]->(a3:Account)-[:TRANSFERRED_TO]->(a1)
RETURN a1, a2, a3In relational databases, detecting money-laundering rings requires iterative self-joins or recursive CTEs that grow exponentially. A graph database traverses the same patterns in milliseconds.
Graph Traversal Algorithms
Breadth-First Search (BFS)
// BFS from Alice, stopping at first person matching a condition
MATCH (alice:Person {name: "Alice"})
CALL apoc.path.bfs(alice, "FRIENDS_WITH", 10)
YIELD path
UNWIND nodes(path) AS node
RETURN DISTINCT node.name;PageRank
Neo4j Graph Data Science library provides built-in graph algorithms:
// Run PageRank to find influential people
CALL gds.pageRank.write({
nodeProjection: "Person",
relationshipProjection: "FRIENDS_WITH",
writeProperty: "pagerank"
---);
// Find the top 10 most influential people
MATCH (p:Person)
RETURN p.name, p.pagerank
ORDER BY p.pagerank DESC
LIMIT 10;Community Detection (Louvain)
// Detect communities of closely connected people
CALL gds.louvain.write({
nodeProjection: "Person",
relationshipProjection: "FRIENDS_WITH",
writeProperty: "community"
---);
// List communities by size
MATCH (p:Person)
RETURN p.community, count(*) AS members
ORDER BY members DESC;Betweenness Centrality
Identifies nodes that act as bridges between different parts of the graph — critical for understanding information flow and single points of failure:
CALL gds.betweenness.write({
nodeProjection: "Person",
relationshipProjection: "FRIENDS_WITH",
writeProperty: "betweenness"
---);
MATCH (p:Person)
RETURN p.name, p.betweenness
ORDER BY p.betweenness DESC
LIMIT 10;Graph Database vs Relational
| Aspect | Relational (PostgreSQL) | Graph (Neo4j) |
|---|---|---|
| Data model | Tables, rows, foreign keys | Nodes, relationships, properties |
| Joins | Required for related data | Direct pointer traversal |
| Deep traversals | Many JOINs, exponential slowdown | Constant-time per hop |
| Schema | Fixed schema (ALTER TABLE) | Schema-optional |
| Query language | SQL | Cypher |
| Horizontal scaling | Sharding (complex) | Read replicas, clustering |
| Best for | Transactional, tabular data | Connected, relationship-heavy data |
For deep traversal queries (3+ hops), graph databases can be 1,000x faster than equivalent SQL queries because they follow pointers rather than computing JOINs. However, for transactional workloads with simple lookups, relational databases remain more efficient and operationally simpler.
Performance Tips
- Index lookup properties — always index the properties used in MATCH clauses
- Use relationship direction — directed relationships are faster to traverse
- Limit path lengths — unbounded variable-length paths can be expensive
- Profile queries — use
PROFILEbefore queries to see the execution plan - Avoid cartesian products — ensure MATCH clauses produce connected patterns
- Use MERGE carefully — MERGE checks existence before creating, which is slower than CREATE
// Profile a query to see execution details
PROFILE MATCH (a:Person {name: "Alice"})-[:FRIENDS_WITH*1..3]->(connected)
RETURN connected.name;RDF vs Property Graph
The two dominant graph data models serve different ecosystems:
Property Graph Model (Neo4j, JanusGraph) — nodes and relationships carry key-value properties. Relationships are named and directed. This model maps naturally to application domains like social networks, recommendations, and access control.
RDF Model (Apache Jena, RDF4J) — represents data as triples (subject-predicate-object). RDF uses URIs for global identity, which makes it ideal for linked data and knowledge graphs that span organizational boundaries. SPARQL is the query language for RDF.
Choose the property graph model for application development. Choose RDF for data integration, semantic web, and knowledge graphs that merge data from multiple sources.
When to Avoid Graph Databases
Graph databases are not a universal replacement for relational or document databases. Avoid them when:
- Your data has few relationships per entity (less than 2-3 connections per node on average)
- Your queries never traverse beyond a single hop
- You need high-volume transactional processing with simple lookups
- Your team has no experience with graph modeling and Cypher
In these cases, a relational database with well-indexed foreign keys will be simpler, faster, and cheaper to operate.
FAQ
When should I use a graph database instead of a relational database? Use a graph database when your data is highly connected and your queries traverse relationships (friend-of-friend, shortest path, fraud rings). Use relational when your data is tabular with simple joins, or when you need complex aggregations and reporting.
Is Cypher similar to SQL? Cypher uses a similar declarative style (MATCH instead of SELECT, RETURN instead of SELECT, WHERE is shared), but the pattern-matching syntax is unique. WHERE clauses, aggregation with GROUP BY, and ORDER BY work similarly. The graph-specific features like variable-length paths and shortestPath have no SQL equivalent.
How does Neo4j handle ACID transactions? Neo4j provides full ACID guarantees within a single database instance. Multi-database and clustered configurations have different consistency models. For most use cases, Neo4j’s transaction semantics are comparable to PostgreSQL’s.
What are the main graph data models? The two primary models are the Property Graph Model (nodes, relationships, properties, labels — used by Neo4j) and the Resource Description Framework (RDF) model (subject-predicate-object triples — used by knowledge graph systems like Apache Jena). Property graphs are more common for application development.
Can I use graph databases for full-text search? While Neo4j supports basic string matching with CONTAINS and STARTS WITH, it is not designed for full-text search at scale. For text search, pair Neo4j with Elasticsearch. Neo4j handles the graph traversal while Elasticsearch handles the search indexing.
For a comprehensive overview, read our article on Acid Transactions Guide.
For a comprehensive overview, read our article on Database Backup Recovery.