Awesome
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
```erlang
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 = neo4j:create_relationship(StartNode, EndNode, <<"KNOWS">>, Options),
Relationship2 = neo4j: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.
Thanks to:
- @sdebnath and @zsoci for Diyalizing
- @ppikula for fixing Hackney
- @msmilom for adding rebar configuration
Breaking changes from 0.1
- jsx has been replaced by jiffy. This means that you now absolutely have to use EEP0018 (refer to jiffy documentation 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 with one caveat:
- Does not implement streaming API
- Uses jiffy for JSON
- Uses 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 = neo4j:create_relationship(StartNode, EndNode, <<"KNOWS">>),
Relationship2 = neo4j:create_relationship(StartNode, EndNode, <<"KNOWS">>, {[{<<"prop2">>, <<"value2">>}]}),
ok = neo4j:delete_relationship(Relationship1).
Read on for more details, or refer to comments in code or to the test suite.
Details
The wrapper follows Neo4j's REST API 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 create_node(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):
> 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, Neo4j returned a HTTP 409 Conflict 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 or transactions) 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
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). 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