Harnessing the Power of Graph Databases with Django and Neo4j

Aditya Bhosle
Simform Engineering
6 min readOct 31, 2023

Explore the seamless integration of Neomodel, an OGM for the Neo4j Graph database, with Django.

Imagine a web application where data doesn’t reside in rigid tables and rows but flows naturally from one connected node to another, forming complex, life-like relationships. We’ll delve into the dynamic synergy of Django and Neo4j, where the strengths of a relational web framework combine with the merits of a graph database, opening up new possibilities for smarter, relationship-driven applications.

Let’s start with the basics!

Understanding the Basics of Neo4j

Neo4j is a reliable graph-based database that excels at handling complex data with intricate linkages. Unlike traditional relational databases that use tables and rows, Neo4j centers around nodes (entities and data points) and relationships (how entities interact). It’s an ideal choice for representing complex connections in real-world scenarios like recommendation engines, social connections, and knowledge graphs. Neo4j is also fully ACID compliant.

Illustration of Nodes and Relationships

Illustration of Nodes and Relationships

In Neo4j, a node has properties (key-value pairs) and can have multiple labels assigned to it. Relationships describe connections between a source node and a target node and always have a direction. For example, from the above fig.

  • Node with Label “Person” has properties: “name” and “born.”
  • Node with Label “Movie” has properties: “Title” and “released.”
  • The “ACTED_IN” and “DIRECTED” are relationship entities that connect different nodes.
  • Relationships can also have properties like “roles.”

What is Cypher & How is it Different from SQL?

Cypher is Neo4j’s declarative graph query language, created as an equivalent to SQL for graph databases. It allows users to focus on what to retrieve from a graph rather than how to retrieve it.

The base syntax is : (nodes)-[:CONNECT_TO]→(other nodes)

Here’s a basic comparison:

# Cypher Example

MATCH (movie:Movie)
WHERE movie.rating > 7
RETURN movie.title

# SQL Example

SELECT movie.name
FROM movie
WHERE movie.rating

You can install Neo4j Browser or Desktop app for a Graphical User Interface to perform Cypher queries and visualize nodes and relationships.

What is Neomodel?

Neomodel is a Python Object-Graph Mapping (OGM) library designed for working with Neo4j. It provides an object-oriented way to interact with Neo4j, mapping Python classes to nodes and relationships in the graph.

Neo4j and Django’s ORM are typically used for different types of databases (graph vs. relational), and they don’t share the same migration system. If you’re working with a Neo4j graph database using Neomodel, you don’t create migrations in the same way you do with Django’s ORM.

Instead, you define your graph models in a manner similar to Django’s models.py and interact with Neo4j using Neomodel’s API.

Creating a Database in Neo4j

You can create a Neo4j database either:

  • locally with Neo4j Desktop/Browser app, or
  • use Neo4j auraDB, a fully-managed cloud service.

A fresh installation of Neo4j includes two databases: system and neo4j (default one)

Configuration with Django

To integrate Neomodel with Django, start by installing it from pypi :

pip install neomodel

In your settings.py, configure the database URL as needed, keeping or removing any native relational database in Django’s configuration based on your project’s requirements. I suggest keeping RDBMS to collect logs for Neo4j’s operations. Here’s an example configuration:

from neomodel import config

#bolt://username:password@localhost:7687/db_name
config.DATABASE_URL = 'bolt://neo4j:neo4j@localhost:7687/mydb'

Now, you can define your Nodes and Relationships in your models.py file.

from neomodel import (config, StructuredNode, StringProperty, IntegerProperty,
UniqueIdProperty, RelationshipTo)


class Country(StructuredNode):
code = StringProperty(unique_index=True, required=True)

class City(StructuredNode):
name = StringProperty(required=True)
country = RelationshipTo(Country, 'FROM_COUNTRY')

class Person(StructuredNode):
uid = UniqueIdProperty()
name = StringProperty(unique_index=True)
age = IntegerProperty(index=True, default=0)

# traverse outgoing IS_FROM relations, inflate to Country objects
country = RelationshipTo(Country, 'IS_FROM')

# traverse outgoing LIVES_IN relations, inflate to City objects
city = RelationshipTo(City, 'LIVES_IN')

Creating instances of these models (nodes) is straightforward and can be added to your views following standard practices:

# Creating instanse of Node Country with Properity Code
new_country = Country(code="India")
new_country.save()

# other operations
new_country.delete() # delete Node
new_country.refresh() # reload properties from the database
new_country.element_id # neo4j internal element id

Retrieving Data

You can retrieve using the .nodes class property. For example:

# Return all nodes
all_country_nodes = Country.nodes.all()

# Returns Country by Country.code=='India' or raises neomodel.DoesNotExist if no match
country_node = Country.nodes.get(code='India')

# Will return None unless "USA" exists
country_node = Country.nodes.get_or_none(code='USA')

# Will return the first Country node with the code USA. This raises neomodel.DoesNotExist if there's no match.
country_node = Country.nodes.first(name='USA')

# Will return the first Country node with the name USA or None if there's no match
country_node = Country.nodes.first_or_none(name='USA')

# Return set of nodes
people = Person.nodes.filter(age__gt=3)

Working with Relationships

Working with relationships in Neo4j involves adding, checking, disconnecting, or replacing relations between nodes.

connect: to add relation b/w 2 nodes
is_connected: check if a relation exists on 2 nodes
disconnect: remove relation b/w 2 nodes
replace: change relationship b/w 2 nodes

Here’s an example:

uk = Country(code='UK').save()
john = Person.nodes.get(name='john')

# Add Relationship
john.country.connect(uk)

# Check Relationship
if john.country.is_connected(uk):
print("John is from UK")

# Remove John's country relationship with UK
john.country.disconnect(uk)

# Replace John's country relationship with a new one
john.country.replace(usa)

To retrieve additional relations with a single call, use fetch_relations :

results = Person.nodes.all().fetch_relations('country')
for result in results:
print(result[0]) # Person
print(result[1]) # associated Country

Relationships & Cardinality

You can establish undirected relationships between entities using the Relationship class. This requires the class of the connected entity as well as the type of the relationship :

class Person(StructuredNode):
friends = Relationship('Person', 'FRIEND')

Remember to declare cardinality constraints on both sides of the relationship definition. For example:

class Person(StructuredNode):
car = RelationshipTo('Car', 'OWNS', cardinality=One)

class Car(StructuredNode):
owner = RelationshipFrom('Person', 'OWNS', cardinality=One)

Transactions in Neo4j

Transactions in Neomodel can be used via a context manager or as a function decorator. Here are a few examples:

from neomodel import db

# Option 1 : via context manager
with db.transaction:
Person(name='Bob').save()

# Option 2 : via fucntion decorator
@db.transaction
def update_user_name(uid, name):
user = Person.nodes.filter(uid=uid)[0]
user.name = name
user.save()

Transactions are local to the thread, so when using task schedulers like Celery, it’s advised to wrap each task within a transaction.

By default, starting a transaction without explicitly specifying its type results in a WRITE transaction. You can explicitly also mention the type of transaction to perform. i.e with db.read_transaction or with db.write_transaction

In a nutshell

Neomodel provides an excellent gateway to Neo4j’s graph database world, operating similarly to Django’s ORM. Its intuitive model definitions, relationship management, and expressive querying make it incredibly easy for Django developers to transition to Neo4j. Its user-friendly interface and familiar functionality enable developers to seamlessly adopt graph databases, leading to innovative, relationship-centric applications.

Whether to choose a relational or graph database should depend on the specific characteristics and data structure of your project. Trust your instincts, but let Neomodel guide you toward a graph-based data solution.

References

Follow the Simform Engineering for more technology insights!

--

--