Using TypeScript with Neo4j
Tags: typescript, webdev, neo4j, node
Published: 2022-11-01 09:25:58

**TL;DR: You can learn more in the [Building Neo4j Applications with TypeScript course
](https://graphacademy.neo4j.com/courses/app-typescript/?ref=dev.to) on [Neo4j GraphAcademy](https://graphacademy.neo4j.com/?ref=dev.to)**

The recent 5.2.0 release of the [Neo4j JavaScript Driver](https://github.com/neo4j/neo4j-javascript-driver/) features some significant improvements for TypeScript users.   So much so that it has inspired me to write an article.

It is now 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 Worked Example

For example, let's take a query from the Recommendations Dataset.  Say we would like to find a list of all actors that have appeared in a movie.  

To find this, we would need to create a new driver instance, open up a new session and then use the `executeRead()` function to send a Cypher statement and await the result.

```ts
async function main() {
  // Create a Driver Instance
  const driver = neo4j.driver(
    'neo4j://localhost:7687',
    neo4j.auth.basic('neo4j', 'letmein!')
  )

  // Open a new Session
  const session = driver.session()

  try {
    // 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(row => row.get('p'))

    console.log(people)
  }
  finally {
    // Close the Session
    await session.close()
  }
}
```

Straightforward enough, and as are all perfect developers we will never experience any problems writing code this way.  

On the other hand, if  _someone_  happens to make a typo in the line `res.records.map(row => row.get('p'))` or tries to `.get()` a value that isn't returned in the result, the Driver is written to throw an Error.

Say that row is changed to:

```ts
const people = res.records.map(row => row.get('something'))
```

As `something` doesn't exist in the result, a `Neo4jError` will be thrown:

```
Neo4jError: This record has no field with key 'something', 
available key are: [p,r,m].
```

You will eventually find this out when you run the application, but the whole point of TypeScript is to identify these errors during the development process.

### Adding Type-Checking

To protect against this type of scenario, we can now use an interface to define the keys available on each record.

In the case of the query above, we have three values:
1. `p` - a `Node` with a label of `Person` with properties including `name` and `born`
2. `r` - a `Relationship` of type `ACTED_IN` with properties including `roles` - an array of strings
3. `m` a `Node` with a label of `Movie`.

The `neo4j-driver` library exports two type definitions, `Node` and `Relationship`, that we can use to define these items.

```ts
import neo4j, { 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.

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`.

Person values can be defined as a TypeScript `type`.

```ts
import { Integer } from 'neo4j-driver'

interface PersonProperties {
  tmdbId: string;
  name: string;
  born: number; // Year of birth
}

type Person = Node<Integer, PersonProperties>
```

Or, for a more terse example, you can define the properties directly in the second generic:

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

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

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

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

```ts
interface PersonActedInMovie {
  p: Person;
  r: ActedIn;
  m: Movie;
}
```

Both the `session.run()` and `tx.run()` accept the interface and add type checking to any subsequent processing.  The above example can be updated to pass the `PersonActedInMovie` interface to the `tx.run()` method call.

```ts
// 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' }))
```

### Type Checking in Action

As the record shape has been defined, TypeScript will now validate the code as it is written and provide suggestions.


#### Suggesting Record Keys

Suggestions are now provided when calling the `record.get()` method.

![VS Code suggests p, r and m as possible values](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ufob3qnv2m2givu4ifan.png)

#### Suggesting Properties

TypeScript is aware that `people` is an array of `Person` nodes, and properties defined in the interface can be suggested while typing.

![VS Code suggests potential properties for the Person node](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/36xu2k5f2ub7p2dupd42.png)

#### Checking Property Keys

If a key does not exist in the properties of a node or relationship, TypeScript will pick this up straight away and throw an error:

```ts
const names = people.map(
  person => person.properties.foo 
  // Property 'foo' does not exist
  // on type 'PersonProperties' 

)
```

#### Type-checking Properties

TypeScript is now also be aware of the type of each of the properties, so TypeScript will throw an error if you try to use a value that is not defined in the Type.

```ts
const names: string[] = people.map(
  person => person.properties.born
)
// Type 'number[]' is not assignable to type 'string[]'.
```

## Interested in learning more?

If you are interesting in learning more about Neo4j, I recommend checkout out the [Beginners Neo4j Courses on GraphAcademy](https://graphacademy.neo4j.com?ref=dev.to).

If you are interested in learning more, I am currently working on a new [Neo4j & TypeScript course](https://graphacademy.neo4j.com/courses/app-typescript/?ref=dev.to) for [Neo4j GraphAcademy](https://graphacademy.neo4j.com/?ref=dev.to).  

You can also learn everything you need to know about [using Neo4j in a Node.js project]((https://graphacademy.neo4j.com/courses/app-nodejs/?ref=dev.to)) in the [Building Neo4j Applications with Node.js
](https://graphacademy.neo4j.com/courses/app-nodejs/?ref=dev.to) course, in which you will take a deeper dive into the Driver lifecycle and replace hardcoded data with responses from a [Neo4j Sandbox instance](https://sandbox.neo4j.com).

The [Building Neo4j Applications with TypeScript course
](https://graphacademy.neo4j.com/courses/app-typescript/?ref=dev.to) is a shorter, two hour course that covers the fundamentals of the Neo4j JavaScript Driver along with additional TypeScript features.

If you have any comments or questions, feel free to [reach out to me on Twitter](https://twitter.com/adamcowley).