Using and Accessing neo4j-graphql with NodeJS

Following a previous, more introductory blog post on Neo4j and two of their GraphQL projects this post shows how to use Neo4j with GraphQL based on neo4j-graphql. Besides the easy installation, configuration and schema introspection on the Neo4j side it will also show how to access this Neo4j GraphQL server from external clients, especially from NodeJS.

Neo4j Configuration

As discussed in the mentioned blog post neo4j-graphql is the database extension (making the Neo4j server itself the GraphQL server), not to be confused with neo4j-graphql-js (which is to be used with an external GraphQL server).

Versions

neo4j-graphql: neo4j-graphql-3.3.0.1.jar

neo4j-apoc-procedure (optional): apoc-3.3.0.2-all.jar

Neo4j: Neo4j 3.3.3

Setup

Exact setup instructions with basic Dockerfiles can be found in the previous blog post. If you want to make use of the apoc utilities make sure to COPY these as well (as shown in the neo4j-graphql-js Dockerfile, however they are not mandatory for neo4j-graphql).

This post will use the Northwind Database in the Neo4j Web UI Import version (the full example, e.g. also including employees can be found here). To follow the exact examples here go to the beautiful Neo4j web interface and hit :play northwind-graph and follow the instructions (you will have to click a few import and index creation queries and then you are done).

After that, just to be sure, restart your Neo4j instance so that on startup neo4j-graphql can auto-create the GraphQL schema (alternatively you can as well POST your own schema to /graphql/id/).

Schema

After the restart go back to the Web UI, enter call db.schema and you get the following simplified Northwind Database:

NEO4J_Northwind_DB-Schema

There are also various other ways to examine the contents, structure and metadata of your currently mounted Neo4j database instance:

call apoc.meta.schema
call apoc.meta.data
call apoc.meta.graph
call apoc.meta.subGraph({labels:["Product", "Order"]})

To see the corresponding GraphQL schema (also from the Neo4j Web UI):

call graphql.schema()
call graphql.introspect("http://localhost:7474/graphql/",{Authorization:"Basic bmVvNGo...="})
# Authorization Header 
# "Basic <neo4j_user_name>:<password>" (base64 encoded)

Details on how the db schema is transformed to a GraphQL schema can be found here.

Access

Now that we know what the schema looks like let's see how we can access the Neo4j GraphQL endpoint from external clients. As kind of a "teaser": Start with simple queries ... and do be amazed that also complex queries are served to you as simple queries.

GraphiQL

The most simple way is to use GraphiQL. Setup instructions related to neo4j-graphql can be found here. If you are using the NodeJS based neo4j-graphql-js anyway you could also use the instructions in the previous blog post for setting up an GraphiQL endpoint (for the Neo4j based GraphQL Server) within neo4j-graphql-js.

A simple query might be something like this:
NEO4J_Northwind_DB-Sample-Query-1

It just gives you a list of productNames and categoryNames ... yet at the same time within one GraphQL request (and one query to the database ... multiple entities per query are turned into a UNION).

More complex examples could look like this:

# Cypher Query:
MATCH (p:Product {productName: "Chang"})<-[:ORDERS]-(c:Order {customerID: "SAVEA"}) RETURN c.orderID
# Cypher Result:
"c.orderID"
"11030"
"10722"
"10714"
"10440"
"10393"
# GraphiQL Query:
{
  Product(productName:"Chang") {
    orders(customerID:"SAVEA") {
      orderID
    }
  }
}
# GraphiQL Result:
"data": { "Product": [{ "orders": [ {"orderID": "11030"},{"orderID": "10722"},{"orderID": "10714"},{"orderID": "10440"},{"orderID": "10393"}]}]}

# Cypher Query:
MATCH (c:Customer)-[:PURCHASED]->(:Order)-[:ORDERS]->(p:Product)-[:PART_OF]->(:Category {categoryName: "Beverages"}) RETURN c.contactName, p.productName
# Cypher Result:
"c.contactName"   "p.productName"
"Michael Holz"    "Lakkalikööri"             
"Yvonne Moncada"  "Lakkalikööri"             
...               ...
# GraphiQL Query:
{
  Category(categoryName: "Beverages") {
    partOf {
      productName
      orders {
        purchased {
          contactName
        }
      }
    }
  }
}
# GraphiQL Result:
"data": {
  "Category": [
    {
      "partOf": [
        {
          "productName": "Lakkalikööri",
          "orders": [{
              "purchased": {
                "contactName": "Michael Holz"
              }
            },{
              "purchased": {
                "contactName": "Yvonne Moncada"
              }
            },...

Of course GraphiQL, as nice and useful as it is, is only for manual developing and testing purposes. So the next section will cover some more code-based options.

Curl

What else first than a plane curl command ... Be sure to have a host-mapping of your Neo4j container port if you want access it from outside the Docker Host (if access happens from another container the ports EXPOSED by Neo4j are sufficient if the other container is able to reach and resolve the Neo4j container):

# Manual, interacitve access from outside Docker

curl -X POST -H "Content-Type: application/json" -d '{"query": "{ Product {productName}}"}' http://<docker_host_ip>:7474/graphql/ -u neo4j
# Enter password
# Result
{"data":{"Product":[{"productName":"Chai"},{"productName":"Chang"}, ...

# Access from another container with Authorization Header
# "Basic <neo4j_user_name>:<password>" (base64 encoded)

curl -X POST -H "Content-Type: application/json" -H 'Authorization:Basic bmVvNGo...=' -d '{"query": "{ Product {productName}}"}' http://<container_name>:7474/graphql/

NodeJS - Server-Side

For accessing the Neo4j GraphQL server within a NodeJS application you could just use a simple HTTP request or the request npm module.

However, there is already the dedicated request module graphql-request for GraphQL endpoints. For an Express based application bootstrapped via express-generator the basic code snippet in routes/index.js could look like this:

const express = require('express');
const router = express.Router();

const { GraphQLClient } = require('graphql-request');

const client = new GraphQLClient('http://<container_name>:7474/graphql/', {
  headers: {
    Authorization: 'Basic bmVvNGo...=',
  },
})

const query = `{
  Customer {
    contactName
  }
}`
 
// For variables : https://www.npmjs.com/package/graphql-request#using-variables

router.get('/api', (req, res, next) => {
  client.request(query)
    .then(data => res.send(data))
    .catch(err => {
      console.log(err.response.errors) // GraphQL response errors
      console.log(err.response.data) // Response data if available
      return next(err);
    });
});

module.exports = router;

In a containerized environment the NodeJS container must be able to reach and resolve the Neo4J container (e.g. via links or by being on the same docker network in a docker swarm).

NodeJS - Client-Side

For a simple XHR based access example, just modify views/index.pug like this

extends layout

block content
  h1= title
  p Welcome to #{title}
  div
    input(type="button", value="GraphQL Test", onclick="graphQLTest()")

  script.
    const graphQLTest = () => {

      var xhr = new XMLHttpRequest();
      xhr.responseType = 'json';
      xhr.open('POST', 'http://<docker_host_ip>:7474/graphql/');

      xhr.setRequestHeader('Content-Type', 'application/json');
      xhr.setRequestHeader('Accept', 'application/json');
      xhr.setRequestHeader('Authorization', 'Basic bmVvNGo...=');

      xhr.onload = function () {
        alert(JSON.stringify(xhr.response.data.Supplier[0]));
      }

      xhr.send(JSON.stringify({query: `{
        Supplier(supplierID: "1") {
          _id
          supplierID
          country
          contactTitle
          address
          city
          phone
          contactName
          postalCode
          companyName
        }
      }`}));
    };  

This of course would mean that the Neo4j database container is exposed to the world (in a dockerized setup by a port host mapping) and that the credentials are passed down to the browser on the client side. So this xhr example is rather only for demonstration purposes than for production use (unless you use a proxy in front of Neo4j and something like csurf to sign your request on the client side and handle db authentication on the server side).

But as GraphQL has been primarily created as a lean query language for clients there also dedicated tools available: E.g. Apollo Client, Relay, urql, and vue-apollo. You can find good comparisons of some here (for Apollo vs. urql) and here (for Apollo vs. Relay).

Other Tools & Examples

As just mentioned there are other, more dedicated clients as well. A good overview can be found here and here. For complete fullstack examples have a look at GraphQL Boilerplates.

Summary

Only two lines for setup on the Neo4j side (copy the .jar and register it) and then: One endpoint, (m)any data response(s), and one roundtrip to the db.

And keep in mind: If the auto-created schema does not fit your needs you can post your own schema. Or use neo4j-graphql and its auto-created schema side-by-side with neo4j-graphql-js and use neo4j-graphql-js for the custom schema.

Further Information

Neo4j GraphQL Setup:
https://github.com/neo4j-graphql/neo4j-graphql/releases
https://hub.docker.com/_/neo4j/
(optionally):
https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases

GraphiQL:
https://github.com/graphql/graphiql
https://github.com/neo4j-graphql/neo4j-graphql#graphiql

graphql-request:
https://www.npmjs.com/package/graphql-request

GraphQL Client Examples:
http://graphql.org/graphql-js/graphql-clients/
https://blog.grandstack.io/a-look-at-graphql-clients-for-react-apps-512c2396cb53
https://blog.graph.cool/relay-vs-apollo-comparing-graphql-clients-for-react-apps-b40af58c1534
https://github.com/graphql-boilerplates
https://www.apollographql.com/client
https://facebook.github.io/relay/
https://github.com/FormidableLabs/urql
https://github.com/Akryum/vue-apollo

neo4j-graphql vs. neo4j-graphql-js:
https://daten-und-bass.io/blog/getting-started-with-neo4j-and-graphql/