domvas

domvas

Hello,
I’m pleased and a bit proud to present you seraph a library to define schema and query Neo4j database.

Again?
Yes again…
Since I’ve started Elixir, I’ve seen here and there attempts to develop a library to use Neo4j nicely, and even an adapter to Ecto… released by me.
But nothing was satisfying enough and ecto_neo4j was, and is still, a big disappointment. ecto is relational database oriented, and there is more or less only one entity is this case: table. But when it comes to graph, you have two entities: node and relationship. Then all I’ve done with ecto_neo4j was to force this two entities and graph concepts into a relational shape. Code became awful because of hacks everywhere, usage was limited and so restricted that you can’t model you graph database as you wish.
Then I drop this project and start seraph.

seraph is heavily inspired by ecto because using ecto to work with database is joy. And I tried to bring this joy to Neo4j graph database.
It’s still the beginning, a LOT is missing but it’s a first (good?) step for a nice library to work with Neo4j with schema, nice query api, etc.

Hope you’ll enjoy it.

Testers and contributors

Because of the amount of work, help would be appreciated, so if you want to test, to add some features, write some docs, please do!

So, quickly, what you can do if you don’t want to read the docs.

Schema

a node schema is like this:

defmodule GraphApp.Blog.User do
  use Seraph.Schema.Node
  import Seraph.Changeset

  alias GraphApp.Blog.User
  alias GraphApp.Blog.Relationship
  alias GraphApp.Blog.Relationship.NoProperties

  node "User" do
    property :firstName, :string
    property :lastName, :string
    property :email, :string
    
    # You can define two relatinship with same type but pointing to different node
    # without problem
    outgoing_relationship("WROTE", GraphApp.Blog.Post, :posts, Relationship.Wrote,
      cardinality: :many
    )

    outgoing_relationship(
      "WROTE",
      GraphApp.Blog.Comment,
      :comments,
      NoProperties.UserToComment.Wrote,
      cardinality: :many
    )
    
    # A relationship can ends to the same node it starts 
    outgoing_relationship(
      "FOLLOWS",
      GraphApp.Blog.User,
      :followed,
      NoProperties.UserToUser.Follows,
      cardinality: :many
    )
  end

# classic changeset
  def changeset(%User{} = user, params \\ %{}) do
    user
    |> cast(params, [:firstName, :lastName, :email])
    |> validate_required([:firstName, :lastName, :email])
  end
end

a relationship schema:

defmodule GraphApp.Blog.Relationship.Wrote do
  use Seraph.Schema.Relationship

  @cardinality [outgoing: :one, incoming: :many]

  relationship "WROTE" do
    start_node GraphApp.Blog.User
    end_node GraphApp.Blog.Post

    property :when, :utc_datetime
  end
end

Atomic operation with Repo.*

Each entity has its Repo.* functions:

  • get
  • get_by
  • set
  • create
    …

Query DSL

And you can write nice queries with the query api.

import Seraph.Query

query = match [{u, User}],
  where: [u.firstName == "John"],
  return: [u]

GraphApp.Repo.all(query)

----

match([
    {u, GraphApp.Blog.User, %{firstName: "Jim"}},
    {u2, GraphApp.Blog.User, %{firstName: "Jane"}},
    [{u}, [rel, GraphApp.Blog.Relationship.NoProperties.UserToUser.Follows], {u2}]
]) 
|> delete([rel]) 
|> GraphApp.Repo.execute()

Showing Posts 1 to 7

josefrichter

josefrichter

oh god I wish I had a nice project to use with neo4j. it must be fun using it!

swelham

swelham

Great work! I have been wanting this to exist for some time but never been able to get around to building it myself. I have some initial thoughts listed below that came to mind whilst looking through the guides and I will definitely be looking to contribute where I can.

  • Support a non global config. Allow the config to be specified per instance of Repo in the supervision tree. This provides more flexibility and if the user wants a global config they can still do it.
  • Default datetimes to utc_datetime_usec. I believe this plays better with Elixir’s datetime module (no need for Datetime.truncate everywhere) and I seem to remember reading that the ecto core team wish they did this but now can’t due to backwards compatibility.
  • Support having snake case schema fields. I see the rational in enforcing camel case but as it’s not idiomatic Elixir it will end up forcing some inconsistency in the user’s application. This seems more like a db implementation detail and I think it should be possible to support snake case fields and then convert to camel case in the queries when dealing with the db.

But honestly it’s looking really good so far!

domvas

domvas OP

Thank for the kind words! It makes me think that the package is useful :sweat_smile:

What is the goal? Having the ability to connect / disconnect from database at runtime?
In fact, this update is quite trivial and can (will) be added.
Keep in mind that multi-tenancy is already supported and that in Neo4j, you can manage multiple databases on the same server (Database administration - Operations Manual)

Noted.
I haven’t implement the timestamps macro so it will be with utc_datetime_usec.
In fact, the date format in Neo4j is converted to datetime with microseconds in Elixir so I already get bitten when I used utc_datetime for the example :slight_smile:

I think you’re not going to be the last to have remarks on this point.
In any case, there’s a tradeoff:

  • CamelCase enforcing → atom with uppercase are not idiomatic (except for modules…) but valid.
  • snake_cased + conversion → you have to switch when you are writing query with seraph or writing query in pure cypher and this can be subject to errors
  • snake_cased without conversion → it’s not what’s recommended for Neo4j
    One in all, the best thing I can do is to implement all 3, defaulting to CamelCased and add config options to let each user decide what he prefers
swelham

swelham

Yeah, it’s definitely useful :smiley:

The goal is more about flexibility for the user and allowing them to decide how to configure things. Changing the connection info at runtime is a nice benefit (maybe an edge case) but would allow specifying the connection details at app boot time instead of compile time.

Is this a naming convention in neo4j or is there some technical benefit for doing this? Though I agree it’s best to stick to conventions where possible.

For handwritten queries using seraph maybe having a macro that supports handling field names might work out. Ecto has a macro for dealing with dynamic fields (field/2) perhaps something similar to that or maybe something like fragments which allows a field value to be passed in. Honestly, I haven’t given it much thought at this point but would like to dive into the code and check it out more.

domvas

domvas OP

For labels and types, it’s Neo4j’s recommendation and can be seen as a convention.
For properties, it’s based on what I’ve seen during my trainings and many talks / tutorials. So a majority of users has adopted this syntax but it’s not really a convention, then letting the end-user decide could be nice.
Having a field/2-like macro can be tricky to implement but not impossible then it will be implemented. It is in fact one the many features that still missing (like fragments…)

pmangalakader

pmangalakader

Trying it for some fun project, easy to use it like ecto seems excellent. Would love to see transaction support in the future releases.

alaadahmed

alaadahmed

wow really nice work you have done @domvas, really appreciate your work, I am working on some big project that I intend to use neo4j for it. I am reading your guides and I am fascinated. I hope you still maintain this library and keep it up to date.

— All posts loaded —

Where Next? Top

Trending in Announcing Top

type1fool
WebAuthnLiveComponent WebAuthnComponents See this post about renaming the package. Passwordless authentication for Phoenix LiveView app...
New
GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
woylie
I released Doggo, a collection of unstyled Phoenix components. https://github.com/woylie/doggo Features Unstyled Phoenix components....
New
ahamez
Hi everyone, I’ve been working on this protobuf library for 3 years. We use it in the company I work for, EasyMile, to communicate with ...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
kip
I’ll shortly be launching Text, a nascent text analysis library. Current functionality In this early version (not ready for prime time) ...
New
kip
Following on from my CLDR lbraries I started work on Unicode transforms. But like everything related to CLDR there is a lot of yak-shavin...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
budgie
I love Elixir. It’s one of 2 programming languages I’ve ever fallen in love with. But I don’t use it anymore. Serverless was the promis...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews