@brakebein/nest-neo4j
TypeScript icon, indicating that this package has built-in type declarations

1.3.0 • Public • Published

Nest Logo Neo4j Logo

Nest Neo4j

This repository provides Neo4j integration for NestJS wrapping the official neo4j-driver.

It is a fork of adam-cowley/nest-neo4j with some changes:

  • the queried records are extracted to a simplified format
  • non-standard properties (e.g. dates) are converted
  • updated dependencies

Installation

$ npm install @brakebein/nest-neo4j

Setup

Register the Neo4j Module in your application using the forRoot method, passing the Neo4j connection information as an object. Specific Neo4j settings (refer to neo4j-driver) can be passed via options.

import { Module } from '@nestjs/common';
import { Neo4jModule } from '@brakebein/nest-neo4j';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    Neo4jModule.forRoot({
      scheme: 'neo4j',
      host: 'localhost',
      port: 7687,
      username: 'neo4j',
      password: 'neo',
      options: {
        disableLosslessIntegers: true,
      },
      verifyConnectionTimout: 60000,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Alternatively, you can use forRootAsync to access information dynamically via ConfigService:

import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Neo4jModule } from '@brakebein/nest-neo4j';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    Neo4jModule.forRootAsync({
      useFactory: async (configService: ConfigService) => ({
        scheme: configService.get('NEO4J_SCHEME'),
        host: configService.get('NEO4J_HOST'),
        port: configService.get('NEO4J_PORT'),
        username: configService.get('NEO4J_USERNAME'),
        password: configService.get('NEO4J_PASSWPRD'),
        database: configService.get('NEO4J_DATABASE'),
      }),
      inject: [ConfigService],
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

If the Neo4j instance is unavailable, connection is not possible. Driver instantiation will be re-invoked a few times before throwing an error (useful in cases like server startup when node application is live earlier than the Neo4j database instance). Set verifyConnectionTimeout to increase the timeout before throwing an error.

Querying Neo4j

The Neo4jService is @Injectable, so can be passed into any constructor:

import { Neo4jService } from '@brakebein/nest-neo4j';

@Controller()
export class AppController {
  constructor(
    private readonly appService: AppService,
    private readonly neo4j: Neo4jService,
  ) {}

  @Get()
  async getHello(): Promise<any> {
    const results = await this.neo4j.read(`MATCH (n) RETURN count(n) AS count`);

    return `There are ${results[0].count} nodes in the database`;
  }
}

Methods

The query results returned by neo4j-driver are mapped to objects that correlate with identifiers within the RETURN clause and the node's properties. To access the raw query results, use the respective methods, e.g. .readRaw().

.read(cypher: string, params?: Record<string, any>, database?: string)

A very simple read transaction that expects a cypher statement and (optionally) query parameters.

Returns an array of objects, where the object's property names correlate with identifiers within the RETURN clause.

const query = `
  MATCH (p:Person {name: $name})-[:HAS_ADDRESS]->(add:Address)
  RETURN p.name AS name, add AS address
`;

const params = {
  name: 'Alex'
};

const results = await this.neo4j.read(query, params);
console.log(results);

// console.log(results)
// [
//   {
//     name: 'Alex',
//     address: {
//       ZIP: '10178',
//       number: '1',
//       town: 'Berlin',
//       street: 'Alexanderplatz'
//     }
//   }
// ]

Use .readRaw() to get the raw query results.

.write(cypher: string, params?: Record<string, any>, database?: string)

Very similar to .read() (see for details) except that it expects a cypher statement that modifies the database.

Use .writeRaw() to get the raw query results.

.multipleStatements(statements: { statement: string; parameters: Record<string, any> }[])

Execute multiple cypher queries within one transaction. A fail of one statement will lead to the rollback of the whole transaction.

Returns an array of arrays of objects (similar to .read() or .write()).

const statements = [{
  statement: `CREATE ...`,
  parameters: {}
}, {
  statement: `MATCH ... CREATE (n:Node $map) ...`,
  parameters: { map: { value: 'foo' } }
}];

const results = await this.neo4j.multipleStatements(statements);
// handle results

Use .multipleStatementsRaw() to get an array of the raw query results.

.getDriver()

Get the driver instance to access the full API of neo4j-driver.

.getConfig()

Get configuration as provided with Neoj4Module.

.getReadSession(database?: string)

Acquire a READ session to execute, e.g., explicit transactions.

.getWriteSession(database?: string)

Acquire a WRITE session to execute, e.g., explicit transactions.

.extractRecords<T = any>(data: Record[]): T[]()

Used internally to extract and convert the returned records by neo4j-driver to a more simplified format. It converts non-standard values, like date, time, etc., to strings as well as Neo4j integers, if they are outside the safe range.

Takes an array of Neo4j records Record[] and returns an array of objects.

import { Neo4jService } from './neo4j.service';

const query = `
  MATCH (p:Person {name: "Alex"})-[:HAS_ADDRESS]->(add:Address)
  RETURN p.name AS name, add AS address
`;

// query results returned by neo4j-driver
// {
//   records: [
//     Record {
//       keys: [ 'name', 'address' ],
//       length: 2,
//       _fields: [
//         'Alex',
//         Node {
//           identity: 1,
//           labels: [ 'Address' ],
//           properties: {
//             ZIP: '10178',
//             number: '1',
//             town: 'Berlin',
//             street: 'Alexanderplatz'
//           }
//         }
//       ]
//       _fieldLookup: { name: 0, address: 1 }
//     }
//   ],
//   summary: ResultSummary {...}
// }

extractRecords(queryResults.records);

// simplified records returned by neo4j-request
// {
//   name: 'Alex',
//   address: {
//     ZIP: '10178',
//     number: '1',
//     town: 'Berlin',
//     street: 'Alexanderplatz'
//   }
// }

.removeEmptyArrays<T>(data: T[], arrayKey: string, checkKey: string): T[]

Look for empty arrays returned by Neo4j and clean them, if there is null inside.

Sometimes, if the cypher query contains OPTIONAL MATCH node in combination with collect({key: node.value}) AS values, the resulting array may be filled with one object with null values: [{key: null}]. This method reduces the array to [] by calling removeEmptyArrays(data, 'values', 'key').

const query = `
  MATCH (p:Person {name: "Alex"})-[:HAS_ADDRESS]->(add:Address)
  OPTIONAL MATCH (p)-[:HAS_FRIEND]->(f:Person)-[:HAS_ADDRESS]->(fAddr:Address)
  RETURN p.name AS name,
         add AS address,
         collect({name: f.name, address: fAddr}) AS friends
`;

const results = await this.neo4j.read(query);
console.log(results);

// [
//   {
//     name: 'Alex',
//     address: {
//       ZIP: '10178',
//       number: '1',
//       town: 'Berlin',
//       street: 'Alexanderplatz'
//     },
//     friends: [ { address: null, name: null } ]
//   }
// ]

const resultsCleaned = this.neo4j.removeEmptyArrays(results, 'friends', 'name');
console.log(resultsCleaned);

// [
//   {
//     name: 'Alex',
//     address: {
//       ZIP: '10178',
//       number: '1',
//       town: 'Berlin',
//       street: 'Alexanderplatz'
//     },
//     friends: []
//   }
// ]

Readme

Keywords

Package Sidebar

Install

npm i @brakebein/nest-neo4j

Weekly Downloads

13

Version

1.3.0

License

MIT

Unpacked Size

128 kB

Total Files

20

Last publish

Collaborators

  • brakebein