Query the database
You can run a query against the database using Cypher and the method Driver.executeQuery()
.
Driver.executeQuery() was introduced with the version 5.8 of the driver.For queries with earlier versions, see Run your own transactions. |
Due to the usage of |
Write to the database
To create a node representing a person named Alice
, use the Cypher clause MERGE
:
Person
named Alice
const { records, summary } = await driver.executeQuery(
'MERGE (p:Person {name: $name})', (1)
{ name: 'Alice' }, (2)
{ database: 'neo4j' } (3)
)
console.log(
`Created ${summary.counters.updates().nodesCreated} nodes ` +
`in ${summary.resultAvailableAfter} ms.`
)
where (1) specifies the Cypher query, (2) is an object of query parameters, and (3) specifies which database the query should be run against.
MERGE creates a new node matching the requirements unless one already exists, in which case nothing is done.
For strict node creation, use the CREATE clause.
|
Read from the database
To retrieve information from the database, use the Cypher clause MATCH
:
Person
nodesconst { records, summary } = await driver.executeQuery(
'MATCH (p:Person) RETURN p.name AS name',
{},
{ database: 'neo4j' }
)
console.log(
`The query \`${summary.query.text}\` ` +
`returned ${records.length} nodes.\n`
)
for(let record of records) {
console.log(`Person with name: ${record.get('name')}`)
console.log(`Available properties for this node are: ${record.keys}\n`)
}
where result.records
contains the actual result as a list of Record
objects, and result.summary
is a ResultSummary
object, containing a summary of execution from the server.
Update the database
Alice
to add an age
propertyconst { _, summary } = await driver.executeQuery(`
MATCH (p:Person WHERE p.name = $name)
SET p.age = $age
`, { name: 'Alice', age: 42 },
{ database: 'neo4j' }
)
console.log('Query counters:')
console.log(summary.counters.updates())
To create new nodes and relationships linking it to an already existing node, use a combination of the Cypher clauses MATCH
and MERGE
:
KNOWS
between Alice
and Bob
const { records, summary } = await driver.executeQuery(`
MATCH (p:Person WHERE p.name = $name)
MERGE (friend:Person {name: $friendName})
MERGE (p)-[:KNOWS]->(friend)
`, { name: 'Alice', friendName: 'Bob' },
{ database: 'neo4j' }
)
console.log('Query counters:')
console.log(summary.counters.updates())
It may feel tempting to create new relationships with a single |
Delete from the database
To remove a node and any relationship attached to it, use the Cypher clause DETACH DELETE
:
Alice
nodeconst { _, summary } = await session.executeQuery(`
MATCH (p:Person WHERE p.name = $name)
DETACH DELETE p
`, { name: 'Alice' },
{ database: 'neo4j' }
)
console.log('Query counters:')
console.log(summary.counters.updates())
How to pass parameters to queries
Do not hardcode or concatenate parameters directly into queries. Instead, always use placeholders and specify the Cypher parameters as keyword arguments or in a dictionary, as shown in the previous examples. This is for:
-
performance benefits: Neo4j compiles and caches queries, but can only do so if the query structure is unchanged;
-
security reasons: protecting against Cypher injection.
There can be circumstances where your query structure prevents the usage of parameters in all its parts. For those advanced use cases, see Dynamic values in property keys, relationship types, and labels. |
Error handling
To avoid an error in one query crashing your application, you can wrap queries into try/catch
blocks.
We avoid proper error handling throughout this manual to make examples lighter to parse, and because appropriate error handling depends on the application.
Here below an example with a try/catch/finally
block.
try {
const result = await session.executeQuery('MATCH (p:Person) RETURN p')
} catch(err) {
console.log(`Error in query\n${error}`)
} finally {
await session.close()
}
The driver automatically retries to run a failed query, if the failure is deemed to be transient (for example due to temporary server unavailability). |
Query configuration
You can supply a QueryConfig
object as third (optional) parameter to alter the default behavior of .executeQuery()
.
Database selection
It is recommended to always specify the database explicitly with the database
parameter, even on single-database instances.
This allows the driver to work more efficiently, as it does not have to resolve the home database first.
If no database is given, the user’s home database is used.
driver.executeQuery(
'MATCH (p:Person) RETURN p.name',
{},
{
database: 'neo4j'
}
)
Request routing
In a cluster environment, all queries are directed to the leader node by default.
To improve performance on read queries, you can use the configuration routing: 'READ'
to route a query to the read nodes.
driver.executeQuery(
'MATCH (p:Person) RETURN p.name',
{},
{
routing: 'READ', // short for neo4j.routing.READ
database: 'neo4j'
}
)
Routing a write query to READ
nodes will likely result in a runtime error, but do not rely on this for security purposes.
Run queries as a different user
You can execute a query under the security context of a different user with the parameter impersonatedUser
, specifying the name of the user to impersonate.
For this to work, the user under which the Driver
was created needs to have the appropriate permissions.
Impersonating a user is cheaper than creating a new Driver
object.
driver.executeQuery(
'MATCH (p:Person) RETURN p.name',
{},
{
impersonatedUser: 'somebodyElse',
database: 'neo4j'
}
)
When impersonating a user, the query is run within the complete security context of the impersonated user and not the authenticated user (i.e., home database, permissions, etc.).
A full example
const neo4j = require('neo4j-driver');
(async () => {
const URI = 'neo4j://localhost'
const USER = 'neo4j'
const PASSWORD = 'verysecret'
let driver, result
let people = [{name: 'Alice', age: 42, friends: ['Bob', 'Peter', 'Anna']},
{name: 'Bob', age: 19},
{name: 'Peter', age: 50},
{name: 'Anna', age: 30}]
// Connect to database
try {
driver = neo4j.driver(URI, neo4j.auth.basic(USER, PASSWORD))
await driver.verifyConnectivity()
} catch(err) {
return
console.log(`Connection error\n${err}\nCause: ${err.cause}`)
}
// Create some nodes
for(let person of people) {
await driver.executeQuery(
'MERGE (p:Person {name: $person.name, age: $person.age})',
{ person: person },
{ database: 'neo4j' }
)
}
// Create some relationships
for(let person of people) {
if(person.friends != undefined) {
await driver.executeQuery(`
MATCH (p:Person {name: $person.name})
UNWIND $person.friends AS friendName
MATCH (friend:Person {name: friendName})
MERGE (p)-[:KNOWS]->(friend)
`, { person: person },
{ database: 'neo4j' }
)
}
}
// Retrieve Alice's friends who are under 40
result = await driver.executeQuery(`
MATCH (p:Person {name: $name})-[:KNOWS]-(friend:Person)
WHERE friend.age < $age
RETURN friend
`, { name: 'Alice', age: 40 },
{ database: 'neo4j' }
)
// Loop through results and do something with them
for(let person of result.records) {
// `person.friend` is an object of type `Node`
console.log(person.get('friend'))
}
// Summary information
console.log(
`The query \`${result.summary.query.text}\` ` +
`returned ${result.records.length} records ` +
`in ${result.summary.resultAvailableAfter} ms.`
)
await driver.close()
})();
Glossary
- LTS
-
A Long Term Support release is one guaranteed to be supported for a number of years. Neo4j 4.4 is LTS, and Neo4j 5 will also have an LTS version.
- Aura
-
Aura is Neo4j’s fully managed cloud service. It comes with both free and paid plans. Every Neo4j-backed application requires a
Driver
object. - Cypher
-
Cypher is Neo4j’s graph query language that lets you retrieve data from the graph. It is like SQL, but for graphs.
- APOC
-
Awesome Procedures On Cypher (APOC) is a library of (many) functions that can not be easily expressed in Cypher itself.
- Bolt
-
Bolt is the protocol used for interaction between Neo4j instances and drivers. It listens on port 7687 by default.
- ACID
-
Atomicity, Consistency, Isolation, Durability (ACID) are properties guaranteeing that database transactions are processed reliably. An ACID-compliant DBMS ensures that the data in the database remains accurate and consistent despite failures.
- eventual consistency
-
A database is eventually consistent if it provides the guarantee that all cluster members will, at some point in time, store the latest version of the data.
- causal consistency
-
A database is causally consistent if read and write queries are seen by every member of the cluster in the same order. This is stronger than eventual consistency.
- null
-
The null marker is not a type but a placeholder for absence of value. For more information, see Cypher Manual — Working with
null
. - transaction
-
A transaction is a unit of work that is either committed in its entirety or rolled back on failure. An example is a bank transfer: it involves multiple steps, but they must all succeed or be reverted, to avoid money being subtracted from one account but not added to the other.
- backpressure
-
Backpressure is a force opposing the flow of data. It ensures that the client is not being overwhelmed by data faster than it can handle.
- transaction function
-
A transaction function is a callback executed by an
executeRead
orexecuteWrite
call. The driver automatically re-executes the callback in case of server failure. - Driver
-
A
Driver
object holds the details required to establish connections with a Neo4j database.
Was this page helpful?