{"id":14975535,"url":"https://github.com/svenwesterlaken/mongo4j","last_synced_at":"2025-10-27T14:30:34.171Z","repository":{"id":30847429,"uuid":"126174120","full_name":"SvenWesterlaken/mongo4j","owner":"SvenWesterlaken","description":"A mongoose plugin to automatically maintain nodes \u0026 relationships in neo4j","archived":false,"fork":false,"pushed_at":"2025-01-21T06:07:18.000Z","size":1869,"stargazers_count":15,"open_issues_count":12,"forks_count":4,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-02-01T06:05:45.546Z","etag":null,"topics":["document-database","graph-database","javascript","moneo","mongo","mongodb","mongoose","mongoose-plugin","mongoosejs","neo4j","neo4j-driver","node","node-js","nosql-database","object-graph-mapper","ogm","persistence","polyglot","polyglot-persistence"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/mongo4j","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/SvenWesterlaken.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","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":"2018-03-21T12:18:48.000Z","updated_at":"2025-01-10T17:43:43.000Z","dependencies_parsed_at":"2024-09-11T11:27:14.384Z","dependency_job_id":"daacff4f-b776-44ce-b2fb-9db846f6a433","html_url":"https://github.com/SvenWesterlaken/mongo4j","commit_stats":{"total_commits":308,"total_committers":9,"mean_commits":34.22222222222222,"dds":"0.35389610389610393","last_synced_commit":"c645b15854c429149488389a7b0ed32af4fc3f71"},"previous_names":[],"tags_count":37,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SvenWesterlaken%2Fmongo4j","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SvenWesterlaken%2Fmongo4j/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SvenWesterlaken%2Fmongo4j/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SvenWesterlaken%2Fmongo4j/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SvenWesterlaken","download_url":"https://codeload.github.com/SvenWesterlaken/mongo4j/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238508464,"owners_count":19484135,"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":["document-database","graph-database","javascript","moneo","mongo","mongodb","mongoose","mongoose-plugin","mongoosejs","neo4j","neo4j-driver","node","node-js","nosql-database","object-graph-mapper","ogm","persistence","polyglot","polyglot-persistence"],"created_at":"2024-09-24T13:52:10.723Z","updated_at":"2025-10-27T14:30:28.854Z","avatar_url":"https://github.com/SvenWesterlaken.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./.github/images/mongo4j.png\" width=\"310\" height=\"125\"/\u003e\n\u003c/p\u003e\n\u003ch1 align=\"center\"\u003eMongo4J\u003c/h1\u003e\n\n\u003cp align=\"center\" style=\"text-align: center;\"\u003e\n  \u003ca href=\"https://github.com/SvenWesterlaken/mongo4j/actions\"\u003e\u003cimg src=\"https://github.com/SvenWesterlaken/mongo4j/workflows/test%20\u0026amp;%20release/badge.svg?branch=master\" alt=\"Build\" style=\"max-width:100%;\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://coveralls.io/github/SvenWesterlaken/mongo4j?branch=master\" rel=\"nofollow\"\u003e\u003cimg src=\"https://camo.githubusercontent.com/fa0cbb69b0e892fb58f9c7174cb07b5e5ce8a76acf9fd8a5ee6186af0c6b151b/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f5376656e5765737465726c616b656e2f6d6f6e676f346a2f62616467652e7376673f6272616e63683d6d6173746572\" alt=\"Coverage Status\" data-canonical-src=\"https://coveralls.io/repos/github/SvenWesterlaken/mongo4j/badge.svg?branch=master\" style=\"max-width:100%;\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/mongo4j\" rel=\"nofollow\"\u003e\u003cimg src=\"https://camo.githubusercontent.com/e629ecdc387bc40106bff31844373294ac28bfbc88855a873de59ec37382913c/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f6d6f6e676f346a2e737667\" alt=\"npm\" data-canonical-src=\"https://img.shields.io/npm/v/mongo4j.svg\" style=\"max-width:100%;\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/mongo4j\" rel=\"nofollow\"\u003e\u003cimg src=\"https://camo.githubusercontent.com/8a9d847c262af99c0234a34a4468ba26c8e5757c4eb740850dc4cbe4647b7f21/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f64742f6d6f6e676f346a2e737667\" alt=\"npm\" data-canonical-src=\"https://img.shields.io/npm/dt/mongo4j.svg\" style=\"max-width:100%;\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cblockquote align=\"center\"\u003e\n  \u003cp\u003eA \u003ca href=\"http://mongoosejs.com/\" rel=\"nofollow\"\u003emongoose\u003c/a\u003e plugin to automatically maintain nodes in \u003ca href=\"https://neo4j.com/\" rel=\"nofollow\"\u003eneo4j\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\n## Table of contents\n- [Motivation](#motivation---why-mongo4j-another-library)\n- [Installation](#installation)\n- [Setup](#setup)\n  - [Single driver](#single-driver)\n  - [Multiple drivers](#multiple-drivers)\n  - [Add the plugin to the schema](#add-the-plugin-to-the-schema)\n- [Driver Management](#driver-management)\n- [Schema configuration options](#schema-configuration-options)\n  - [Standard Properties](#standard-properties)\n  - [Relationships (References, Nested References \u0026 Subdocuments)](#relationships-references-nested-references--subdocuments)\n- [Document Lifecycle](#document-lifecycle)\n  - [Saving](#saving)\n  - [Updating](#updating)\n  - [Removing](#removing)\n- [Methods](#methods)\n  - [Static](#static)\n- [Examples](#examples)\n- [FAQ](#why-is-there-a-deletion-query-in-the-update-function)\n- [Upcoming features \u0026 to-do-list](#upcoming-features--to-do-list)\n- [Credits](#credits)\n\n## Motivation - Why Mongo4J, another library?\n\nThe usage of mongo4j is found in the term [polyglot persistence](https://en.wikipedia.org/wiki/Polyglot_persistence). In this case, you will most likely want to combine the 'relationship-navigation' of neo4j while still maintaining documents in MongoDB for quick access and saving all information. Unfortunately, this also brings in extra maintenance to keep both databases in-sync. For this matter, several plugins and programs have been written, under which [moneo](https://github.com/srfrnk/moneo), [neo4j-doc-manager](https://neo4j.com/developer/neo4j-doc-manager/) \u0026 [neomongoose](https://www.npmjs.com/package/neomongoose).\n\nThese are great solutions, but I've found myself not fully satisfied by these. The doc manager, for example, needs another application layer to install and run it. The other two solutions were either out of date or needed a manual form of maintaining the graphs in neo4j. That's why I decided to give my own ideas a shot in the form of a mongoose plugin.\n\nMongo4J automatically updates, removes and adds graphs according to the given schema configuration. In addition to this, it adds extra functions to access the graphs from the models through mongoose. This way, there is no need to keep two different approaches to the neo4j-database.\n\n## Installation\n\nDownload and install the package with npm:\n```bash\nnpm install --save mongo4j\n```\n\n## Setup\n\nBefore you use (require) mongo4j anywhere, **First initialize it with drivers**.\n\nThis creates the singleton pattern lifecycle of driver(s) stated by the [neo4j-driver library](https://github.com/neo4j/neo4j-javascript-driver#usage-examples).\n\nSame options can be used as the official driver and there is the possibility of initializing multiple drivers in the beginning. Which should be **only one driver per neo4j database**. Options can be found on the neo4j driver [documentation](https://neo4j.com/docs/api/javascript-driver/current/function/index.html#static-function-driver).\n\n### Single driver\n\n#### mongo4j.init(host, auth, options)\n- `host` - Url to neo4j database. Defaults to `neo4j://127.0.0.1`\n- `auth` - Authentication parameters:\n  - `user` - User for neo4j authentication. Defaults to `neo4j`\n  - `pass` - Password for neo4j authentication. Defaults to `neo4j`\n- `options` - Options for neo4j driver. These can be found in the [documentation](https://neo4j.com/docs/api/javascript-driver/current/function/index.html#static-function-driver).\n\n```javascript\nconst mongo4j = require('mongo4j');\n\nmongo4j.init('neo4j://localhost', {user: 'neo4j', pass: 'neo4j'});\n```\n\n### Multiple drivers\n\n#### mongo4j.init(hosts, auth, options)\n\n- `hosts` - Array of hosts. A host in this case consists of:\n  - `name` - Identifier to reference this specific driver. _(Must be a string)_ **Required**\n  - `url` - Url to neo4j database. Defaults to `neo4j://127.0.0.1`\n  - `auth` - Authentication parameters:\n    - `user` - User for neo4j authentication. Defaults to `neo4j`\n    - `pass` - Password for neo4j authentication. Defaults to `neo4j`\n  - `options` - Options for neo4j driver. These can be found in the [documentation](https://neo4j.com/docs/api/javascript-driver/current/function/index.html#static-function-driver).\n- `auth` - Authentication parameters. _Will be overwritten by individual authentication set in hosts_:\n  - `user` - User for neo4j authentication. Defaults to `neo4j`\n  - `pass` - Password for neo4j authentication. Defaults to `neo4j`\n- `options` - Options for neo4j driver. These can be found in the [documentation](https://neo4j.com/docs/api/javascript-driver/current/function/index.html#static-function-driver). _Will be overwritten by individual options set in hosts_\n\nIn the case of multiple drivers make sure you initialize every driver with an identifier (name) in string format for later re-use, otherwise, an error will be thrown.\n\n```javascript\nconst mongo4j = require('mongo4j');\n\nmongo4j.init(\n  [{\n    name: 'testconnection1',\n    url: 'neo4j://127.0.0.1',\n    auth: {\n      user: 'neo4j',\n      pass: 'neo4j'\n    }\n  }, {\n    name: 'testconnection2',\n    url: 'neo4j://127.0.0.1'\n  }]\n);\n```\n\nAuthentication can be specified as a second argument to use the same authentication for all drivers. Authentication set per host will override these global authentication settings.\n\nThe same goes for options. If you only want to use shared options, make sure you pass `null` as a second argument:\n\n```javascript\nconst mongo4j = require('mongo4j');\n\n// connectionPoolSize is set for both drivers\nmongo4j.init([host1, host2], null, {connectionPoolSize: 100});\n```\n\n### Add the plugin to the schema\n\n#### CustomSchema.plugin(moneo.plugin(identifier))\n- `identifier` - Identifier to reference the specific driver to use _(in case of multiple drivers)_.\n\n```javascript\n// Use the default driver connection (in case of one driver)\nPersonSchema.plugin(mongo4j.plugin());\n\n// Use the 'testconnection1' driver to connect to neo4j\nPersonSchema.plugin(mongo4j.plugin('testconnection1'))\n```\n\n## Driver management\nThese functions will help manage the drivers for neo4j.\n\n#### mongo4j.getDriver(identifier)\n- `identifier` - Identifier to reference the specific driver. Can also be an integer. **Required in case of multiple drivers**\n\n**Returns:** a driver. In the case of multiple drivers. It will return an `Object` like:\n```javascript\n{\n  name: 'testconnection1', // Identifier\n  driver: //Neo4JDriver\n}\n```\n\n```javascript\n// Get driver in case of only one\nmongo4j.getDriver();\n\n// Get testconnection1 driver in case of multiple\nmongo4j.getDriver('testconnection1');\n\n// Get testconnection1 driver in case of multiple with integer identifier\n// NOTE: identifier = index + 1\nmongo4j.getDriver(1);\n```\n\n#### mongo4j.close(identifier)\n- `identifier` - Identifier to reference the specific driver. Can also be an integer or `true` to close all drivers at once. **Required in case of multiple drivers**\n\n**Returns:** a single `Promise` (also in case of multiple drivers).\n\n```javascript\n// Close driver in case of only one\nmongo4j.close();\n\n// Close testconnection1 driver in case of multiple\nmongo4j.close('testconnection1');\n\n// Close testconnection1 driver in case of multiple with integer identifier\n// NOTE: identifier = index + 1\nmongo4j.close(1);\n\n// Close all drivers in case of multiple\nmongo4j.close(true);\n```\n\n#### mongo4j.reset()\n**Returns:** a single `Promise` (also in case of multiple drivers).\n\n```javascript\n// Close all drivers and set drivers to undefined in mongo4j context\nmongo4j.reset();\n```\n\n## Schema configuration options\n\nAfter you have added mongo4j as a plugin to your document schema there are several properties to configure which and how data of the document is saved in neo4j.\n\n### Standard Properties\nThese options apply to simple schema properties.\n\n#### neo_prop: `Boolean`\n- Defaults to `false`.\n- If set to `true` this property will be saved in neo4j.\n- **Note:** the `_id` property in MongoDB will automatically be added as `m_id` in neo4j.\n\n```javascript\n// Save firstName as a property in neo4j\nconst PersonSchema = new Schema({\n  firstName: {\n    type: String,\n    neo_prop: true\n  }\n});\n```\n\n### Relationships (References, Nested References \u0026 Subdocuments)\nReferences, nested references \u0026 subdocuments are automatically saved as different nodes as explained [here](#saving).\nTherefore there are several options to configure how to relationship is saved.\n\n#### neo_rel_name: `String`\n- Defaults to `[PROPERTY NAME]_[DOCUMENT TYPE]_[RELATED DOCUMENT TYPE]`. ie: `SUPERVISOR_CLASS_PERSON`\n- **Note:** relationships will be converted to uppercase to conform to the neo4j naming conventions.\n\n```javascript\n// Results in 'TAUGHT_BY' relationship\nconst ClassSchema = new Schema({\n  teacher: {\n    type: mongoose.Schema.ObjectId,\n    ref: 'Person',\n    neo_rel_name: \"Taught By\"\n  }\n});\n\n// NOTE: CLASS refers to class mongo model, not an actual Javascript class.\n// Results in 'SUPERVISOR_CLASS_PERSON' relationship (including a start_date property)\nconst ClassSchema = new Schema({\n  supervisor: {\n    person: {\n      type: mongoose.Schema.ObjectId,\n      ref: 'Person'\n    },\n    start_date: Date\n  },\n});\n```\n\n#### neo_omit_rel: `Boolean`\n- Defaults to `false`.\n- If set to `true` this relationship will not be saved (omitted) in neo4j.\n\n```javascript\n// Don't save the relationship to teacher in neo4j (the teacher can still be saved separately)\nconst ClassSchema = new Schema({\n  teacher: {\n    type: mongoose.Schema.ObjectId,\n    ref: 'Person',\n    neo_omit_rel: true\n  }\n});\n```\n\n## Document lifecycle\n\n### Saving\n\nSaving a mongo-document in neo4j is executed as you would normally. Therefore, return values will still be the same as without mongo4j. Post hooks of `Document.save()` \u0026 `Model.insertMany()` will cause the saved document(s) to be saved in neo4j as well.\n\n**Note:** The hooks for saving in neo4j are executed asynchronously.\n\n```javascript\nconst Person = require('path/to/models/person');\n\nneil = new Person({\n  firstName: \"Neil\",\n  lastName: \"Young\",\n  address: {\n    city: \"Brentwood\",\n    street: \"Barleau St.\"\n  }\n});\n\n// Save 'neil' as a node in neo4j (as well as MongoDB) according to the schema configuration\nneil.save();\n\nconst henry = new Person({firstName: \"Henry\", lastName: \"McCoverty\"});\nconst daniel = new Person({firstName: \"Daniel\", lastName: \"Durval\"});\nconst jason = new Person({firstName: \"Jason\", lastName: \"Campbell\"});\n\n// Save all three persons in neo4j as well as MongoDB\nPerson.insertMany([daniel, jason, henry]);\n```\n\n### Updating\n\nUnfortunately, mongoose doesn't supply a direct way of accessing data in update hooks. Therefore a custom method on the document will be used that will both handle the saving in MongoDB and neo4j. It can be seen as a wrapper around the original `Document.updateOne()` method.\n\n#### Document.updateNeo(criteria, options, cb)\n- **Note**: parameters are identical to that of `Model.updateOne()`. Detailed documentation can therefore be found [here](https://mongoosejs.com/docs/api.html#document_Document-updateOne).\n- `criteria`: Data that should be changed _(json format)_\n- `options`: options for the `updateOne()` method executed. Refer to the [documentation](https://mongoosejs.com/docs/api.html#query_Query-setOptions) of mongoose for available options.\n- `cb`: Callback function to be executed by the `updateOne()` method.\n\n**Returns:** a promise with a result of an array containing (in order):\n- Result of the updateOne method. See [documentation](https://mongoosejs.com/docs/api.html#document_Document-updateOne)\n- Result of the cypher update query\n- Result of the cypher query that deleted all the previous relationships. **(If not executed this will be null)**. Why this query is executed is explained [here](#why-is-there-a-deletion-query-in-the-update-function).\n\n```javascript\n// variable `person` refers to a document fetched from the database or returned as a result after saving\n\n// Update the firstname to 'Peter' and lastname to 'Traverson'.\nperson.updateNeo({firstName: 'Peter', lastName: 'Traverson'}).then((results) =\u003e {\n  // First item of the array is the result of the update query by mongoose\n  let mongoUpdateResult = results[0];\n\n  // Second item of the array is the result of the neo4j cypher query for updates\n  let neo4jUpdateResult = results[1];\n\n  // Third item of the array is the result of the delete query. In this case null,\n  // because the updates didn't involve any changes in relationships between nodes.\n  let neo4jDeleteResult = result[2];\n});\n```\n\n### Removing\n\nRemoving a mongo-document in neo4j is executed as you would normally. Post hooks of `Document.remove()` will cause the removed document(s) to be removed in neo4j as well (including subdocuments \u0026 relationships; not the related docs, of course).\n\n```javascript\n// Remove 'neil' from neo4j as well as mongo\nneil.remove()\n```\n\n## Methods\n### Static\nThese methods can be called without an instance of an object. In other words, straight from the model.\n\n#### Model.cypherQuery(query, params, options)\n- `query`: Cypher query to execute in string format.\n- `params`: Parameters of the query. More info on this in the neo4j [driver-manual](https://neo4j.com/docs/driver-manual/current/cypher-workflow/#driver-queries-results)\n- `options`: Object with the following options for the query:\n  - `sub`: Return a subscription. Can be used as explained [here](https://github.com/neo4j/neo4j-javascript-driver#consuming-records-with-streaming-api). Defaults to `false`\n  - `parse`: Parse result with [parse-neo4j](https://www.npmjs.com/package/parse-neo4j). This is only available in the case of a Promise. If both options are `true` the query will throw an error. Defaults to `false`\n\n**Returns:** a `Promise` with the result of the [cypher query](https://github.com/neo4j/neo4j-javascript-driver#consuming-records-with-promise-api). _or a subscription in case of sub-options set to `true`._\n\n**Note**: The session is automatically closed after the query, _**only in the case of a promise!**_\n\n```javascript\nconst Person = require('./models/person');\n\n// Basic usage with a cypher query\nPerson.cypherQuery('MATCH (n:Person)-[r:Takes_Class]-(c:Class) return n;')\n  .then(result =\u003e {\n    result.records.forEach(record =\u003e {\n      console.log(record.get('name'))\n    })\n  })\n  .catch(error =\u003e {\n    console.log(error)\n  })\n```\n```javascript\n// Run query with parse on for the result \u0026 using parameters for the query\nPerson.cypherQuery('MATCH (n:Person {name: $nameParam }) RETURN n;', {nameParam: 'James'}, { parse: true });\n  .then(result =\u003e {\n    result.records.forEach(record =\u003e {\n      console.log(record.get('name'))\n    })\n  })\n  .catch(error =\u003e {\n    console.log(error)\n  })\n```\n```javascript\n// Run query with sub on to handle the cypher query with the stream api\nPerson.cypherQuery('MATCH (n:Person) RETURN n;', { sub: true })\n  .subscribe({\n    onKeys: keys =\u003e {\n      console.log(keys)\n    },\n    onNext: record =\u003e {\n      console.log(record.get('name'))\n    },\n    onCompleted: () =\u003e {\n      session.close() // returns a Promise\n    },\n    onError: error =\u003e {\n      console.log(error)\n    }\n  })\n```\n\n## Examples\nFor examples, refer to the [test cases](test/functions/) \u0026 [test models](test/models) for now.\n\n## FAQ\n### Why is there a deletion query in the update function?\nAfter trying a couple of times, I couldn't find a consistent way of determining what nodes or relationships have changed and to what.\nAt the time (may still be) the data also couldn't fit into a single query.\nIn order to maintain flexibility and speed, a delete query has been added before refilling the neo4j database with the new relationships, nodes \u0026 data.\n\n## Upcoming features \u0026 to-do-list\nUnfortunately, I don't have much time for keeping this repo up-to-date. However, from time to time I will try to have a look and see where I can fix or expand features. Right now all of the functionality described should work correctly and should cover the basic needs for scenarios where this package would be used.\n\n**Feel free to contribute by picking something from the to-do-list below and making a pull-request!**\n_I will check these every now and then_\n\n#### To-do-list:\n\n- Wrappers around static functions of a model (adding, updating \u0026 deleting)\n- Code documentation\n- Debug Mode (ie. show neo4j query's)\n- Helper functions for neo4j access\n- State hooks\n- Work with the new reactive sessions from neo4j\n\n## Credits\n\nBig shoutout to [srfrnk](https://github.com/srfrnk) for creating the repo called [moneo](https://github.com/srfrnk/moneo).\n\nAfter some digging through the code, I missed some functionality and saw that the old HTTP driver for neo4j was used.\nI decided to rewrite the code with extra functionality and use the [new neo4j driver](https://github.com/neo4j/neo4j-javascript-driver) with _'bolt'_ connection.\n\nMoneo has provided me with the basic info to get started and mongo4j could be seen as a (continued) **version 2.0**.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsvenwesterlaken%2Fmongo4j","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsvenwesterlaken%2Fmongo4j","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsvenwesterlaken%2Fmongo4j/lists"}