The Neo4j JavaScript Driver provides you with two APIs for consuming results:
-
Promise API
-
Streaming API
The Promise API
The most common method of consuming results is with the Promise API.
When using session.run()
, tx.run()
, or one of the two transaction functions, the query will return a Promise.
Once the query has finished with all results results streamed to the Driver, the method resolves with a Result object.
Here is an example query call using async
/await
.
try {
const res = await session.executeRead(tx =>
tx.run(
'MATCH (p:Person) RETURN p.name AS name LIMIT 10'
)
)
const names = res.records.map(row => {
return row.get('name')
})
// `names` is an array of strings
console.log(names)
}
catch (e) {
// There was a problem with the
// database connection or the query
console.log(e)
}
finally {
await session.close()
}
By using the await
keyword, the Result object is assigned to the res
variable once the database has stopped streaming records to the Driver.
Here is an example query utilizing the then()
function from the returned Promise.
session.executeRead(tx => tx.run(
'MATCH (p:Person) RETURN p.name AS name LIMIT 10')
)
.then(res => {
return res.records.map(row => {
return row.get('name')
})
})
.then(names => {
// `names` is an array of strings
console.log(names)
})
.catch(e => {
// There was a problem with the
// database connection or the query
console.log(e)
})
// Finally, close the session
.finally(() => session.close())
Once the database has stopped streaming records to the Driver, the Promise will resolve with a Result object.
The Result
The Result object, defined as res
in the examples above, contains the total number of records received by the Driver along with a set of additional meta data about the query.
An individual row of results is referred to as a record
, and can be accessed by the res.records
array.
A record refers to the keyed set of values specified in the RETURN
portion of the statement.
If no RETURN
values are specified, the query will not return any results, and as a result res.records
will be an empty array.
The meta data included in the Result include statistics on how many nodes and relationships were created, updated, or deleted as a result of the query.
Records
You can access the records returned by the query through the records
property.
This property is a native JavaScript array which can be iterated over, for example using the .map()
or .foreach()
methods.
const names = res.records.map(row => {
return row.get('name')
})
Key or Index
You can either access a value within the record by using the alias as specified in theRETURN
portion of the Cypher statement or by specifing the column number as an integer.
The available keys can be accessed through res.keys()
.You can also iterate over a result within a for
loop.
for (const record in res.records) {
console.log(record.get('name'))
}
Result Summary
You can access summary information about the query through the summary
property.
Included in this summary is information about the server, the query, execution times, and a counters
object which provide statistics about the query.
For example, to get information about how long the query took to complete, you can use the following property:
// Time in milliseconds before receiving the first result
console.log(res.summary.resultAvailableAfter) // 10
// Time in milliseconds once the final result was consumed
console.log(res.summary.resultConsumedAfter) // 30
Another interesting part of the summary is the counters
property, which holds numerical data about the queries execution.
To access statistics as a result of a Write Transaction, you can call the updates()
method on the counters
property:
console.log(res.summary.counters.updates())
/*
{
nodesCreated: 10,
nodesDeleted: 0,
relationshipsCreated: 5,
relationshipsDeleted: 0,
propertiesSet: 20,
labelsAdded: 10,
labelsRemoved: 0,
indexesAdded: 0,
indexesRemoved: 0,
constraintsAdded: 0,
constraintsRemoved: 0
}
*/
The Streaming API
The drawback to the Promise API method of consuming results, is that the Promise will only resolve after all results have been received by the Driver.
There may be occasions where you have more complex, or slower-running queries in your application but wish to update a front-end application as results become available. For example, many Flight search websites have complex queries that take up to a minute to complete, but simple, more expensive flights may be available immediately.
In this case, you can use the subscribe()
method to consume results as they become available.
This method can be used when calling session.run()
or tx.run()
within a transaction function.
You can use this method with websockets, or a similar technology to update the front-end application with new results as they are made available.
The subscribe()
method accepts one argument, an object of callbacks:
-
onKeys
- is called when keys are made available for all records. -
onNext
- called when the next record is available. -
onError
- called if an error is thrown. -
onCompleted
- called when all records have been consumed and no error has been thrown.
// Run a Cypher statement, reading the result in a streaming manner as records arrive:
session
.run('MERGE (alice:Person {name : $nameParam}) RETURN alice.name AS name', {
nameParam: 'Alice'
})
.subscribe({
onKeys: keys => {
console.log(keys) // ['name]
},
onNext: record => {
console.log(record.get('name')) // 'Alice'
},
onCompleted: (summary) => {
// `summary` holds the same information as `res.summary`
// Close the Session
session.close()
},
onError: error => {
console.log(error)
}
})
Reactive Streams with RxJS
The Drivers also offer out-of-the-box support for RxJS streams.
To create a reactive session, call the rxSession()
method on the driver.
The execution of the query is almost identical, with the exception of a records()
method which returns an RxJS Observable
, to which you can apply operators.
const res = await rxSession
.executeWrite(txc =>
txc
.run('MERGE (p:Person) RETURN p.name AS name LIMIT 10')
.records()
.pipe(
map(record => record.get('name'))
)
)
Check Your Understanding
1. What is the drawback of using the Promise API to consume results?
-
❏ The Promise API is only available to Enterprise customers.
-
✓ Results are only available once the Driver has received the final record.
-
❏ You can only use the Promise API within a Read Transactions.
Hint
If you are not subscribing to the record stream, you will only be able to access the first record once the entire stream has finished.
Solution
Results are only available once the Driver has received the final record. This can provide a negative experience for users waiting for the results of a long-running query.
Lesson Summary
You now have all of the information required to send Cypher queries to Neo4j and consume the results.
Next, we will look at the Cypher Type System and some of the considerations that you need to make when working with values coming from Neo4j in your Node.js application.