cypher-rs
cypher-rs copied to clipboard
Configurable Cypher REST endpoints
Cypher-RS
Neo4j server-extension that allows to configure fixed REST-Endpoints for Cypher queries.
You can PUT cypher queries to an endpoint with a certain url-suffix and then later execute those queries by running
GETwith query parameters, only readonly queriesPOSTwith JSON (map, list of maps) payload for parametersPOSTwith CSV payload for parameters, with optional batch-size and delimiter
CREATE ENDPOINT
Verb: PUT
URL: /cypher-rs/<yourEndpoint>
Headers:
Content-type: plain/text
Body:
<yourCypherRequest>
Examples
PUT /cypher-rs/users
Content-type: plain/text
Body:
MATCH (n:User) WHERE n.name={name} RETURN n
--> 201 Location: /cypher-rs/users
PUT /cypher-rs/create-user
Content-type: plain/text
Body:
CREATE (n:Node {name:{name},age:{age},male:{male}})
--> 201 Location: /cypher-rs/create-user
QUERY ENDPOINT
Verb: GET
URL: /cypher-rs/<yourEndpoint>
Examples
GET /cypher-rs/users?name=Andres
--> 200
[
{
"name": "Andres",
"age": 21,
"male": true,
"children": [
"Cypher",
"L.",
"N."
]
}
]
GET /cypher-rs/users?name=NotExists
--> 204
POST JSON-DATA TO ENDPOINT
Verb: POST
URL: /cypher-rs/<yourEndpoint>
Headers:
Content-type: application/json
Body:
<jsonData>
Examples
POST /cypher-rs/users
Content-type: application/json
Body:
{
"name": "Andres"
}
--> 200
[
{
"name": "Andres",
"age": 21,
"male": true,
"children": [
"Cypher",
"L.",
"N."
]
}
]
POST /cypher-rs/users
Content-type: application/json
Body:
{
"name": "NotExists"
}
--> 204
POST /cypher-rs/users
Content-type: application/json
Body:
[
{
"name": "Andres"
},
{
"name": "Peter"
},
{
"name": "NotExists"
}
]
--> 200
[
{
"name": "Andres",
"age": 21,
"male": true,
"children": [
"Cypher",
"L.",
"N."
]
},
{
"name": "Peter",
"age": 32,
"male": true,
"children": [
"Neo4j",
"O.",
"K."
]
},
null
]
POST CSV DATA TO ENDPOINT
Verb: POST
URL: /cypher-rs/<yourEndpoint>
Headers:
Content-type: text/plain
Body:
<csvData>
Examples
POST /cypher-rs/create-user
Content-type: text/plain
Body:
name,age,male\nAndres,21,true
--> 200
{
"nodes_created": 1,
"labels_added": 1,
"properties_set": 3,
"rows": 1
}
POST /cypher-rs/create-user?delim=\t&batch=20000
Content-type: text/plain
Body: name\tage\tmale\nAndres\t21\ttrue
--> 200
{
"nodes_created": 1,
"labels_added": 1,
"properties_set": 3,
"rows": 1
}
DELETE ENDPOINT
Verb: DELETE
URL: /cypher-rs/<yourEndpoint>
Example
DELETE /cypher-rs/users
--> 200
LIST ENDPOINTS
Verb: GET
URL: /cypher-rs
Example
GET /cypher-rs
--> 200 ["users","create-user"]
GET /cypher-rs?full=true
--> 200 {"users": "start n=node:node_auto_index(name={name}) return n",
"create-user": "create (n {name:{name},age:{age},male:{male}})"}
GET ENDPOINT QUERY
Verb: GET
URL: /cypher-rs/<yourEndpoint>/query
Example
GET /cypher-rs/users/query
--> 200 start n=node:node_auto_index(name={name}) return n
GET /cypher-rs/create-users/query
--> 200 create (n {name:{name},age:{age},male:{male}})
Types of results:
single column, single row
[
{
"name": "Andres",
"age": 21,
"male": true,
"children": [
"Cypher",
"L.",
"N."
]
}
]
single column, multiple rows
[
{
"name": "Andres",
"age": 21,
"male": true,
"children": [
"Cypher",
"L.",
"N."
]
},
{
"name": "Peter",
"age": 32,
"male": true,
"children": [
"Neo4j",
"O.",
"K."
]
}
]
multiple columns, single row (column names are keys)
[
{
"user": "Andres",
"friends": [
"Peter",
"Michael"
]
}
]
multiple columns, multiple rows (column names are keys)
[
{
"user": "Andres",
"friends": [
"Peter",
"Michael"
]
},
{
"user": "Michael",
"friends": [
"Peter",
"Andres"
]
},
{
"user": "Peter",
"friends": [
"Andres",
"Michael"
]
}
]
Configuration
Build with mvn clean install dependency:copy-dependencies
Copy files cp target/cypher-rs-2.1-SNAPSHOT.jar target/dependency/opencsv-2.3.jar path/to/server/plugins
Add this line to path/to/server/conf/neo4j-server.properties
org.neo4j.server.thirdparty_jaxrs_classes=org.neo4j.cypher_rs=/cypher-rs
Notes
There is some magic happening with converting query parameters to cypher parameters, as query-parameters are all strings things that look like a number are converted to numbers and collections (aka multiple query parameters) are converted into lists.
Ideas
- all endpoints should be able to generate CSV when supplied with the accept header
- replace Jackson with faster Gson?
- performance tests
Concrete Example for the Movie-Graph in Neo4j-Server
A "movie and cast" endpoint
- parameter:
title - result: a document with the movie title and a collection of cast names
curl -i -XPUT -H content-type:text/plain \
-d'MATCH (movie:Movie {title:{title}})
OPTIONAL MATCH (movie)<-[:ACTED_IN]-(actor)
RETURN {title:movie.title, cast: collect(actor.name)} as movie' \
http://localhost:7474/cypher-rs/movie
// use it
curl http://localhost:7474/cypher-rs/movie?title=The%20Matrix
A "co-actors" endpoint
- parameter:
name - result: a document of the requested actor and actors who co-acted in the same movies, ordered by frequency
curl -i -XPUT -H content-type:text/plain \
-d'MATCH (actor:Person {name:{name}})-[:ACTED_IN*2..2]-(co_actor)
WITH actor.name as name, {name:co_actor.name, count: count(*)} as co_actors
ORDER BY count(*) DESC
RETURN {name:name, co_actors: collect(co_actors)} as result' \
http://localhost:7474/cypher-rs/co-actor
// use it
curl -i http://localhost:7474/cypher-rs/co-actor?name=Keanu%20Reeves
A "create movie only" endpoint
- parameters:
titleandreleased - result: document with the movie's properties
- uses
MERGEas "get-or-create" operation
curl -i -XPUT -H content-type:text/plain \
-d'MERGE (movie:Movie {title:{title}}) ON CREATE SET movie.released={released} RETURN movie' \
http://localhost:7474/cypher-rs/create-movie
// use it
curl -i -XPOST -H content-type:application/json -d'{"title":"Forrest Gump","released":1994}' http://localhost:7474/cypher-rs/create-movie
A "create movie with cast" endpoint
- parameters:
title,releasedfor the movies,actorsfor the actor names - result: document with the movie's properties
- uses
MERGEas "get-or-create" operation for both movie and actors
curl -i -XPUT -H content-type:text/plain \
-d'MERGE (movie:Movie {title:{title}}) ON CREATE SET movie.released={released}
FOREACH (name in {actors} | MERGE (actor:Person {name:name}) MERGE (actor)-[:ACTED_IN]->(movie))
RETURN movie' \
http://localhost:7474/cypher-rs/create-movie2
// use it
curl -i -XPOST -H content-type:application/json -d'{"title":"Forrest Gump","released":1994, "actors":["Tom Hanks","Robin Wright","Gary Sinise"]}' http://localhost:7474/cypher-rs/create-movie2
curl http://localhost:7474/cypher-rs/movie?title=Forrest%20Gump
```