The Neo4j server combines the REST API and the Web admin application in a single easy to install and run solution. The REST API has remained consistent from the 0.8 release and has simply been ported to the new server infrastructure. As such existing code should continue to run.
There's also information on the server on the wiki.
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 Jerseyclient components, which are easily downloaded via Maven.
Before we can perform any actions on the server, we need to start it as per the instructions on the wiki.
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()));
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 conenct to the server, then please revisit the getting started page on the wiki.
If you get any other response than 200 OK (particularly 4xx or 5xx responses) then please check your configuration and look in the log files in the data/log directory.
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/manage/node WebResource resource = Client.create().resource(nodeEntryPointUri); // http://localhost:7474/db/data/node ClientResponse response = resource.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).entity("{}").post(ClientResponse.class); // POST {} to the node entry point URI System.out.println(String.format("POST to [%s], status code [%d], location header [%s]", nodeEntryPointUri, response.getStatus(), response.getLocation().toString())); return response.getLocation();
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.
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; WebResource resource = Client.create().resource(propertyUri); // http://localhost:7474/db/data/node/{node_id}/properties/{property_name} 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()));
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.
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 relation representation to the start node of the relation. Correspondingly in Java we POST some JSON to the URI of our node that represents Joe Strummer, to establish a relation 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 relations, 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); ClientResponse response = resource.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).entity(relationshipJson).post(ClientResponse.class); // POST JSON to the relationships URI System.out.println(String.format("POST to [%s], status code [%d], location header [%s]", fromUri, response.getStatus(), response.getLocation().toString())); return response.getLocation(); }
If all goes well, we receive a 201 Created status code and a Location header which contains a URI of the newly created relation.
Like nodes, relations can have properties. Since we're big fans of both Joe Strummer and the Clash, we'll add a rating to the relation 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"); WebResource resource = Client.create().resource(propertyUri); String entity = toJsonNameValuePairCollection(name, value); 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())); }
Assuming all goes well, we'll get a 200 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.
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 POSTed 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 POSTing to the server, and in this case we've hardcoded the traverser to look for all nodes with outgoing relationships with the type "singer".
// TraversalDescription turns into JSON to send to the Server TraversalDescription t = new TraversalDescription(); t.setOrder(TraversalDescription.DEPTH_FIRST); t.setUniqueness(TraversalDescription.NODE); t.setMaxDepth(10); t.setReturnFilter(TraversalDescription.ALL); t.setRelationships(new Relationship("singer", Relationship.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 POSTing 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.getProperty("line.separator") + "%s", jsonTraverserPayload, traverserUri, response.getStatus(), response.getEntity(String.class)));
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" } ]
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.
The HTTP API provides a 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.