{"id":13725152,"url":"https://github.com/adam-cowley/neode","last_synced_at":"2025-05-16T06:08:12.108Z","repository":{"id":22363741,"uuid":"95789247","full_name":"adam-cowley/neode","owner":"adam-cowley","description":"Neo4j OGM for Node.js","archived":false,"fork":false,"pushed_at":"2024-04-24T14:42:06.000Z","size":1223,"stargazers_count":399,"open_issues_count":56,"forks_count":71,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-05-05T08:06:55.890Z","etag":null,"topics":["cypher","cypher-query","hacktoberfest","neo4j","neo4j-ogm","nodejs"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/adam-cowley.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-06-29T15:01:01.000Z","updated_at":"2025-02-16T19:44:32.000Z","dependencies_parsed_at":"2024-06-18T13:52:54.886Z","dependency_job_id":"0147e94e-2d89-42c3-a1cd-a495941939aa","html_url":"https://github.com/adam-cowley/neode","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adam-cowley%2Fneode","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adam-cowley%2Fneode/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adam-cowley%2Fneode/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adam-cowley%2Fneode/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adam-cowley","download_url":"https://codeload.github.com/adam-cowley/neode/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254478187,"owners_count":22077676,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cypher","cypher-query","hacktoberfest","neo4j","neo4j-ogm","nodejs"],"created_at":"2024-08-03T01:02:14.392Z","updated_at":"2025-05-16T06:08:07.100Z","avatar_url":"https://github.com/adam-cowley.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# Neode\n\nNeode 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.\n\n- [Getting Started](#getting-started)\n- [Reading from the Graph](#reading)\n- [Writing to the Graph](#writing)\n- [Query Builder](#query-builder)\n- [Schema](#schema)\n\n\n## Getting Started\n\n### Installation\n```javascript\nnpm install --save neode\n```\n\n### Usage\n```javascript\n// index.js\nimport Neode from 'neode';\n\nconst instance = new Neode('bolt://localhost:7687', 'username', 'password');\n```\n\n#### Enterprise Mode\n\nTo initiate Neode in enterprise mode and enable enterprise features, provide a true variable as the fourth parameter.\n\n```javascript\n// index.js\nimport Neode from 'neode';\n\nconst instance = new Neode('bolt://localhost:7687', 'username', 'password', true);\n```\n\n#### Usage with .env variables\n```\nnpm i --save dotenv\n```\n\n```\n// .env\nNEO4J_PROTOCOL=neo4j\nNEO4J_HOST=localhost\nNEO4J_USERNAME=neo4j\nNEO4J_PASSWORD=neo4j\nNEO4J_PORT=7687\nNEO4J_DATABASE=neo4j\nNEO4J_ENCRYPTION=ENCRYPTION_OFF\n```\n\n```javascript\n// index.js\nimport Neode from 'neode';\n\nconst instance = Neode.fromEnv();\n```\n\n#### Additional Driver Config\n\nAdditional driver configuration can be passed as the fifth parameter in the constructor, or defined in .env:\n\n```\nNEO4J_ENCRYPTED=ENCRYPTION_ON                   # ENCRYPTION_ON or ENCRYPTION_OFF\nNEO4J_TRUST=TRUST_SIGNED_CERTIFICATES           # TRUST_ALL_CERTIFICATES, TRUST_ON_FIRST_USE, TRUST_SIGNED_CERTIFICATES, TRUST_CUSTOM_CA_SIGNED_CERTIFICATES, TRUST_SYSTEM_CA_SIGNED_CERTIFICATES\nNEO4J_TRUSTED_CERTIFICATES=/path/to/cert.pem\nNEO4J_KNOWN_HOSTS=127.0.0.1\nNEO4J_MAX_CONNECTION_POOLSIZE=100\nNEO4J_MAX_TRANSACTION_RETRY_TIME=5000\nNEO4J_LOAD_BALANCING_STRATEGY=least_connected   # least_connected or round_robin\nNEO4J_MAX_CONNECTION_LIFETIME=36000\nNEO4J_CONNECTION_TIMEOUT=36000\nNEO4J_DISABLE_LOSSLESS_INTEGERS=false\n```\n\n\n#### Loading `with` Models\n\nYou can use the `with()` method to load multiple models at once.\n\n```javascript\nconst neode = require('neode')\n    .fromEnv()\n    .with({\n        Movie: require('./models/Movie'),\n        Person: require('./models/Person')\n    });\n\n```\n\n#### Load from Directory\n\nYou can load a directory of models by calling the `withDirectory()` method.\n\n```javascript\n// models/Person.js\nmodule.exports = {\n  id: {\n    type: 'uuid',\n    primary: true\n  },\n  name: 'string'\n}\n```\n\n```javascript\n// index.js\ninstance.withDirectory(__dirname+'/models');\n```\n\n### Defining a Node Definition\n\nNeode revolves around the notion of node definitions, or `Model`s.  To interact with the graph, you will need to define a node, identified by a `name` and with a `schema` of properties.\n\n```javascript\ninstance.model(name, schema);\n```\n\n#### Schema Object\n```javascript\ninstance.model('Person', {\n    person_id: {\n        primary: true,\n        type: 'uuid',\n        required: true, // Creates an Exists Constraint in Enterprise mode\n    },\n    payroll: {\n        type: 'number',\n        unique: 'true', // Creates a Unique Constraint\n    },\n    name: {\n        type: 'name',\n        index: true, // Creates an Index\n    },\n    age: 'number' // Simple schema definition of property : type\n});\n```\n\n##### Property Types\n\nThe following property types are supported:\n\n- `string`\n- `number`\n- `int`\n- `integer`\n- `float`\n- `uuid`\n- `node`\n- `nodes`\n- `relationship`\n- `relationships`\n- Temporal\n  - `date`\n  - `time`\n  - `datetime`\n  - `localtime`\n  - `localdatetime`\n  - `duration`\n- Spatial\n  - `point`\n  - `distance`\n\n##### Validation\n\nValidation is provided by the [Joi](https://github.com/hapijs/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](https://github.com/hapijs/joi/blob/v13.4.0/API.md).\n\n##### All Types\n\n| option | type | description | example |\n| -- | -- | -- | -- |\n| allow | Array | Whitelist of values that are allowed | `allow: ['A', 'B', 'C']` |\n| valid | Array | A strict whitelist of valid options.  All others will be rejected.  | `valid: ['A', 'B', 'C']` |\n| invalid | Array | A list of forbidden values  | `invalid: ['A', 'B', 'C']` |\n| required | Boolean | Should this field be required?  | `required: true` |\n| optional | Boolean | Allow the value to be `undefined`  | `optional: true` |\n| forbidden | Boolean | Marks a key as forbidden which will not allow any value except undefined. Used to explicitly forbid keys.  | `forbidden: true` |\n| strict | Boolean | prevent type casting for the current key | `strict: true`\n| strip | Boolean | Marks a key to be removed from a resulting object or array after validation. | `strip: true`\n| default | Mixed/Function | Default value for the property | `default: () =\u003e new Date()`\n| empty | Boolean | Considers anything that matches the schema to be empty | `empty: true`\n| error | Error/String/Function | Overrides the default error | `error: errors =\u003e new CustomValidationError('Oh No!',  errors)`\n\n\n##### Boolean\n\n| option | type | description | example |\n| -- | -- | -- | -- |\n| truthy | String\n| falsy | String\n| insensitive | Boolean\n\n\n##### Date, Time, DateTime, LocalDateTime, LocalTime\n\n| option | type | description | example |\n| -- | -- | -- | -- |\n| before | String | `Date`, date string or `\"now\"` to compare to the current date\n| after | String | `Date`, date string or `\"now\"` to compare to the current date\n\n\n##### Numbers (number, int, integer, float)\n\n| option | type | description | example |\n| -- | -- | -- | -- |\n| min | Number\n| max | Number\n| integer | Boolean | Requires the number to be an integer\n| precision | Number | Specifies the maximum number of decimal places | `precision: 2`\n| multiple | Number | Multiple of a number | `multiple: 2`\n| positive | Boolean\n| negative | Boolean\n| port | Boolean | Requires the number to be a TCP port, so between 0 and 65535.\n\n\n##### Strings\n\n| option | type | description | example |\n| -- | -- | -- | -- |\n| insensitive | Boolean\n| min | Number | Min length\n| max | Number | Max length\n| truncate | Boolean | Will truncate value to the max length\n| creditCard | Boolean | Requires the number to be a credit card number (Using Luhn Algorithm).\n| length | Number | Exact string length\n| regex | Object | Regular expression rule | `{ pattern: /([A-Z]+)/, invert: true, name: 'myRule'}`\n| replace | Object | Replace in value | `{ pattern: /(^[A-Z]+)/, replace: '-' }`\n| alphanum | Boolean | Requires the string value to only contain a-z, A-Z, and 0-9.\n| token | Boolean | Requires the string value to only contain a-z, A-Z, 0-9, and underscore _.\n| email | Boolean/Object |\n| ip | Boolean/Object |\n| uri | Boolean/Object |\n| guid  | Boolean\n| hex | Boolean/Object\n| base64 | Boolean/Object\n| hostname | Boolean\n| normalize | Boolean/String\n| lowercase | Boolean\n| uppercase | Boolean\n| trim | Boolean\n| isoDate | Boolean\n\n\n#### Defining Relationships\n\nRelationships can be created in the schema or defined retrospectively.\n\n```javascript\ninstance.model(label).relationship(type, relationship, direction, target, schema, eager, cascade, node_alias);\n```\n\n```javascript\ninstance.model('Person').relationship('knows', 'relationship', 'KNOWS', 'out', 'Person', {\n    since: {\n        type: 'number',\n        required: true,\n    },\n    defaulted: {\n        type: 'string',\n        default: 'default'\n    }\n});\n```\n\n#### Eager Loading\n\nYou can eager load relationships in a `findAll()` call by setting the `eager` property inside the relationship schema to `true`.\n\n```javascript\n{\n    acts_in: {\n        type: \"relationship\",\n        target: \"Movie\",\n        relationship: \"ACTS_IN\",\n        direction: \"out\",\n        properties: {\n            name: \"string\"\n        },\n        eager: true // \u003c-- eager load this relationship\n    }\n}\n```\n\nEager loaded relationships can be retrieved by using the `get()` method.  A `Collection` instance will be returned.\n\n```\nconst person = person.find({name: \"Tom Hanks\"})\nconst movies = person.get('acts_in');\nconst first = movies.first();\n```\n\n\n### Extending a Schema definition\nYou can inherit the schema of a class and extend by calling the extend method.\n\n```\ninstance.extend(original, new, schema)\n```\n\n```javascript\ninstance.extend('Person', 'Actor', {\n    acts_in: {\n        type: \"relationship\",\n        target: \"Movie\",\n        relationship: \"ACTS_IN\",\n        direction: \"out\",\n        properties: {\n            name: \"string\"\n        }\n    }\n})\n```\n\n## Reading\n\n### Running a Cypher Query\n\n```\ninstance.cypher(query, params)\n```\n\n```javascript\ninstance.cypher('MATCH (p:Person {name: $name}) RETURN p', {name: \"Adam\"})\n    .then(res =\u003e {\n        console.log(res.records.length);\n    })\n```\n\n### Running a Batch\nBatch queries run within their own transaction.  Transactions can be sent as either a string or an object containing `query` and `param` properties.\n\n```\ninstance.batch(queries)\n```\n\n```javascript\ninstance.batch([\n    {query: 'CREATE (p:Person {name: $name}) RETURN p', params: {name: \"Adam\"}},\n    {query: 'CREATE (p:Person {name: $name}) RETURN p', params: {name: \"Joe\"}},\n    {query: 'MATCH (first:Person {name: $first_name}), (second:Person {name: $second_name}) CREATE (first)-[:KNOWS]-\u003e(second)', params: {name: \"Joe\"}}\n])\n    .then(res =\u003e {\n        console.log(res.records.length);\n    })\n```\n\n\n### Get `all` Nodes\n\n```\ninstance.all(label, properties)\ninstance.model(label).all(properties)\n```\n\n```javascript\ninstance.all('Person', {name: 'Adam'}, {name: 'ASC', id: 'DESC'}, 1, 0)\n    .then(collection =\u003e {\n        console.log(collection.length); // 1\n        console.log(collection.get(0).get('name')); // 'Adam'\n    })\n```\n\n### Get Node by Internal Node ID\n```\ninstance.findById(label, id)\ninstance.model(label).findById(id)\n```\n\n```javascript\ninstance.findById('Person', 1)\n    .then(person =\u003e {\n        console.log(person.id()); // 1\n    });\n```\n\n### Get Node by Primary Key\nNeode will work out the model's primary key and query based on the supplied value.\n```\ninstance.find(label, id)\ninstance.model(label).find(id)\n```\n\n```javascript\ninstance.find('Person', '1234')\n    .then(res =\u003e {...});\n```\n\n### First by Properties\n\n#### Using a key and value\n```\ninstance.first(label, key, value)\ninstance.first(label).first(key, value)\n```\n```javascript\ninstance.first('Person', 'name', 'Adam')\n    .then(adam =\u003e {...})\n```\n\n\n#### Using multiple properties\n```\ninstance.first(label, properties)\ninstance.first(label).first(properties)\n```\n```javascript\ninstance.first('Person', {name: 'Adam', age: 29})\n    .then(adam =\u003e {...})\n```\n\n\n\n## Writing\n\n### Creating a Node\n```javascript\ninstance.create(label, properties);\ninstance.model(label).create(properties);\n```\n\n```javascript\ninstance.create('Person', {\n    name: 'Adam'\n})\n.then(adam =\u003e {\n    console.log(adam.get('name')); // 'Adam'\n});\n```\n\n### Merging a Node\nNodes are merged based on the indexes and constraints.\n\n```javascript\ninstance.merge(label, properties);\ninstance.model(label).merge(properties);\n```\n\n```javascript\ninstance.merge('Person', {\n    person_id: 1234,\n    name: 'Adam',\n});\n```\n\n### Merge On Specific Properties\nIf you know the properties that you would like to merge on, you can use the `mergeOn` method.\n\n```javascript\ninstance.mergeOn(label, match, set);\ninstance.model(label).mergeOn(match, set);\n```\n```javascript\ninstance.mergeOn('Person', {person_id: 1234}, {name: 'Adam'});\n```\n\n### Updating a Node\nYou can update a Node instance directly by calling the `update()` method.\n\n```javascript\ninstance.create('Person', {name: 'Adam'})\n    .then(adam =\u003e adam.update({age: 29}));\n```\n\n### Creating a Relationships\nYou can relate two nodes together by calling the `relateTo()` method.\n\n```javascript\nmodel.relateTo(other, type, properties)\n```\n```javascript\nPromise.all([\n    instance.create('Person', {name: 'Adam'}),\n    instance.create('Person', {name: 'Joe'})\n])\n.then(([adam, joe]) =\u003e {\n    adam.relateTo(joe, 'knows', {since: 2010})\n        .then(res =\u003e {\n            console.log(res.startNode().get('name'), ' has known ', res.endNode().get('name'), 'since', res.get('since'));  // Adam has known Joe since 2010\n        });\n});\n```\n\n**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.\n\n### Detaching two nodes\nYou can detach two nodes by calling the `detachFrom()` method.\n\n```javascript\nmodel.detachFrom(other)\n```\n```javascript\nPromise.all([\n    instance.create('Person', {name: 'Adam'}),\n    instance.create('Person', {name: 'Joe'})\n])\n.then(([adam, joe]) =\u003e {\n    adam.detachFrom(joe) // Adam does not know Joe\n});\n\n### Deleting a node\nYou can delete a Node instance directly by calling the `delete()` method.\n\n```javascript\ninstance.create('Person', {name: 'Adam'})\n  .then(adam =\u003e adam.delete());\n```\n\n#### Cascade Deletion\nWhile 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.\n\nYou 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.\n\n```javascript\n// Movie.js\nmodule.exports = {\n  // ...\n  ratings: {\n    type: 'relationship',\n    'relationship': 'RATED',\n    direction: 'IN',\n    target: 'User',\n    'cascade': 'delete'\n  },\n  actors: {\n    type: 'relationship',\n    'relationship': 'ACTS_IN',\n    direction: 'IN',\n    target: 'Actor',\n    'cascade': 'detach'\n  }\n};\n```\n\n**Note**: Attempting to delete a Node without first removing any relationships will result in an error.\n\n\n### Deleting a set of nodes\nTODO\n```javascript\ninstance.delete(label, where)\n```\n\n```javascript\ninstance.delete('Person', {living: false});\n```\n\n### Deleting all nodes of a given type\n```javascript\ninstance.deleteAll('Person');\n  .then(() =\u003e console.log('Everyone has been deleted'));\n```\n\n\n## Query Builder\nNeode comes bundled with a query builder.  You can create a Query Builder instance by calling the `query()` method on the Neode instance.\n\n```javascript\nconst builder = instance.query();\n```\n\nOnce you have a Builder instance, you can start to defining the query using the fluent API.\n```javascript\nbuilder.match('p', 'Person')\n    .where('p.name', 'Adam')\n    .return('p');\n```\n\nFor query examples, check out the [Query Builder Test suite](https://github.com/adam-cowley/neode/blob/master/test/Query/Builder.spec.js).\n\n\n### Building Cypher\nYou 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.\n\n```javascript\nconst {query, params} = builder.build();\n\ninstance.query(query, params)\n    .then(res =\u003e {\n        console.log(res.records.length);\n    });\n```\n\n### Executing a Query\nYou can execute a query by calling the `execute()` method on the query builder.\n\n```javascript\nbuilder.match('this', 'Node')\n    .whereId('this', 1)\n    .return('this')\n    .execute()\n    .then(res =\u003e {\n        console.log(res.records.length);\n    });\n```\n\n\n## Schema\nNeode will install the schema created by the constraints defined in your Node definitions.\n\n### Installing the Schema\n```javascript\ninstance.schema.install()\n    .then(() =\u003e console.log('Schema installed!'))\n```\n\n**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.\n\n### Dropping the schema\nDropping the schema will remove all indexes and constraints created by Neode.  All other indexes and constraints will be left intact.\n\n```javascript\ninstance.schema.drop()\n    .then(() =\u003e console.log('Schema dropped!'))\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadam-cowley%2Fneode","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadam-cowley%2Fneode","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadam-cowley%2Fneode/lists"}