Introducing Deno Runtime to the Neo4j Driver for Javascript
With the new Neo4j Driver for Javascript version 5.14, you can now query Neo4j using Deno natively.
Deno is a Typescript and Javascript runtime with secure defaults and native support for testing, linting, formatting, and package management in the box. Like NodeJS, Deno runs on the V8 Javascript engine.
The Deno version of the driver started with a contribution from Braden MacDonald, who created the tool for automatically generating Deno source code from the existing driver code; a big thanks to him for his contribution.
The generated code was adapted and submitted to the same acceptance test suite as the Node version of the driver. In this process, a new network layer was added to use Deno native sockets instead of WebSockets for better performance and stability.
Then, the release of 5.14.0 of the neo4j-driver introduced support for Deno as a preview feature. This version of the driver deployed to deno.land is based on the lite version of the driver, so there is no support for RxJS sessions.
To query Neo4j from Deno, you must first import the driver from the repository in your Typescript/Javascript file, and Deno will take care of downloading and installing the dependencies.
import neo4j from "https://deno.land/x/neo4j_driver_lite@5.14.0/mod.ts"
With the library imported, you can instantiate the driver object and run queries using the driver. The most straightforward way of running a query in the driver is by using the Driver.executeQuery
API. This API provides a cluster-safe way to execute a single query transaction.
import neo4j from "https://deno.land/x/neo4j_driver_lite@5.14.0/mod.ts";
const driver = neo4j.driver(
"neo4j://xxx.databases.neo4j.io:7687",
neo4j.auth.basic("neo4j", "<password>"),
);
const { records } = await driver.executeQuery(
"MATCH (p: Person { name: $name }) RETURN p.name AS name, p.age AS age",
{ name: "Antonio" }, // query params
{ // query config
database: "neo4j", // select database which query will run.
routing: neo4j.routing.READERS, // type of cluster members the query
},
);
for (const record of records) { // iterate over the received records
console.log(
`Found ${record.get("name")}, who is ${record.get("age")} years old.`,
);
}
await driver.close(); // Finally closing the driver at the end of the script
The driver needs the usage of the flags --allow-net
and -- allow-sys
for running in Deno. For example,
$ deno run --allow-net --allow-sys myscript.ts
These flags are needed because Deno focuses on security, so scripts should always signalize the permission required for running. In the case of the Neo4j driver, there is a need to access the network and system information to communicate with the database.
Running Your Own Transactions
The driver also provides APIs to manage your transactions for different needs, such as running multiple queries in a single transaction or controlling the result cursor.
Session.executeRead
and Session.executeWrite
are the methods for running read and write transactional units. Translating the first example, we get the following:
import neo4j from "https://deno.land/x/neo4j_driver_lite@5.14.0/mod.ts";
const driver = neo4j.driver(
"neo4j://xxx.databases.neo4j.io:7687",
neo4j.auth.basic("neo4j", "<password>"),
);
const session = driver.session({ database: "neo4j" }); // create a session for database 'neo4j'
try {
const { records } = await session.executeRead((tx: neo4j.Transaction) =>
// executing a read transaction
tx.run( // running a query and returning the result
"MATCH (p: Person { name: $name }) RETURN p.name AS name, p.age AS age",
{ name: "Antonio" }, // query params
)
);
for (const record of records) { // iterate over the received records
console.log(
`Found ${record.get("name")} with ${record.get("age")} years old.`,
);
}
} finally {
await session.close(); // finally closing the session
}
await driver.close(); // Finally closing the driver at the end of the script
Use this API when running multiple queries. When running a single query and returning all records, prefer the first example, as the driver can apply more optimizations.
Let’s imagine a scenario in which we want to send data of all people called “Antonio” to an external service to calculate their credit score and write it back to the database.
This external service is an integration that takes the tax identification number as input, and in some cases, it can take almost one second to run. The database has thousands of Antonios, so we might not want to download all the records eagerly before processing them.
So, the code for this scenario might look something like this:
import neo4j from "https://deno.land/x/neo4j_driver_lite@5.14.0/mod.ts";
const driver = neo4j.driver(
"neo4j://xxx.databases.neo4j.io:7687",
neo4j.auth.basic("neo4j", "<password>"),
);
const session = driver.session({ database: "neo4j" }); // create a session for database 'neo4j'
try {
const totalPropertiesSet = await session.executeWrite(
async (tx: neo4j.Transaction) => { // executing a write transaction
const result = tx.run( // running a query
"MATCH (p: Person { name: $name }) RETURN p.tin AS tin",
{ name: "Antonio" }, // query params
);
let propertiesSet = 0;
// async iterate over records on the result,
// backpressure will be applied if needed.
// data will be processed as it arrives.
for await (const record of result) {
const tin = record.get("tin");
const score = await getCreditScore(tin);
// running second query
// summary contains query result summary such as
// properties set
const { summary } = await tx.run(
"MATCH (p: Person { tin: $tin }) SET p.score = $score",
{ tin, score }, // query params
);
propertiesSet += summary.counters.updates().propertiesSet;
}
return propertiesSet;
},
);
console.log(`Total of properties updated: ${totalPropertiesSet}`);
} finally {
await session.close(); // finally closing the session
}
await driver.close(); // Finally closing the driver at the end of the script
Explicit Resource Management and Driver version 5.15.0
Support for the TC39 proposal for explicit resource management was added to the driver in 5.15.0; this reduces boilerplate related to the Driver and Session objects usage since the using keyword can replace the close method calls.
This feature is available in core-js and Typescript.
Since Deno supports Typescript natively, this feature is available without needing any extra library. So, we can rewrite our previous example as:
import neo4j from "https://deno.land/x/neo4j_driver_lite@5.15.0/mod.ts";
await using driver = neo4j.driver(
"neo4j://databases.neo4j.io:7687",
neo4j.auth.basic("neo4j", "<password>"),
);
await using session = driver.session({ database: "neo4j" });// create a session for database 'neo4j'
const totalPropertiesSet = await session.executeWrite(
async (tx: neo4j.Transaction) => { // executing a write transaction
const result = tx.run( // running a query
"MATCH (p: Person { name: $name }) RETURN p.tin AS tin",
{ name: "Antonio" }, // query params
);
let propertiesSet = 0;
// async iterate over records on the result,
// backpressure will be applied if needed.
// data will be processed as it arrives.
for await (const record of result) {
const tin = record.get("tin");
const score = await getCreditScore(tin);
// running second query
// summary contains query result summary such as
// properties set
const { summary } = await tx.run(
"MATCH (p: Person { tin: $tin }) SET p.score = $score",
{ tin, score }, // query params
);
propertiesSet += summary.counters.updates().propertiesSet;
}
return propertiesSet;
},
);
console.log(`Total of properties updated: ${totalPropertiesSet}`);
Please try out the new Deno driver and let us know if your have improvement requests or run into issues on GitHub