Creating a graph through the REST API from Java
The REST API uses HTTP and JSON, so that it can be used from many languages and platforms. Still, when geting started it’s useful to see some patterns that can be re-used. In this brief overview, we’ll show you how to create and manipulate a simple graph through the REST API and also how to query it.
For these examples, we’ve chosen the Jersey client components, which are easily downloaded via Maven.
Start the server
Before we can perform any actions on the server, we need to start it as per Section 23.2, “Server Installation”. Next up, we’ll check the connection to the server:
WebResource resource = Client.create() .resource( SERVER_ROOT_URI ); ClientResponse response = resource.get( ClientResponse.class ); System.out.println( String.format( "GET on [%s], status code [%d]", SERVER_ROOT_URI, response.getStatus() ) ); response.close();
If the status of the response is 200 OK
, then we know the server is running fine and we can continue.
If the code fails to connect to the server, then please have a look at Operations.
Note If you get any other response than |
Sending Cypher
Using the REST API, we can send Cypher queries to the server. This is the main way to use Neo4j. It allows control of the transactional boundaries as needed.
Let’s try to use this to list all the nodes in the database which have a name
property.
final String txUri = SERVER_ROOT_URI + "transaction/commit"; WebResource resource = Client.create().resource( txUri ); String payload = "{\"statements\" : [ {\"statement\" : \"" +query + "\"} ]}"; ClientResponse response = resource .accept( MediaType.APPLICATION_JSON ) .type( MediaType.APPLICATION_JSON ) .entity( payload ) .post( ClientResponse.class ); System.out.println( String.format( "POST [%s] to [%s], status code [%d], returned data: " + System.lineSeparator() + "%s", payload, txUri, response.getStatus(), response.getEntity( String.class ) ) ); response.close();
For more details, see Section 20.1, “Transactional Cypher HTTP endpoint”.
Fine-grained REST API calls
For exploratory and special purposes, there is a fine grained REST API, see Chapter 20, REST API. The following sections highlight some of the basic operations.
Creating a node
The REST API uses POST
to create nodes.
Encapsulating that in Java is straightforward using the Jersey client:
final String nodeEntryPointUri = SERVER_ROOT_URI + "node"; // http://localhost:7474/db/data/node WebResource resource = Client.create() .resource( nodeEntryPointUri ); // POST {} to the node entry point URI ClientResponse response = resource.accept( MediaType.APPLICATION_JSON ) .type( MediaType.APPLICATION_JSON ) .entity( "{}" ) .post( ClientResponse.class ); final URI location = response.getLocation(); System.out.println( String.format( "POST to [%s], status code [%d], location header [%s]", nodeEntryPointUri, response.getStatus(), location.toString() ) ); response.close(); return location;
If the call completes successfully, under the covers it will have sent a HTTP request containing a JSON payload to the server.
The server will then have created a new node in the database and responded with a 201 Created
response and a Location
header with the URI of the newly created node.
In our example, we call this functionality twice to create two nodes in our database.
Adding properties
Once we have nodes in our datatabase, we can use them to store useful data. In this case, we’re going to store information about music in our database. Let’s start by looking at the code that we use to create nodes and add properties. Here we’ve added nodes to represent "Joe Strummer" and a band called "The Clash".
URI firstNode = createNode(); addProperty( firstNode, "name", "Joe Strummer" ); URI secondNode = createNode(); addProperty( secondNode, "band", "The Clash" );
Inside the addProperty
method we determine the resource that represents properties for the node and decide on a name for that property.
We then proceed to PUT
the value of that property to the server.
String propertyUri = nodeUri.toString() + "/properties/" + propertyName; // http://localhost:7474/db/data/node/{node_id}/properties/{property_name} WebResource resource = Client.create() .resource( propertyUri ); ClientResponse response = resource.accept( MediaType.APPLICATION_JSON ) .type( MediaType.APPLICATION_JSON ) .entity( "\"" + propertyValue + "\"" ) .put( ClientResponse.class ); System.out.println( String.format( "PUT to [%s], status code [%d]", propertyUri, response.getStatus() ) ); response.close();
If everything goes well, we’ll get a 204 No Content
back indicating that the server processed the request but didn’t echo back the property value.
Adding relationships
Now that we have nodes to represent Joe Strummer and The Clash, we can relate them.
The REST API supports this through a POST
of a relationship representation to the start node of the relationship.
Correspondingly in Java we POST
some JSON to the URI of our node that represents Joe Strummer,
to establish a relationship between that node and the node representing The Clash.
URI relationshipUri = addRelationship( firstNode, secondNode, "singer", "{ \"from\" : \"1976\", \"until\" : \"1986\" }" );
Inside the addRelationship
method, we determine the URI of the Joe Strummer node’s relationships, and then POST
a JSON description of our intended relationship.
This description contains the destination node, a label for the relationship type, and any attributes for the relation as a
JSON collection.
private static URI addRelationship( URI startNode, URI endNode, String relationshipType, String jsonAttributes ) throws URISyntaxException { URI fromUri = new URI( startNode.toString() + "/relationships" ); String relationshipJson = generateJsonRelationship( endNode, relationshipType, jsonAttributes ); WebResource resource = Client.create() .resource( fromUri ); // POST JSON to the relationships URI ClientResponse response = resource.accept( MediaType.APPLICATION_JSON ) .type( MediaType.APPLICATION_JSON ) .entity( relationshipJson ) .post( ClientResponse.class ); final URI location = response.getLocation(); System.out.println( String.format( "POST to [%s], status code [%d], location header [%s]", fromUri, response.getStatus(), location.toString() ) ); response.close(); return location; }
If all goes well, we receive a 201 Created
status code and a Location
header which contains a URI of the newly created relation.
Add properties to a relationship
Like nodes, relationships can have properties. Since we’re big fans of both Joe Strummer and the Clash, we’ll add a rating to the relationship so that others can see he’s a 5-star singer with the band.
addMetadataToProperty( relationshipUri, "stars", "5" );
Inside the addMetadataToProperty
method, we determine the URI of the properties of the relationship and PUT
our new values (since it’s PUT
it will always overwrite existing values, so be careful).
private static void addMetadataToProperty( URI relationshipUri, String name, String value ) throws URISyntaxException { URI propertyUri = new URI( relationshipUri.toString() + "/properties" ); String entity = toJsonNameValuePairCollection( name, value ); WebResource resource = Client.create() .resource( propertyUri ); ClientResponse response = resource.accept( MediaType.APPLICATION_JSON ) .type( MediaType.APPLICATION_JSON ) .entity( entity ) .put( ClientResponse.class ); System.out.println( String.format( "PUT [%s] to [%s], status code [%d]", entity, propertyUri, response.getStatus() ) ); response.close(); }
Assuming all goes well, we’ll get a 204 OK
response back from the server (which we can check by calling
ClientResponse.getStatus()
) and we’ve now established a very small graph that we can query.
Querying graphs
As with the embedded version of the database, the Neo4j server uses graph traversals to look for data in graphs.
Currently the Neo4j server expects a JSON payload describing the traversal to be POST
-ed at the starting node for the traversal (though this is likely to change in time to a GET
-based approach).
To start this process, we use a simple class that can turn itself into the equivalent JSON, ready for POST
-ing to the server, and in this case we’ve hardcoded the traverser to look for all nodes with outgoing relationships with the type "singer"
.
// TraversalDefinition turns into JSON to send to the Server TraversalDefinition t = new TraversalDefinition(); t.setOrder( TraversalDefinition.DEPTH_FIRST ); t.setUniqueness( TraversalDefinition.NODE ); t.setMaxDepth( 10 ); t.setReturnFilter( TraversalDefinition.ALL ); t.setRelationships( new Relation( "singer", Relation.OUT ) );
Once we have defined the parameters of our traversal, we just need to transfer it.
We do this by determining the URI of the traversers for the start node, and then POST
-ing the JSON representation
of the traverser to it.
URI traverserUri = new URI( startNode.toString() + "/traverse/node" ); WebResource resource = Client.create() .resource( traverserUri ); String jsonTraverserPayload = t.toJson(); ClientResponse response = resource.accept( MediaType.APPLICATION_JSON ) .type( MediaType.APPLICATION_JSON ) .entity( jsonTraverserPayload ) .post( ClientResponse.class ); System.out.println( String.format( "POST [%s] to [%s], status code [%d], returned data: " + System.lineSeparator() + "%s", jsonTraverserPayload, traverserUri, response.getStatus(), response.getEntity( String.class ) ) ); response.close();
Once that request has completed, we get back our dataset of singers and the bands they belong to:
[ { "outgoing_relationships" : "http://localhost:7474/db/data/node/82/relationships/out", "data" : { "band" : "The Clash", "name" : "Joe Strummer" }, "traverse" : "http://localhost:7474/db/data/node/82/traverse/{returnType}", "all_typed_relationships" : "http://localhost:7474/db/data/node/82/relationships/all/{-list|&|types}", "property" : "http://localhost:7474/db/data/node/82/properties/{key}", "all_relationships" : "http://localhost:7474/db/data/node/82/relationships/all", "self" : "http://localhost:7474/db/data/node/82", "properties" : "http://localhost:7474/db/data/node/82/properties", "outgoing_typed_relationships" : "http://localhost:7474/db/data/node/82/relationships/out/{-list|&|types}", "incoming_relationships" : "http://localhost:7474/db/data/node/82/relationships/in", "incoming_typed_relationships" : "http://localhost:7474/db/data/node/82/relationships/in/{-list|&|types}", "create_relationship" : "http://localhost:7474/db/data/node/82/relationships" }, { "outgoing_relationships" : "http://localhost:7474/db/data/node/83/relationships/out", "data" : { }, "traverse" : "http://localhost:7474/db/data/node/83/traverse/{returnType}", "all_typed_relationships" : "http://localhost:7474/db/data/node/83/relationships/all/{-list|&|types}", "property" : "http://localhost:7474/db/data/node/83/properties/{key}", "all_relationships" : "http://localhost:7474/db/data/node/83/relationships/all", "self" : "http://localhost:7474/db/data/node/83", "properties" : "http://localhost:7474/db/data/node/83/properties", "outgoing_typed_relationships" : "http://localhost:7474/db/data/node/83/relationships/out/{-list|&|types}", "incoming_relationships" : "http://localhost:7474/db/data/node/83/relationships/in", "incoming_typed_relationships" : "http://localhost:7474/db/data/node/83/relationships/in/{-list|&|types}", "create_relationship" : "http://localhost:7474/db/data/node/83/relationships" } ]
Phew, is that it?
That’s a flavor of what we can do with the REST API.
Naturally any of the HTTP idioms we provide on the server can be easily wrapped, including removing nodes and relationships through DELETE
.
Still if you’ve gotten this far, then switching .post()
for .delete()
in the Jersey client code should be straightforward.
What’s next?
The HTTP API provides a good basis for implementers of client libraries, it’s also great for HTTP and REST folks. In the future though we expect that idiomatic language bindings will appear to take advantage of the REST API while providing comfortable language-level constructs for developers to use, much as there are similar bindings for the embedded database.