Queries¶
CongraphDB provides three ways to query your graph:
- Cypher Query Language (below) - Industry-standard graph query language
- JavaScript Native API - Programmatic interface for CRUD operations
- Navigator API - Fluent graph traversal API
This page covers the Cypher query language. For JavaScript API queries, see JavaScript API Queries below, or the JavaScript API Reference.
Cypher Query Language¶
CongraphDB uses Cypher, a graph query language that makes it easy to work with connected data.
Basic Query Structure¶
MATCH Clause¶
Find nodes and relationships in your graph.
Find All Nodes¶
Find Relationships¶
const result = await conn.query(`
MATCH (u:User)-[k:Knows]->(f:User)
RETURN u.name AS user, f.name AS friend, k.since
`);
Filter with WHERE¶
CREATE Clause¶
Create new nodes and relationships.
Create a Node¶
Create Nodes with Relationships¶
await conn.query(`
CREATE (alice:User {name: 'Alice', age: 30})
-[:Knows {since: 2020}]->
(bob:User {name: 'Bob', age: 25})
`);
Create Relationship Between Existing Nodes¶
await conn.query(`
MATCH (alice:User {name: 'Alice'})
MATCH (bob:User {name: 'Bob'})
CREATE (alice)-[:Knows {since: 2020}]->(bob)
`);
Variable-Length Paths¶
Find patterns with varying relationship lengths.
// Friends of friends (1 to 3 hops)
const result = await conn.query(`
MATCH (u:User {name: 'Alice'})-[:Knows*1..3]->(f:User)
RETURN DISTINCT f.name
`);
Aggregation¶
// Count friends per user
const result = await conn.query(`
MATCH (u:User)-[:Knows]->(f:User)
RETURN u.name, COUNT(f) AS friend_count
`);
// Average age by user
const result = await conn.query(`
MATCH (u:User)-[:Knows]->(f:User)
RETURN u.name, AVG(f.age) AS avg_friend_age
`);
CASE Expressions¶
CongraphDB supports CASE expressions for conditional logic within your queries.
Simple CASE¶
const result = await conn.query(`
MATCH (u:User)
RETURN u.name,
CASE u.age
WHEN 18 THEN 'Adult'
WHEN 65 THEN 'Senior'
ELSE 'Other'
END AS status
`);
Generic CASE¶
const result = await conn.query(`
MATCH (u:User)
RETURN u.name,
CASE
WHEN u.age < 18 THEN 'Minor'
WHEN u.age >= 18 AND u.age < 65 THEN 'Adult'
ELSE 'Senior'
END AS life_stage
`);
Ordering and Limiting¶
CongraphDB supports ORDER BY, SKIP, and LIMIT clauses for controlling query results.
ORDER BY¶
Sort results by one or more columns:
// Sort by single column (descending)
const result = await conn.query(`
MATCH (u:User)
RETURN u.name, u.age
ORDER BY u.age DESC
`);
// Sort by multiple columns
const result = await conn.query(`
MATCH (p:Post)
RETURN p.title, p.created, p.author
ORDER BY p.created DESC, p.title ASC
`);
// Sort with aggregation
const result = await conn.query(`
MATCH (u:User)-[:KNOWS]->(f:User)
RETURN u.name, COUNT(f) AS friend_count
ORDER BY friend_count DESC
`);
SKIP and LIMIT¶
Paginate through results:
// Basic pagination (skip first 10, get next 20)
const result = await conn.query(`
MATCH (u:User)
RETURN u.name, u.age
ORDER BY u.name
SKIP 10 LIMIT 20
`);
// Get top N results
const result = await conn.query(`
MATCH (u:User)
RETURN u.name, u.score
ORDER BY u.score DESC
LIMIT 10
`);
// Combined pagination
const page = 2;
const pageSize = 25;
const result = await conn.query(`
MATCH (p:Post)
RETURN p.title
ORDER BY p.created DESC
SKIP ${page * pageSize} LIMIT ${pageSize}
`);
UNION¶
Combine results from multiple MATCH patterns using the UNION operator. This is useful when you need to merge results from different query patterns.
Basic UNION¶
// Union of two relationship types
const result = await conn.query(`
MATCH (u:User)-[:FOLLOWS]->(f:User)
RETURN u.name AS name, f.name AS value
UNION
MATCH (u:User)-[:KNOWS]->(k:User)
RETURN u.name AS name, k.name AS value
`);
UNION with Different Node Types¶
// Find contacts from different entity types
const result = await conn.query(`
MATCH (u:User)
RETURN u.name AS name, u.email AS contact, 'User' AS type
UNION
MATCH (o:Organization)
RETURN o.name AS name, o.website AS contact, 'Organization' AS type
`);
UNION with Aggregations¶
// Combine different counts
const result = await conn.query(`
MATCH (u:User)-[:POSTED]->(:Post)
RETURN u.name AS name, COUNT(*) AS count, 'Posts' AS source
UNION
MATCH (u:User)-[:COMMENTED]->(:Comment)
RETURN u.name AS name, COUNT(*) AS count, 'Comments' AS source
ORDER BY count DESC
`);
DELETE Clause¶
Remove nodes and relationships.
// Delete a relationship
await conn.query(`
MATCH (u:User {name: 'Alice'})-[k:Knows]->(f:User {name: 'Bob'})
DELETE k
`);
// Delete a node and its relationships
await conn.query(`
MATCH (u:User {name: 'Bob'})
DETACH DELETE u
`);
SET Clause¶
Update properties.
REMOVE Clause¶
Remove properties and labels from nodes and relationships.
// Remove a property
await conn.query(`
MATCH (u:User {name: 'Alice'})
REMOVE u.age
`);
// Remove a label
await conn.query(`
MATCH (u:User {name: 'Alice'})
REMOVE u:Active
`);
WRITE Clause¶
Combine read and write operations in a single Cypher statement. The WRITE clause allows you to perform modifications while also returning results from the same query.
WRITE with CREATE¶
// Create nodes and return them
const result = await conn.query(`
WRITE CREATE (u:User {name: 'Dave', age: 28})
RETURN u
`);
WRITE with SET¶
// Update and return the modified node
const result = await conn.query(`
MATCH (u:User {name: 'Alice'})
WRITE SET u.age = 32, u.lastUpdated = timestamp()
RETURN u.name, u.age, u.lastUpdated
`);
WRITE with DELETE¶
// Delete and return what was deleted
const result = await conn.query(`
MATCH (u:User {name: 'Bob'})-[k:KNOWS]->(f:User)
WRITE DELETE k
RETURN u.name AS from_user, f.name AS to_user
`);
WRITE with Multiple Operations¶
// Complex write operation with reads
const result = await conn.query(`
MATCH (u:User {name: 'Alice'})
WRITE CREATE (u)-[:KNOWS {since: 2024}]->(new:User {name: 'Eve', age: 27})
RETURN u.name AS user, new.name AS created
`);
WRITE with MERGE¶
// Merge and return the result
const result = await conn.query(`
WRITE MERGE (u:User {name: 'Frank'})
ON CREATE SET u.created = timestamp()
ON MATCH SET u.lastSeen = timestamp()
RETURN u.name, u.created, u.lastSeen
`);
MERGE Clause¶
Match existing nodes or create new ones if they don't exist. Supports conditional updates with ON MATCH and ON CREATE.
// Basic MERGE
await conn.query(`
MERGE (u:User {name: 'Alice'})
`);
// MERGE with conditional updates
await conn.query(`
MERGE (u:User {name: 'Alice'})
ON CREATE SET u.created = timestamp(), u.age = 30
ON MATCH SET u.lastSeen = timestamp(), u.visitCount = coalesce(u.visitCount, 0) + 1
`);
Pattern Comprehensions¶
Pattern comprehensions allow you to create collections from patterns in your graph. They're useful for extracting related data into lists.
Single-Node Pattern Comprehensions¶
// Collect names of all friends
const result = await conn.query(`
MATCH (u:User {name: 'Alice'})
RETURN [(u)-[:KNOWS]->(f) | f.name] AS friend_names
`);
// Result: { friend_names: ['Bob', 'Charlie', 'David'] }
Relationship Patterns¶
// Collect friend names with additional filtering
const result = await conn.query(`
MATCH (u:User {name: 'Alice'})
RETURN [(u)-[:KNOWS]->(f) WHERE f.age > 25 | f.name] AS older_friends
`);
// Result: { older_friends: ['Bob', 'Charlie'] }
Multi-Hop Patterns¶
// Collect friends of friends
const result = await conn.query(`
MATCH (u:User {name: 'Alice'})
RETURN [(u)-[:KNOWS]->(f)-[:KNOWS]->(ff) | ff.name] AS friends_of_friends
`);
Outer Variable Scope¶
Pattern comprehensions can reference variables from the outer query context:
// Filter comprehension using outer variable
const result = await conn.query(`
MATCH (u:User {name: 'Alice'})
WHERE u.age > 30
RETURN [(u)-[:KNOWS]->(f) WHERE f.age > u.age - 5 | f.name] AS peers
`);
Nested Comprehensions¶
// Get friends with their friends
const result = await conn.query(`
MATCH (u:User {name: 'Alice'})
RETURN [
(u)-[:KNOWS]->(f) |
{ name: f.name, friends: [(f)-[:KNOWS]->(ff) | ff.name] }
] AS social_network
`);
Temporal Types¶
CongraphDB supports temporal types for working with dates and times.
Date Type¶
// Create a date
const result = await conn.query(`
RETURN date('2024-03-15') AS today
`);
// Result: { today: '2024-03-15' }
// Use in WHERE clause
await conn.query(`
MATCH (e:Event)
WHERE e.date >= date('2024-01-01')
RETURN e.title
`);
DateTime Type¶
// Current datetime
const result = await conn.query(`
RETURN datetime() AS now
`);
// Parse datetime string
const result = await conn.query(`
RETURN datetime('2024-03-15T10:30:00') AS meeting_time
`);
// Compare datetimes
await conn.query(`
MATCH (o:Order)
WHERE o.created_at > datetime('2024-03-01T00:00:00')
RETURN o.id
`);
Duration Type¶
// Parse duration
const result = await conn.query(`
RETURN duration('P1DT2H30M') AS time_span
`);
// Result: 1 day, 2 hours, 30 minutes
// Calculate duration between dates
await conn.query(`
MATCH (u:User {name: 'Alice'})-[:KNOWS {since: date('2020-01-01')}]->(f:User)
RETURN duration.between(date('2020-01-01'), date()).years AS years_known
`);
Temporal Functions Reference¶
| Function | Description | Example |
|---|---|---|
date(string) |
Parse or create date | date('2024-03-15') |
datetime() |
Get current datetime | datetime() |
datetime(string) |
Parse datetime string | datetime('2024-03-15T10:30:00') |
timestamp() |
Unix timestamp in ms | timestamp() |
duration(string) |
Parse ISO 8601 duration | duration('P1D') |
Map Literals¶
Create maps (objects) directly in your queries:
// Create a map literal
const result = await conn.query(`
RETURN {name: 'Alice', age: 30, active: true} AS user_data
`);
// Result: { user_data: { name: 'Alice', age: 30, active: true } }
// Use with pattern comprehensions
const result = await conn.query(`
MATCH (u:User {name: 'Alice'})-[:KNOWS]->(f:User)
RETURN {
name: f.name,
age: f.age,
is_adult: f.age >= 18
} AS friend_info
`);
Multi-Label Nodes¶
Nodes can have multiple labels for categorization:
// Create node with multiple labels
await conn.query(`
CREATE (u:User:Admin:Premium {name: 'Alice', role: 'admin'})
`);
// Query by multiple labels
await conn.query(`
MATCH (u:User:Admin)
RETURN u.name
`);
// Get all labels for a node
const result = await conn.query(`
MATCH (u:User {name: 'Alice'})
RETURN labels(u) AS all_labels
`);
// Result: { all_labels: ['User', 'Admin', 'Premium'] }
// Filter by label presence
await conn.query(`
MATCH (u)
WHERE 'Admin' IN labels(u)
RETURN u.name
`);
Path Finding Functions¶
shortestPath()¶
Find the shortest path between two nodes:
// Basic shortest path
const result = await conn.query(`
MATCH p = shortestPath(
(alice:User {name: 'Alice'})-[:KNOWS*]-(bob:User {name: 'Bob'})
)
RETURN [node IN nodes(p) | node.name] AS path
`);
// Result: { path: ['Alice', 'Charlie', 'Bob'] }
// Limit path length
const result = await conn.query(`
MATCH p = shortestPath(
(alice:User {name: 'Alice'})-[:KNOWS*..5]-(bob:User {name: 'Bob'})
)
RETURN length(p) AS hops, [node IN nodes(p) | node.name] AS path
`);
allShortestPaths()¶
Find all shortest paths at minimum length:
// Find all shortest paths
const result = await conn.query(`
MATCH p = allShortestPaths(
(alice:User {name: 'Alice'})-[:KNOWS*..3]-(bob:User {name: 'Bob'})
)
RETURN [node IN nodes(p) | node.name] AS path
`);
// Returns multiple rows, one for each shortest path
Relationship Directions¶
// Outgoing only
MATCH p = shortestPath((a)-[:FOLLOWS*]->(b))
// Incoming only
MATCH p = shortestPath((a)<-[:FOLLOWS*]-(b))
// Undirected (any direction)
MATCH p = shortestPath((a)-[:KNOWS*]-(b))
Complete Examples¶
Social Network Query¶
// Find users who know someone named 'Alice'
const result = await conn.query(`
MATCH (u:User)-[:Knows]->(alice:User {name: 'Alice'})
RETURN u.name, u.email
`);
Recommendation Query¶
// Recommend friends: friends of friends I don't know yet
const result = await conn.query(`
MATCH (me:User {name: 'Alice'})-[:Knows]->(friend)-[:Knows]->(foaf:User)
WHERE NOT (me)-[:Knows]->(foaf) AND me <> foaf
RETURN foaf.name, COUNT(friend) AS mutual_friends
ORDER BY mutual_friends DESC
LIMIT 10
`);
Shortest Path¶
// Find shortest path between two users
const result = await conn.query(`
MATCH p=shortestPath(
(alice:User {name: 'Alice'})-[:Knows*]-(bob:User {name: 'Bob'})
)
RETURN [node IN nodes(p) | node.name] AS path
`);
Working with Results¶
const result = await conn.query(`
MATCH (u:User)
RETURN u.name, u.age
`);
// Get all rows at once
const rows = result.getAll();
for (const row of rows) {
console.log(row);
}
// Iterate row by row (streaming)
while (result.hasMore()) {
const row = result.getNext();
console.log(row);
}
// Get column information
console.log(result.getColumnNames()); // ['u.name', 'u.age']
console.log(result.getColumnDataTypes()); // ['STRING', 'INT64']
// Always close when done
result.close();
Query Execution Statistics¶
CongraphDB tracks performance metrics for every query execution. You can access these via the statistics property.
const result = await conn.query("MATCH (u:User) RETURN u.name");
console.log(result.statistics);
// Output:
// {
// query: "MATCH (u:User) RETURN u.name",
// execution_time_ms: 0.12,
// row_count: 5,
// query_type: "MATCH"
// }
| Metric | Description |
|---|---|
query |
The original query string |
execution_time_ms |
Time taken to execute (excluding network/FFI overhead) |
row_count |
Number of rows in the result set |
query_type |
Type of query (MATCH, CREATE, DELETE, etc.) |
JavaScript API Queries¶
CongraphDB's JavaScript Native API provides a programmatic alternative to Cypher for many common operations. This is especially useful for:
- Simple CRUD operations
- Application-specific data access
- Type safety with TypeScript
- Developers who prefer method calls over query strings
CRUD Operations¶
Create Nodes¶
const { Database, CongraphDBAPI } = require('congraphdb');
const db = new Database('./my-graph.cgraph');
await db.init();
const api = new CongraphDBAPI(db);
// Create a node
const alice = await api.createNode('User', {
name: 'Alice',
age: 30,
email: 'alice@example.com'
});
// Returns: { _id: '...', name: 'Alice', age: 30, email: '...' }
Create Relationships¶
const bob = await api.createNode('User', {
name: 'Bob',
age: 25
});
await api.createEdge(alice._id, 'KNOWS', bob._id, {
since: 2020
});
Read Nodes¶
// Get by ID
const node = await api.getNode(alice._id);
// Get all nodes by label
const users = await api.getNodesByLabel('User');
Update Nodes¶
Delete Nodes¶
// Delete without relationships
await api.deleteNode(alice._id);
// Delete with relationships (detach)
await api.deleteNode(alice._id, true);
Pattern Matching Queries¶
The find() method provides declarative pattern matching similar to graph triple stores.
Basic Pattern Matching¶
// Find Alice's friends
const friends = await api.find({
subject: alice._id,
predicate: 'KNOWS',
object: api.v('friend')
});
// Results: [{ friend: { name: 'Bob', age: 25, ... } }, ...]
Pattern with Variables¶
// Find all KNOWS relationships
const relationships = await api.find({
subject: api.v('person'),
predicate: 'KNOWS',
object: api.v('friend')
});
// Results: [
// { person: { name: 'Alice', ... }, friend: { name: 'Bob', ... } },
// ...
// ]
Filtered Pattern Matching¶
// Find friends over age 25
const friends = await api.find({
subject: alice._id,
predicate: 'KNOWS',
object: api.v('friend')
}, {
where: 'friend.age > 25'
});
Navigator Traversal Queries¶
The Navigator API provides fluent chaining for graph traversal, ideal for multi-hop queries.
One-Hop Traversal¶
Multi-Hop Traversal¶
// Friends of friends
const friendsOfFriends = await api.nav(alice._id)
.out('KNOWS')
.out('KNOWS')
.values();
Bidirectional Traversal¶
// All connections (incoming and outgoing)
const connections = await api.nav(alice._id)
.both('KNOWS')
.values();
Filtered Traversal¶
// Friends in specific city
const nycFriends = await api.nav(alice._id)
.out('KNOWS')
.where('city = "NYC"')
.values();
// Or with JavaScript function
const youngFriends = await api.nav(alice._id)
.out('KNOWS')
.where(f => f.age < 30)
.values();
Limited Results¶
Counting Results¶
// Count friends without retrieving all data
const count = await api.nav(alice._id)
.out('KNOWS')
.count();
Path Finding¶
// Find shortest path to Bob
const path = await api.nav(alice._id)
.out('KNOWS')
.to(bob._id)
.values();
Edge Queries¶
// Get all edges from a node
const outgoing = await api.getEdges({ from: alice._id });
// Get all edges to a node
const incoming = await api.getEdges({ to: alice._id });
// Get edges by type
const knowsEdges = await api.getEdges({ type: 'KNOWS' });
// Combined filters
const results = await api.getEdges({
from: alice._id,
type: 'KNOWS'
});
Transaction Queries¶
await api.transaction(async (txApi) => {
const alice = await txApi.createNode('User', { name: 'Alice' });
const bob = await txApi.createNode('User', { name: 'Bob' });
await txApi.createEdge(alice._id, 'KNOWS', bob._id);
// All operations commit if no error is thrown
});
Async Iteration¶
// Iterate over results
for await (const friend of api.nav(alice._id).out('KNOWS')) {
console.log(friend.name);
}
Comparison: Cypher vs JavaScript API vs Navigator¶
| Operation | Cypher | JavaScript API | Navigator |
|---|---|---|---|
| Find Alice's friends | MATCH (a:User {name: 'Alice'})-[:KNOWS]->(f) RETURN f |
api.find({subject: alice._id, predicate: 'KNOWS', object: api.v('f')}) |
api.nav(alice._id).out('KNOWS').values() |
| Friends of friends | MATCH (a)-[:KNOWS]->()-[:KNOWS]->(f) RETURN f |
Chain find() calls |
api.nav(id).out('KNOWS').out('KNOWS').values() |
| Filter results | WHERE f.age > 25 |
where: 'f.age > 25' |
.where(f => f.age > 25) |
| Limit results | LIMIT 10 |
Manual filtering | .limit(10) |
| Create node | CREATE (u:User {...}) |
api.createNode('User', {...}) |
N/A |
| Delete node | DETACH DELETE u |
api.deleteNode(id, true) |
N/A |
| Shortest path | shortestPath((a)-[*]-(b)) |
Use Cypher | .to(targetId).values() |
When to Use Each Interface¶
Use Cypher for: - Complex multi-hop queries with conditions - Aggregations and analytics - Pattern comprehensions - Complex filtering and sorting
Use JavaScript API (find) for: - Simple pattern matching - When you need variable binding - Programmatic query building
Use Navigator for: - Multi-hop traversals - Fluent chaining - Path finding - When code readability is important
Use JavaScript API (CRUD) for: - Create, read, update, delete operations - Application-specific data access - When you want type safety
For a detailed decision guide, see Choosing Your Query Interface.
Next Steps¶
- Transactions — Group operations into transactions
- Vector Search — Semantic search with embeddings