Wondering what’s next for npm?Check out our public roadmap! »

    neode
    TypeScript icon, indicating that this package has built-in type declarations

    0.4.7 • Public • Published

    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 = new 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(rel.from().get('name'), ' has known ', rel.to().get('name'), 'since', rel.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!'))

    Install

    npm i neode

    DownloadsWeekly Downloads

    668

    Version

    0.4.7

    License

    MIT

    Unpacked Size

    257 kB

    Total Files

    56

    Last publish

    Collaborators

    • avatar