Type Checking

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:

ts
People who acted in Movies
  // 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.

ts
Import Node and Relationship Types
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.

ts
Person Node Definition with Properties
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:

ts
Movie Node Definition
type Movie = Node<Integer, {
  tmdbId: string;
  title: string;
  rating: number;
}>

Relationships almost almost identical, but use the Relationship type instead.

ts
ACTED_IN Relationship Definition
type ActedIn = Relationship<Integer, {
  roles: string[];
}>

These types can be combined within an interface to represent the each record in the result:

ts
Query Result
interface PersonActedInMovie {
  p: Person;
  r: ActedIn;
  m: Movie;
}
ts
Using the PersonActedInMovie interface
// 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.