# Erlang client library for Neo4J
This is a lightweight wrapper for [Neo4j REST API](http://docs.neo4j.org/chunked/stable/rest-api.html).
### 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 = 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
```erlang
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:
```erlang
%%
%% 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):
```erlang
> 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)):
```erlang
> 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.
```erlang
> 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)
```erlang
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:
```erlang
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