So far, we have only hinted at the use of Generics with the Driver.
It is possible to use an interface to define the type of records returned by your Cypher query, giving you the added benefit of type-checking and type hinting while processing results.
A Working Example
For example, if you want to find a list of all actors that have appeared in a movie, you may end up with the following code:
// Execute a Cypher statement in a Read Transaction
const res = await session.executeRead(tx =>
tx.run(`
MATCH (p:Person)-[r:ACTED_IN]->(m:Movie {title: $title})
RETURN p, r, m
`, { title: 'Pulp Fiction' })
)
const people = res.records.map(
// No type guarding or suggestions on record keys
record => record.get('p')
)
const names = people.map(
// No checks or suggestions on property keys
person => person.properties.foo
)
This code presents three potential problems with this statement; the p
key may not be included in the record, the .name
property may not appear on the Person node, or the type of the property may not be a string
.
You will eventually find this out when you run the application, but the whole purpose of TypeScript is to identify these errors during the development process.
Type Checking
You can instruct TypeScript to identify the problems mentioned above by defining an interface
to represent the shape of the records and pass it as a generic to the tx.run()
function.
In the Cypher statement above, three values are returned:
p - a Node with a label of Person with properties including name and born r - a Relationship of type ACTED_IN with properties including roles - an array of strings m a Node with a label of Movie.
The neo4j-driver package exports two type definitions, Node and Relationship, that we can use to define these items.
import { Integer, Node, Relationship } from 'neo4j-driver'
Both of these classes accept generics to define the type of the .identity and the properties held on the value.
Lossless Integers
Unless you have set the disableLosslessIntegers
option when creating the Driver, the identity will be an instance of the Integer type exported from neo4j-driver.
You can read more about Neo4j JavaScript Driver and integers here.
Person values can be defined as a TypeScript type
.
import { Integer, Node } from 'neo4j-driver'
interface PersonProperties {
tmdbId: string;
name: string;
born: number;
}
type Person = Node<Integer, PersonProperties>
Or, for a more terse example, you can define the properties directly in the second generic:
type Movie = Node<Integer, {
tmdbId: string;
title: string;
rating: number;
}>
Relationships almost almost identical, but use the Relationship type instead.
type ActedIn = Relationship<Integer, {
roles: string[];
}>
These types can be combined within an interface to represent the each record in the result:
interface PersonActedInMovie {
p: Person;
r: ActedIn;
m: Movie;
}
// Execute a Cypher statement in a Read Transaction
const res = await session.executeRead(tx =>
tx.run<PersonActedInMovie>(`
MATCH (p:Person)-[r:ACTED_IN]->(m:Movie {title: $title})
RETURN p, r, m
`,
{ title: 'Pulp Fiction' })
)
This simple change will now allow the TypeScript interpreter to:
-
Suggest Record Keys - when calling
record.get()
,'m'
,'r'
and'p'
will be suggested as potential options -
Suggest Properties - when accessing a Node or Relationship type, the appropriate properties will be suggested
-
Check Property Keys - if you try to access a property that doesn’t exist on the type, the interpreter will pick up the error immediately
-
Type-check Properties - the interpreter will be aware of the type of each property, so if a property type is incorrectly cast an error will be thrown
Check Your Understanding
TypeScript Features
Which TypeScript features are enabled if you add a generic to the tx.run()
method call?
-
✓ Code suggestions in your IDE
-
✓ Type Checking
-
✓ Type Guarding
Hint
All three options above are benefits of defining types in your code.
Solution
All three options above are benefits of defining types in your code.
Lesson Summary
In this lesson you learned how to use TypeScript types to allow the TypeScript interpreter to identify errors during development.
You should now have everything you need to use the Neo4j JavaScript Driver in your next TypeScript project.