{"id":20956276,"url":"https://github.com/moxious/neo4j-serverless-functions","last_synced_at":"2025-09-03T10:32:55.847Z","repository":{"id":39708514,"uuid":"120679380","full_name":"moxious/neo4j-serverless-functions","owner":"moxious","description":"google cloud functions for ingesting data into neo4j","archived":false,"fork":false,"pushed_at":"2023-03-04T04:06:20.000Z","size":1592,"stargazers_count":18,"open_issues_count":14,"forks_count":9,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-05-14T05:33:26.384Z","etag":null,"topics":["cloud","cloudfunctions","google","graph","neo4j"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/moxious.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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,"zenodo":null}},"created_at":"2018-02-07T22:24:03.000Z","updated_at":"2023-01-05T15:00:03.000Z","dependencies_parsed_at":"2025-05-14T05:31:35.344Z","dependency_job_id":"8212b69c-8ec8-45f7-a9b0-38034448df07","html_url":"https://github.com/moxious/neo4j-serverless-functions","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/moxious/neo4j-serverless-functions","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moxious%2Fneo4j-serverless-functions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moxious%2Fneo4j-serverless-functions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moxious%2Fneo4j-serverless-functions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moxious%2Fneo4j-serverless-functions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/moxious","download_url":"https://codeload.github.com/moxious/neo4j-serverless-functions/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moxious%2Fneo4j-serverless-functions/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273430450,"owners_count":25104479,"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","status":"online","status_checked_at":"2025-09-03T02:00:09.631Z","response_time":76,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["cloud","cloudfunctions","google","graph","neo4j"],"created_at":"2024-11-19T01:25:05.429Z","updated_at":"2025-09-03T10:32:55.822Z","avatar_url":"https://github.com/moxious.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Neo4j Cloud Functions\n\n[![Actions Status](https://github.com/moxious/neo4j-serverless-functions/workflows/CI/badge.svg)](https://github.com/moxious/neo4j-serverless-functions/actions)\n\nCloud functions for working with Neo4j.  Deploy these to Google Cloud, and you can pipe\ndata into Neo4j from any system that can make an HTTP request, or can send a message to\na PubSub topic, like Cloud Dataflow, PubSub itself, and many others.\n\n## Features\n\n* Endpoint for running batched Cypher operations\n* Endpoint for piping in data via the CUD format\n* PubSub and HTTP availability\n\n## Pre-Requisites\n\n- Have a Google Cloud project\n- Have `gcloud` CLI installed\n- Enable the Cloud Functions API on that project.\n- Enable the Secrets Manager API\n- Create a service account with access to APIs above\n\n## Setup\n\n```\nyarn install\n```\n\n## Configure\n\nOn this branch you may use Google Secrets manager with the following keys, and ensure\nthat your service account has access to the following secrets.  Only the latest versions\nwill be used.\n\n- `NEO4J_USER`\n- `NEO4J_PASSWORD`\n- `NEO4J_URI`\n\n*If you do not enable secrets manager, the secrets will be taken from environment variables of the same name*\n\nThe functions will not execute correctly if these details are not provided one way or the other.\n\nAs an env var, you may set `GOOGLE_PROJECT` to point to the project where the secrets\nshould be taken from.\n\n## Deploy\n\n*Make sure to tweak the settings in this deploy*.  This deploys unsecured functions\nthat unauthenticated users can connect to.  Tailor the settings to your needs.\n\nThe `.github/workflows/build.yaml` file gives a GitHub Actions pipeline example of how\nto build and deploy this module.  The build requires a service key JSON secret `GOOGLE_APPLICATION_CREDENTIALS` and it requires a `GCP_PROJECT_ID` secret indicating the\nproject to deploy to.\n\nBelow commands are for manual deploys only, and are examples.\n\n### PubSub Triggered Functions\n\nMake sure to customize the trigger topic and environment variables!\n\n```\n# Ensure Google Secret Manager secrets are set\n\ngcloud functions deploy cudPubsub \\\n     --ingress-settings=all --runtime=nodejs12 --allow-unauthenticated \\\n     --timeout=300 \\\n     --set-env-vars GCP_PROJECT=${{ secrets.GCP_PROJECT_ID }} \\\n     --set-env-vars URI_SECRET=projects/graphs-are-everywhere/secrets/NEO4J_URI/versions/latest \\\n     --set-env-vars USER_SECRET=projects/graphs-are-everywhere/secrets/NEO4J_USER/versions/latest \\\n     --set-env-vars PASSWORD_SECRET=projects/graphs-are-everywhere/secrets/NEO4J_PASSWORD/versions/latest \\\n     --trigger-topic neo4j-cud\n\ngcloud functions deploy cypherPubsub \\\n     --ingress-settings=all --runtime=nodejs12 --allow-unauthenticated \\\n     --timeout=300 \\\n     --set-env-vars GCP_PROJECT=${{ secrets.GCP_PROJECT_ID }} \\\n     --set-env-vars URI_SECRET=projects/graphs-are-everywhere/secrets/NEO4J_URI/versions/latest \\\n     --set-env-vars USER_SECRET=projects/graphs-are-everywhere/secrets/NEO4J_USER/versions/latest \\\n     --set-env-vars PASSWORD_SECRET=projects/graphs-are-everywhere/secrets/NEO4J_PASSWORD/versions/latest \\\n     --trigger-topic cypher\n```\n\n### HTTP Functions\n\n(On deploy carefully note the secret env vars if you want to use GSM)\n\n```\nexport NEO4J_USER=neo4j\nexport NEO4J_PASSWORD=secret\nexport NEO4J_URI=neo4j+s://my-host:7687/\n\ngcloud functions deploy cud \\\n     --ingress-settings=all --runtime=nodejs12 --allow-unauthenticated \\\n     --timeout=300 \\\n     --set-env-vars NEO4J_USER=$NEO4J_USER,NEO4J_PASSWORD=$NEO4J_PASSWORD,NEO4J_URI=$NEO4J_URI \\\n     --trigger-http\n\ngcloud functions deploy cypher \\\n     --ingress-settings=all --runtime=nodejs12 --allow-unauthenticated \\\n     --timeout=300 \\\n     --set-env-vars NEO4J_USER=$NEO4J_USER,NEO4J_PASSWORD=$NEO4J_PASSWORD,NEO4J_URI=$NEO4J_URI \\\n     --trigger-http\n\ngcloud functions deploy node \\\n     --ingress-settings=all --runtime=nodejs12 --allow-unauthenticated \\\n     --timeout=300 \\\n     --set-env-vars NEO4J_USER=$NEO4J_USER,NEO4J_PASSWORD=$NEO4J_PASSWORD,NEO4J_URI=$NEO4J_URI \\\n     --trigger-http\n\ngcloud functions deploy edge \\\n     --ingress-settings=all --runtime=nodejs12 --allow-unauthenticated \\\n     --timeout=300 \\\n     --set-env-vars NEO4J_USER=$NEO4J_USER,NEO4J_PASSWORD=$NEO4J_PASSWORD,NEO4J_URI=$NEO4J_URI \\\n     --trigger-http\n```\n\n[See related documentation](https://cloud.google.com/functions/docs/env-var)\n\n## Quick Example of functions and their results\n\n```\n# Given this local deploy URL prefix (provided by local testing above)\nLOCALDEPLOY=http://localhost:8080/\n\n# CUD\ncurl --data @test/cud-messages.json \\\n    -H \"Content-Type: application/json\" -X POST \\\n    $LOCALDEPLOY\n\n# Cypher\ncurl --data @test/cypher-payload.json \\\n    -H \"Content-Type: application/json\" -X POST \\\n    $LOCALDEPLOY\n\n# Node\ncurl -H \"Content-Type: application/json\" -X POST \\\n   -d '{\"username\":\"xyz\",\"name\":\"David\"}' \\\n   $LOCALDEPLOY/node?label=User\n\n# Node\ncurl -H \"Content-Type: application/json\" -X POST \\\n   -d '{\"username\":\"foo\",\"name\":\"Mark\"}' \\\n   $LOCALDEPLOY/node?label=User\n\n# Edge\ncurl -H \"Content-Type: application/json\" -X POST \\\n   -d '{\"since\":\"yesterday\",\"metadata\":\"whatever\"}' \\\n   $LOCALDEPLOY'/edge?fromLabel=User\u0026fromProp=username\u0026fromVal=xyz\u0026toLabel=User\u0026toProp=username\u0026toVal=foo\u0026relType=knows'\n```\n\n![Example Result Graph](example.png)\n\n## Node Function\n\nThis function takes JSON body data reported into the endpoint, and creates a node with a specified label having those properties.   Example:\n\n```\ncurl -XPOST -d '{\"name\":\"Bob\"}' http://cloud-endpoint/node?label=Person\n```\n\nWill result in a node with the label Foo, having property names like `name:\"Bob\"`.\n\nIf deeply nested JSON is posted to the endpoint, the dictionary will be flattened, so that:\n```\n{\n    \"model\": {\n        \"name\": \"something\"\n    }\n}\n```\n\nWill turn into a property\n\n```\n`model.name`: \"something\"\n```\n\nin neo4j.\n\nBy customizing the URL you use for the webhook, you can track source of data.  For example,\nproviding to the slack external webhook a URL of: `http://cloud-endpoint/node?label=SlackMessage`.\n\n## Edge Function\n\nThis function matches two nodes, and creates a relationship between them with a given set of properties.\n\nExample:\n\n```\ncurl -XPOST -d '{\"x\":1,\"y\":2}' 'http://localhost:8010/test-drive-development/us-central1/edge?fromLabel=Foo\u0026toLabel=Foo\u0026fromProp=x\u0026toProp=x\u0026fro=6\u0026toVal=5\u0026relType=blark'\n```\n\nThis is equivalent to doing this in Cypher:\n\n```\n   MATCH (a:Foo { x: \"5\" }), (b:Foo { x: \"6\" })\n   CREATE (a)-[:blark { x: 1, y: 2 }]-\u003e(b);\n```\n\nAny POST'd JSON data will be stored as properties on the relationship.\n\n## CUD Function\n\nThe CUD function takes an array of CUD command objects.\n\nThe CUD format is a tiny JSON format that allows you to specify a graph \"Create, Update,\nor Delete\" (CUD) operation on a graph.  For example, a JSON message may indicate that you\nwant to create a node with certain labels and properties.\n\n[See here for documentation on the CUD format](https://neo4j.com/docs/labs/neo4j-streams/current/consumer/#_cud_file_format)\n\nExample:\n\n```\ncurl --data @test/cud-messages.json \\\n    -H \"Content-Type: application/json\" -X POST \\\n    $LOCALDEPLOY\n```\n\n## Cypher Function\n\nIt takes two simple arguments:  a cypher string, and an array of batch inputs.  An example\ninput would look like this:\n\n```\n{\n    \"cypher\": \"CREATE (p:Person) SET p += event\",\n    \"batch\": [\n        { \"name\": \"Sarah\", \"age\": 22 },\n        { \"name\": \"Bob\", \"age\": 25 }\n    ]\n}\n```\n\nYour query will always be prepended with the clause `UNWIND batch AS event` so that\nthe \"event\" variable reference will always be defined in your query to reference an individual\nrow of data.\n\nHere's another example which would create a set of relationships; make a simple social network\nin one JSON post body.\n\n```\n{\n    \"cypher\": \"MERGE (p1:Person{name: event.originator}) MERGE (p2:Person{name: event.accepter}) MERGE (p1)-[:FRIENDED { date: event.date }]-\u003e(p2)\",\n    \"batch\": [\n       { \"originator\": \"John\", \"accepter\": \"Sarah\", \"date\": \"2020-01-01\" },\n       { \"originator\": \"Anita\", \"accepter\": \"Joe\", \"date\": \"2020-01-02\" },\n       { \"originator\": \"Baz\", \"accepter\": \"John\", \"date\": \"2020-01-03\" },\n       { \"originator\": \"Evander\", \"accepter\": \"Sarah\", \"date\": \"2020-01-04\" },\n       { \"originator\": \"Idris\", \"accepter\": \"Evander\", \"date\": \"2020-01-05\" },\n       { \"originator\": \"Sarah\", \"accepter\": \"Baz\", \"date\": \"2020-01-06\" },\n       { \"originator\": \"Nia\", \"accepter\": \"Joe\", \"date\": \"2020-01-01\" },\n       { \"originator\": \"Joe\", \"accepter\": \"Baz\", \"date\": \"2020-01-03\" },\n       { \"originator\": \"Bob\", \"accepter\": \"Idris\", \"date\": \"2020-01-03\" },\n       { \"originator\": \"Joe\", \"accepter\": \"Evander\", \"date\": \"2020-01-03\" }\n    ]\n}\n```\n\n## Custom Cypher Function\n\nIn the Cypher function above, note that the message to the function requires sending a Cypher query.  Sometimes\nthe publisher of the message or sender of the JSON payload won't know the Cypher queries.  In this case, we want\nto bake in the cypher query and ensure that the function in question can only ever use 1 query.  That's what the\ncustom cypher function is for.  \n\nThe input format accepted is then only a batch array.  Because the Cypher sink query is \"baked into the function\",\nthe function you deploy can only ever run that query.\n\nHere's a deployment example of how you can use hard-wired custom cypher functions:\n\n```\necho \"GCP_PROJECT: ${{ secrets.GCP_PROJECT_ID }}\" \u003e\u003e /tmp/env.yaml\necho \"URI_SECRET: projects/graphs-are-everywhere/secrets/NEO4J_URI/versions/latest\" \u003e\u003e /tmp/env.yaml\necho \"USER_SECRET: projects/graphs-are-everywhere/secrets/NEO4J_USER/versions/latest\" \u003e\u003e /tmp/env.yaml\necho \"PASSWORD_SECRET: projects/graphs-are-everywhere/secrets/NEO4J_PASSWORD/versions/latest\" \u003e\u003e /tmp/env.yaml\necho 'CYPHER: \"MERGE (p:Person) SET p += event\"' \u003e\u003e /tmp/env.yaml\n\n\ngcloud functions deploy myCustomFunction \\\n    --entry-point customCypherPubsub \\\n    --ingress-settings=all --runtime=nodejs12 \\\n    --allow-unauthenticated --timeout=300 \\\n    --service-account=(address of service account)) \\\n    --env-vars-file /tmp/env.yaml \\\n    --trigger-topic personFeed\n```\n\nWhat this does:\n\n* The topic `personFeed` is assumed to publish arrays of JSON only (just the batch data)\n* It takes the `customCypherPubsub` function, and wires it to a Cloud Function called `myCustomFunction`\n* This function will only ever be able to execute the cypher query `MERGE (p:Person) SET p += event`\n\n## Security\n\n*It is very important you secure access to the functions in a way that is appropriate\nto your database*.  These functions fundamentally allow users to run cypher and modify\ndata, so take care to use the existing Google Cloud Functions utilities to secure\nthe endpoints.\n\nThe development documentation in this repo assume an insecure \"anyone can call this\"\nendpoint.  I recommend using [Google Cloud identity or network-based access control](https://cloud.google.com/functions/docs/securing)\n\n## Unit Testing\n\n```\nyarn test\n```\n\n## Local Testing\n\n```\n./node_modules/.bin/functions-framework --target=cud\n./node_modules/.bin/functions-framework --target=cypher\n./node_modules/.bin/functions-framework --target=node\n./node_modules/.bin/functions-framework --target=edge\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoxious%2Fneo4j-serverless-functions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoxious%2Fneo4j-serverless-functions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoxious%2Fneo4j-serverless-functions/lists"}