panache
panache

Reputation: 311

Building APIs to access Neo4j data

I have a huge Neo4j database that I created using the batch import tool. Now I want to expose certain parts of the data via APIs (that will run a query in the backend) to my users. My requirements are pretty general:
1. Latency should be minimum
2. Support qps of about ~10-20.

Can someone give me recommendations on what I should use for this and any documentation on how to go about this? I see several examples of ruby/rails and REST APIs -- however they are specific to exposing the data as is without any complex queries in the backend. I am not sure how to translate that into the specific APIs that I want. Any help would be appreciated.

Thanks.

Upvotes: 1

Views: 1070

Answers (4)

Mostafa Moradian
Mostafa Moradian

Reputation: 31

Use grest.

You can simply define your primary model(s) and its relation(s) (as secondary) and build an API with minimal coding and as quickly as possible!

Upvotes: 0

Michal Bachman
Michal Bachman

Reputation: 2661

Check out the GraphAware Framework. You can build the APIs directly on top of Neo4j (same JVM) but you have to use Cypher, Java, or Scala.

I'd start with Cypher, because you can write it very quickly, then optimise for performance, and finally, if all else fails and your latency is still to high, convert to Java.

You can expose subgraphs (or even partially hydrated nodes and relationship, i.e. only certain properties) very easily. Checkout out the stuff in the api package. Example code:

You'd write a controller to return a person's graph, but only include nodes' names (not ages or anything else):

@RestController
public class ApiExample {

    private final GraphDatabaseService database;

    @Autowired
    public ApiExample(GraphDatabaseService database) {
        this.database = database;
    }

    @RequestMapping(path = "person/{name}")
    public JsonGraph getPersonGraph(@PathVariable(value = "name") String name) {
        JsonGraph<?> result = new JsonGraph() {
            @Override
            protected JsonGraph self() {
                return this;
            }
        };

        try (Transaction tx = database.beginTx()) {
            Node person = database.findNode(label("Person"), "name", name);

            if (person == null) {
                throw new NotFoundException(); //eventually translate to 404
            }

            result.addNode(person, IncludeOnlyNameNodeTransformer.INSTANCE);

            for (Relationship worksFor : person.getRelationships(withName("WORKS_FOR"), Direction.OUTGOING)) {
                result.addRelationship(worksFor);
                result.addNode(worksFor.getEndNode(), IncludeOnlyNameNodeTransformer.INSTANCE);
            }

            tx.success();
        }

        return result;
    }

    private static final class IncludeOnlyNameNodeTransformer implements NodeTransformer<LongIdJsonNode> {

        private static final IncludeOnlyNameNodeTransformer INSTANCE = new IncludeOnlyNameNodeTransformer();

        private IncludeOnlyNameNodeTransformer() {
        }

        @Override
        public LongIdJsonNode transform(Node node) {
            return new LongIdJsonNode(node, new String[]{"name"});
        }
    }
}

Running this test

public class ApiExampleTest extends GraphAwareApiTest {

    @Override
    protected void populateDatabase(GraphDatabaseService database) {
        database.execute("CREATE INDEX ON :Person(name)");
        database.execute("CREATE (:Person {name:'Michal', age:32})-[:WORKS_FOR {since:2013}]->(:Company {name:'GraphAware', est:2013})");
    }

    @Test
    public void testExample() {
        System.out.println(httpClient.get(baseUrl() + "/person/Michal/", 200));
    }
}

would return the following JSON

{
  "nodes": [
    {
      "properties": {
        "name": "GraphAware"
      },
      "labels": [
        "Company"
      ],
      "id": 1
    },
    {
      "properties": {
        "name": "Michal"
      },
      "labels": [
        "Person"
      ],
      "id": 0
    }
  ],
  "relationships": [
    {
      "properties": {
        "since": 2013
      },
      "type": "WORKS_FOR",
      "id": 0,
      "startNodeId": 0,
      "endNodeId": 1
    }
  ]
}

Upvotes: 1

William Lyon
William Lyon

Reputation: 8556

I wrote a simple Flask API example that interfaces with Neo4j for a simple demo (backend for a messaging iOS app).

You might find it a helpful reference: https://github.com/johnymontana/messages-api

There are also a few resources online for using Flask with Neo4j:

Upvotes: 2

Brian Underwood
Brian Underwood

Reputation: 10856

Obviously you can roll your own using frameworks like Rails / Sinatra. If you want a standard for the way that your API is formatted I quite like the JSON API standard:

http://jsonapi.org/

Here is an episode of The Changelog podcast talking about it:

https://changelog.com/189/

There's also a gem for creating resource objects which determine what is exposed and what is not:

https://github.com/cerebris/jsonapi-resources

I tried it out a bit with the neo4j gem and it works at a basic level, though once you start getting into includes there seems to be some dependencies on ActiveRecord. I'd love to see issues like that worked out, though.

You might also check out the GraphQL standard which was created by Facebook:

https://github.com/facebook/graphql

There's a Ruby gem for it:

https://github.com/rmosolgo/graphql-ruby

And, of course, another episode of The Changelog ;)

http://5by5.tv/changelog/149

Various other API resources for Ruby:

https://github.com/webmachine/webmachine-ruby https://github.com/ruby-grape/grape

Upvotes: 1

Related Questions