• Stars
    star
    391
  • Rank 109,364 (Top 3 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created about 7 years ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Neo4j OGM for Node.js

Neode

Neode is a Neo4j OGM for Node JS designed to take care of the CRUD boilerplate involved with setting up a neo4j project with Node. Just install, set up your models and go.

Getting Started

Installation

npm install --save neode

Usage

// index.js
import Neode from 'neode';

const instance = new Neode('bolt://localhost:7687', 'username', 'password');

Enterprise Mode

To initiate Neode in enterprise mode and enable enterprise features, provide a true variable as the fourth parameter.

// index.js
import Neode from 'neode';

const instance = new Neode('bolt://localhost:7687', 'username', 'password', true);

Usage with .env variables

npm i --save dotenv
// .env
NEO4J_PROTOCOL=neo4j
NEO4J_HOST=localhost
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=neo4j
NEO4J_PORT=7687
NEO4J_DATABASE=neo4j
NEO4J_ENCRYPTION=ENCRYPTION_OFF
// index.js
import Neode from 'neode';

const instance = Neode.fromEnv();

Additional Driver Config

Additional driver configuration can be passed as the fifth parameter in the constructor, or defined in .env:

NEO4J_ENCRYPTED=ENCRYPTION_ON                   # ENCRYPTION_ON or ENCRYPTION_OFF
NEO4J_TRUST=TRUST_SIGNED_CERTIFICATES           # TRUST_ALL_CERTIFICATES, TRUST_ON_FIRST_USE, TRUST_SIGNED_CERTIFICATES, TRUST_CUSTOM_CA_SIGNED_CERTIFICATES, TRUST_SYSTEM_CA_SIGNED_CERTIFICATES
NEO4J_TRUSTED_CERTIFICATES=/path/to/cert.pem
NEO4J_KNOWN_HOSTS=127.0.0.1
NEO4J_MAX_CONNECTION_POOLSIZE=100
NEO4J_MAX_TRANSACTION_RETRY_TIME=5000
NEO4J_LOAD_BALANCING_STRATEGY=least_connected   # least_connected or round_robin
NEO4J_MAX_CONNECTION_LIFETIME=36000
NEO4J_CONNECTION_TIMEOUT=36000
NEO4J_DISABLE_LOSSLESS_INTEGERS=false

Loading with Models

You can use the with() method to load multiple models at once.

const neode = require('neode')
    .fromEnv()
    .with({
        Movie: require('./models/Movie'),
        Person: require('./models/Person')
    });

Load from Directory

You can load a directory of models by calling the withDirectory() method.

// models/Person.js
module.exports = {
  id: {
    type: 'uuid',
    primary: true
  },
  name: 'string'
}
// index.js
instance.withDirectory(__dirname+'/models');

Defining a Node Definition

Neode revolves around the notion of node definitions, or Models. To interact with the graph, you will need to define a node, identified by a name and with a schema of properties.

instance.model(name, schema);

Schema Object

instance.model('Person', {
    person_id: {
        primary: true,
        type: 'uuid',
        required: true, // Creates an Exists Constraint in Enterprise mode
    },
    payroll: {
        type: 'number',
        unique: 'true', // Creates a Unique Constraint
    },
    name: {
        type: 'name',
        index: true, // Creates an Index
    },
    age: 'number' // Simple schema definition of property : type
});
Property Types

The following property types are supported:

  • string
  • number
  • int
  • integer
  • float
  • uuid
  • node
  • nodes
  • relationship
  • relationships
  • Temporal
    • date
    • time
    • datetime
    • localtime
    • localdatetime
    • duration
  • Spatial
    • point
    • distance
Validation

Validation is provided by the Joi library. Certain data types (float, integer, boolean) will also be type cast during the data cleansing process. For more information on the full range of validation options, read the Joi API documentation.

All Types
option type description example
allow Array Whitelist of values that are allowed allow: ['A', 'B', 'C']
valid Array A strict whitelist of valid options. All others will be rejected. valid: ['A', 'B', 'C']
invalid Array A list of forbidden values invalid: ['A', 'B', 'C']
required Boolean Should this field be required? required: true
optional Boolean Allow the value to be undefined optional: true
forbidden Boolean Marks a key as forbidden which will not allow any value except undefined. Used to explicitly forbid keys. forbidden: true
strict Boolean prevent type casting for the current key strict: true
strip Boolean Marks a key to be removed from a resulting object or array after validation. strip: true
default Mixed/Function Default value for the property default: () => new Date()
empty Boolean Considers anything that matches the schema to be empty empty: true
error Error/String/Function Overrides the default error error: errors => new CustomValidationError('Oh No!', errors)
Boolean
option type description example
truthy String
falsy String
insensitive Boolean
Date, Time, DateTime, LocalDateTime, LocalTime
option type description example
before String Date, date string or "now" to compare to the current date
after String Date, date string or "now" to compare to the current date
Numbers (number, int, integer, float)
option type description example
min Number
max Number
integer Boolean Requires the number to be an integer
precision Number Specifies the maximum number of decimal places precision: 2
multiple Number Multiple of a number multiple: 2
positive Boolean
negative Boolean
port Boolean Requires the number to be a TCP port, so between 0 and 65535.
Strings
option type description example
insensitive Boolean
min Number Min length
max Number Max length
truncate Boolean Will truncate value to the max length
creditCard Boolean Requires the number to be a credit card number (Using Luhn Algorithm).
length Number Exact string length
regex Object Regular expression rule { pattern: /([A-Z]+)/, invert: true, name: 'myRule'}
replace Object Replace in value { pattern: /(^[A-Z]+)/, replace: '-' }
alphanum Boolean Requires the string value to only contain a-z, A-Z, and 0-9.
token Boolean Requires the string value to only contain a-z, A-Z, 0-9, and underscore _.
email Boolean/Object
ip Boolean/Object
uri Boolean/Object
guid Boolean
hex Boolean/Object
base64 Boolean/Object
hostname Boolean
normalize Boolean/String
lowercase Boolean
uppercase Boolean
trim Boolean
isoDate Boolean

Defining Relationships

Relationships can be created in the schema or defined retrospectively.

instance.model(label).relationship(type, relationship, direction, target, schema, eager, cascade, node_alias);
instance.model('Person').relationship('knows', 'relationship', 'KNOWS', 'out', 'Person', {
    since: {
        type: 'number',
        required: true,
    },
    defaulted: {
        type: 'string',
        default: 'default'
    }
});

Eager Loading

You can eager load relationships in a findAll() call by setting the eager property inside the relationship schema to true.

{
    acts_in: {
        type: "relationship",
        target: "Movie",
        relationship: "ACTS_IN",
        direction: "out",
        properties: {
            name: "string"
        },
        eager: true // <-- eager load this relationship
    }
}

Eager loaded relationships can be retrieved by using the get() method. A Collection instance will be returned.

const person = person.find({name: "Tom Hanks"})
const movies = person.get('acts_in');
const first = movies.first();

Extending a Schema definition

You can inherit the schema of a class and extend by calling the extend method.

instance.extend(original, new, schema)
instance.extend('Person', 'Actor', {
    acts_in: {
        type: "relationship",
        target: "Movie",
        relationship: "ACTS_IN",
        direction: "out",
        properties: {
            name: "string"
        }
    }
})

Reading

Running a Cypher Query

instance.cypher(query, params)
instance.cypher('MATCH (p:Person {name: $name}) RETURN p', {name: "Adam"})
    .then(res => {
        console.log(res.records.length);
    })

Running a Batch

Batch queries run within their own transaction. Transactions can be sent as either a string or an object containing query and param properties.

instance.batch(queries)
instance.batch([
    {query: 'CREATE (p:Person {name: $name}) RETURN p', params: {name: "Adam"}},
    {query: 'CREATE (p:Person {name: $name}) RETURN p', params: {name: "Joe"}},
    {query: 'MATCH (first:Person {name: $first_name}), (second:Person {name: $second_name}) CREATE (first)-[:KNOWS]->(second)', params: {name: "Joe"}}
])
    .then(res => {
        console.log(res.records.length);
    })

Get all Nodes

instance.all(label, properties)
instance.model(label).all(properties)
instance.all('Person', {name: 'Adam'}, {name: 'ASC', id: 'DESC'}, 1, 0)
    .then(collection => {
        console.log(collection.length); // 1
        console.log(collection.get(0).get('name')); // 'Adam'
    })

Get Node by Internal Node ID

instance.findById(label, id)
instance.model(label).findById(id)
instance.findById('Person', 1)
    .then(person => {
        console.log(person.id()); // 1
    });

Get Node by Primary Key

Neode will work out the model's primary key and query based on the supplied value.

instance.find(label, id)
instance.model(label).find(id)
instance.find('Person', '1234')
    .then(res => {...});

First by Properties

Using a key and value

instance.first(label, key, value)
instance.first(label).first(key, value)
instance.first('Person', 'name', 'Adam')
    .then(adam => {...})

Using multiple properties

instance.first(label, properties)
instance.first(label).first(properties)
instance.first('Person', {name: 'Adam', age: 29})
    .then(adam => {...})

Writing

Creating a Node

instance.create(label, properties);
instance.model(label).create(properties);
instance.create('Person', {
    name: 'Adam'
})
.then(adam => {
    console.log(adam.get('name')); // 'Adam'
});

Merging a Node

Nodes are merged based on the indexes and constraints.

instance.merge(label, properties);
instance.model(label).merge(properties);
instance.merge('Person', {
    person_id: 1234,
    name: 'Adam',
});

Merge On Specific Properties

If you know the properties that you would like to merge on, you can use the mergeOn method.

instance.mergeOn(label, match, set);
instance.model(label).mergeOn(match, set);
instance.mergeOn('Person', {person_id: 1234}, {name: 'Adam'});

Updating a Node

You can update a Node instance directly by calling the update() method.

instance.create('Person', {name: 'Adam'})
    .then(adam => adam.update({age: 29}));

Creating a Relationships

You can relate two nodes together by calling the relateTo() method.

model.relateTo(other, type, properties)
Promise.all([
    instance.create('Person', {name: 'Adam'}),
    instance.create('Person', {name: 'Joe'})
])
.then(([adam, joe]) => {
    adam.relateTo(joe, 'knows', {since: 2010})
        .then(res => {
            console.log(res.startNode().get('name'), ' has known ', res.endNode().get('name'), 'since', res.get('since'));  // Adam has known Joe since 2010
        });
});

Note: when creating a relationship defined as in (DIRECTION_IN), from from() and to() properties will be inversed regardless of which model the relationship is created by.

Detaching two nodes

You can detach two nodes by calling the detachFrom() method.

model.detachFrom(other)
Promise.all([
    instance.create('Person', {name: 'Adam'}),
    instance.create('Person', {name: 'Joe'})
])
.then(([adam, joe]) => {
    adam.detachFrom(joe) // Adam does not know Joe
});

### Deleting a node
You can delete a Node instance directly by calling the `delete()` method.

```javascript
instance.create('Person', {name: 'Adam'})
  .then(adam => adam.delete());

Cascade Deletion

While deleting a Node with the delete() method, you can delete any dependant nodes or relationships. For example, when deleting a Movie you may also want to remove any reviews but keep the actors.

You cna do this by setting the cascade property of a relationship to "delete" or "detach". "delete" will remove the node and relationship by performing a DETACH DELETE, while "detach" will simply remove the relationship, leaving the node in the graph.

// Movie.js
module.exports = {
  // ...
  ratings: {
    type: 'relationship',
    'relationship': 'RATED',
    direction: 'IN',
    target: 'User',
    'cascade': 'delete'
  },
  actors: {
    type: 'relationship',
    'relationship': 'ACTS_IN',
    direction: 'IN',
    target: 'Actor',
    'cascade': 'detach'
  }
};

Note: Attempting to delete a Node without first removing any relationships will result in an error.

Deleting a set of nodes

TODO

instance.delete(label, where)
instance.delete('Person', {living: false});

Deleting all nodes of a given type

instance.deleteAll('Person');
  .then(() => console.log('Everyone has been deleted'));

Query Builder

Neode comes bundled with a query builder. You can create a Query Builder instance by calling the query() method on the Neode instance.

const builder = instance.query();

Once you have a Builder instance, you can start to defining the query using the fluent API.

builder.match('p', 'Person')
    .where('p.name', 'Adam')
    .return('p');

For query examples, check out the Query Builder Test suite.

Building Cypher

You can get the generated cypher query by calling the build() method. This method will return an object containing the cypher query string and an object of params.

const {query, params} = builder.build();

instance.query(query, params)
    .then(res => {
        console.log(res.records.length);
    });

Executing a Query

You can execute a query by calling the execute() method on the query builder.

builder.match('this', 'Node')
    .whereId('this', 1)
    .return('this')
    .execute()
    .then(res => {
        console.log(res.records.length);
    });

Schema

Neode will install the schema created by the constraints defined in your Node definitions.

Installing the Schema

instance.schema.install()
    .then(() => console.log('Schema installed!'))

Note: exists constraints will only be created when running in enterprise mode. Attempting to create an exists constraint on Community edition will cause a Neo.DatabaseError.Schema.ConstraintCreationFailed to be thrown.

Dropping the schema

Dropping the schema will remove all indexes and constraints created by Neode. All other indexes and constraints will be left intact.

instance.schema.drop()
    .then(() => console.log('Schema dropped!'))

More Repositories

1

twitch-project

A weekly stream in which I build a web application with Neo4j and Typescript
TypeScript
83
star
2

use-neo4j

React Hooks for Neo4j
TypeScript
73
star
3

nest-neo4j

A NestJS module for integrating with Neo4j
TypeScript
71
star
4

vue-neo4j

Neo4j connector for Vue.js
JavaScript
70
star
5

neode-example

An example project using Neode
JavaScript
29
star
6

neo4j-vscode

VS Code Extension Experiments
TypeScript
27
star
7

graphapp-starter-react

A Graph App Starter Kit for React - Clone, connect and code...
TypeScript
25
star
8

neode-querybuilder

Fluent Cypher Query Builder for NodeJS - As used in Neode
TypeScript
21
star
9

neo4j-wordpress

Neo4j Recommendations for Wordpress
PHP
13
star
10

neode-ogm

Neode OGM Experiments
TypeScript
12
star
11

real-time-ui-vuejs-neo4j-kafka

A Real-time UI on top of Neo4j with VueJS using websockets, driven by Kafka and the Neo4j Streams
Vue
10
star
12

nestjs-neo4j-starter

A starter kit for Nest.js and Neo4j
TypeScript
9
star
13

slack-knowledge-graph

JavaScript
6
star
14

quick-tdd-node

JavaScript
6
star
15

neo4j-desktop-vue

An example visualisation app for Neo4j Desktop built with Vue & Vis.js
JavaScript
6
star
16

neoflix

neoflix
CSS
4
star
17

neode-express

Neode for Express
JavaScript
4
star
18

skyshard

A Sharding example using flight data and Neo4j
TypeScript
4
star
19

neo4j-react-hooks-experiment

TypeScript
4
star
20

neo4j-bigquery

Yo dawg, I heard you like queries so we put some BigQuery in your query so you can query BigQuery from your query
Java
4
star
21

neo4j-nextjs-example

TypeScript
3
star
22

neo4jgraphqlreactnative

Neo4j's Community Graph in React Native via GraphQL
JavaScript
3
star
23

lwjbot

TypeScript
3
star
24

seed-neo4j-docker

Using neo4j-admin to seed Neo4j in a Docker container
Dockerfile
2
star
25

statsbomb-neo4j

Importing Statsbomb Open Data into Neo4j
TypeScript
2
star
26

neode-ogm-test

TypeScript
2
star
27

neobeans

An example project using the official Neo4j Drivers as a Spring Bean
Java
2
star
28

journeyplanning

Cycript
2
star
29

neo4j-graphql-genai

GenAI, LLMs and Retrieval Augmented Generation in your GraphQL APIs
JavaScript
2
star
30

adam-cowley

1
star
31

angularexperiment

TypeScript
1
star
32

social

Social Feed
Java
1
star
33

neo4j-spring-workshop

Simple connection to Neo4j from a Spring application
Java
1
star
34

apoc-graph-app

TypeScript
1
star
35

skyscanner

Skyscanner SDK for Node
JavaScript
1
star
36

lwj

JavaScript
1
star
37

vendorpower

A Multi-tenant SaaS Control Panel for Neo4j Desktop
Vue
1
star
38

old-blog

https://www.adamcowley.co.uk
HTML
1
star
39

twittergraph

Cypher Queries to build a Twitter Graph
1
star
40

neode-tirekicker

Tire Kicking with Neode
JavaScript
1
star
41

neo4j-auth0-examples

Examples for using Neo4j as a Custom Database on Auth0
JavaScript
1
star
42

cityjs-chatbot

TypeScript
1
star