Erlang client library for Neo4J
This is a lightweight wrapper for Neo4j REST API.
Current versions: 0.2.1 and 0.3
If you want to use Basic Auth, use v0.3. Otherwise use 0.2.1. Read on for more info.
v0.3
Thanks to work by @dethtron5000, neo4j-erlang now supports Basic Auth. This means:
- The overall API is not changed
- There are breaking changes in the way you work with the API.
Changes
All API calls now require an additional parameter that contains request options. The options may contain the following parameters for Basic Auth:
{user, binary()}{password, binary}
Sample session for v0.3 (read below for v0.2.1)
### Sample session
Options = [ {base_uri, <"http://localhost:7474/db/data/">}
, {user, <<"user">>}
, {password, <<"password">>}
],Neo = neo4j:connect(Options),
StartNode = neo4j:get_node(Neo, 101, Options), EndNode = neo4j:create_node(Neo, {[{<<"prop1">>, <<"key1">>}]}, Options),
Relationship1 = neo4:create_relationship(StartNode, EndNode, <<"KNOWS">>, Options), Relationship2 = neo4:create_relationship(StartNode, EndNode, <<"KNOWS">>, {[{<<"prop2">>, <<"value2">>}]}, Options),
ok = neo4j:delete_relationship(Relationship1, Options).
Except for the new required parameter the rest of this documentation remains unchanged for v0.3.
#### v0.2.1
All the information below is unchanged for version 0.2.1.
*Breaking changes from [0.1](https://github.com/dmitriid/neo4j-erlang/tree/0.1)*
* [jsx](https://github.com/iskra/jsx) has been replaced by [jiffy](https://github.com/davisp/jiffy).
This means that you now absolutely have to use [EEP0018](http://www.erlang.org/eeps/eep-0018.html) (refer to [jiffy documentation](https://github.com/davisp/jiffy) for a more concise description.
This readme and all comments throughout the code have been updated to reflect this change.
## What?
- Implements all of Neo4J 2.0.0's REST API as referenced [here](http://docs.neo4j.org/chunked/stable/rest-api.html) with one caveat:
- Does *not* implement [streaming API](http://docs.neo4j.org/chunked/stable/rest-api-streaming.html)
- Uses [jiffy](https://github.com/davisp/jiffy) for JSON
- Uses [hackney](https://github.com/benoitc/hackney) for http queries
- Does *not* support HTTPS (yet?)
## How?
### Sample session
Neo = neo4j:connect([{base_uri, <"http://localhost:7474/db/data/">}]),
StartNode = neo4j:get_node(Neo, 101), EndNode = neo4j:create_node(Neo, {[{<<"prop1">>, <<"key1">>}]}),
Relationship1 = neo4:create_relationship(StartNode, EndNode, <<"KNOWS">>), Relationship2 = neo4:create_relationship(StartNode, EndNode, <<"KNOWS">>, {[{<<"prop2">>, <<"value2">>}]}),
ok = neo4j:delete_relationship(Relationship1).
Read on for more details, or refer to comments in [code](blob/master/src/neo4j.erl) or to the [test suite](blob/master/test/neo4j_SUITE.erl).
### Details
The wrapper follows [Neo4j's REST API](http://docs.neo4j.org/chunked/stable/rest-api.html) as close as possible. See code comments for direct links to each method/feature implemented. For example:
%% %% http://docs.neo4j.org/chunked/stable/rest-api-nodes.html#rest-api-create-node-with-properties %% -spec createnode(neo4j_root(), proplists:proplist()) -> neo4j_node() | {error, term()}. create_node(Neo, Props) -> {, URI} = find(<<"node">>, 1, Neo), Payload = jiffy:encode(Props), create(URI, Payload).
That link will tell you exactly what's going on and what you should expect.
### Errors
There are two types of errors the wrapper returns:
- `{error, atom()}` — some generic errors like `{error, not_found}` (which you can use for paged traversals):
neo4j:get_node(Neo, 10000). {error,not_found}
- `{error, {Status::integer(), URI::binary(), Error::proplists:proplist()}` — errors returned by Neo4j. Example of such an error (using [unique indexing](http://docs.neo4j.org/chunked/stable/rest-api-unique-indexes.html#rest-api-create-a-unique-node-or-return-fail-create)):
neo4j:unique_create_node(Neo, [{<<"prop">>, <<"val">>}], <<"index">>, <<"key">>, <<"value">>, <<"create_or_fail">>). [{<<"self">>, <"http://localhost:7474/db/data/index/node/index/key/value/191">}, {<<"extensions">>,[]}, {<<"paged_traverse">>, <"http://localhost:7474/db/data/node/191/paged/traverse/{returnType}{?pageSize,leaseTime}">}, {<<"labels">>, ...
neo4j:unique_create_node(Neo, [{<<"prop">>, <<"val">>}], <<"index">>, <<"key">>, <<"value">>, <<"create_or_fail">>).
error,{409,
<<"http://localhost:7474/db/data/index/node/index?uniqueness=create_or_fail">>,
[{<<"extensions">>,[]},
{<<"paged_traverse">>,...
As [expected](http://docs.neo4j.org/chunked/stable/rest-api-unique-indexes.html#rest-api-create-a-unique-node-or-return-fail-fail), Neo4j returned a [HTTP 409 Conflict](https://github.com/for-GET/know-your-http-well/blob/master/status-codes.md) code.
## Assumptions
### Location header
If an operation returns a `HTTP 201 Created` with a `Location` header, the wrapper will prepend a `{<<"self">>, Location}` to the proplist returned. Be wary of this especially when using paged traversals.
Node = neo4j:get_node(Neo, 101). Body = {[ {<<"order">>, <<"breadth_first">>}
, {<<"uniqueness">>, <<"none">>}
, {<<"return_filter">>, {[ {<<"language">>, <<"builtin">>}
, {<<"name">>, <<"all">>}
]}
}
]}.PT = neo4j:paged_traverse(Node, Body). [ %% <<"self">> is prepended {<<"self">>, <"http://localhost:7474/db/data/node/101/paged/traverse/node/2e23bfca61144b0f91b446fb6be562b6">}, %% actual data {[{<<"labels">>, ...
### No JSON, just EEP0018 structures
Even for complex queries (such as [Cypher queries](http://docs.neo4j.org/chunked/stable/rest-api-cypher.html) or [transactions](http://docs.neo4j.org/chunked/stable/rest-api-transactional.html)) you never send in raw JSON, only proplists representing your objects:
See the example in "Binaries" below
### Binaries
All string data and all URL parameters sent to Neo4J are assumed to be binaries.
As an example, let's create a [paged traverser](http://docs.neo4j.org/chunked/milestone/rest-api-traverse.html#rest-api-creating-a-paged-traverser)
Neo = neo4j:connect([{base_uri, BaseUri}]), Node = neo4j:get_node(Neo, 101), Body = {[ {<<"order">>, <<"breadth_first">>}
, {<<"uniqueness">>, <<"none">>}
, {<<"return_filter">>, {[ {<<"language">>, <<"builtin">>}
, {<<"name">>, <<"all">>}
]}
}
]},PT = neo4j:paged_traverse(Node, Body, [ {<<"returnType">>, ReturnType}
, {<<"leaseTime">>, LeaseTime}
, {<<"pageSize">>, PageSize}
]).
### Does *not* do stuff for you
- Will not urlencode your parameters (as required [here](http://docs.neo4j.org/chunked/stable/rest-api-indexes.html#rest-api-find-node-by-query)). You'll have to do it manually
- Will not assume that an integer/url references a valid node/relationship. You'll have to retrieve nodes/relationships yourself. Typical workflow looks something like this:
Neo = neo4j:connect([{base_uri, <"http://localhost:7474/db/data/">}]),
%% will not work: neo4j:get_node_properties(101).
%% will not work: neo4j:get_node_properties(<"http://localhost:7474/db/data/node/101">).
%% correct way: Node = neo4j:get_node(N, 101), neo4j:get_node_properties(Node).
%% also correct: Node2 = neo4j:get_node(N, <"http://localhost:7474/db/data/node/101">), neo4j:get_node_properties(Node2).
## Contributing
Yes, please! :) If you have ideas, suggestions, pull requests or issues, do not hesitate to send them my way