Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Neo4J Laravel Eloquent Laravel Package

global-innovation/neo4j-laravel-eloquent

Brings native Neo4j graph database support to Laravel's Eloquent ORM. Define nodes and relationships as Eloquent models, run Cypher queries through a fluent query builder, traverse graphs via relationships, and manage schemas with Laravel-native syntax, migrations, and conventions.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and wire up the connection. Pull the package in via Composer, then point it at a running Neo4j instance (Bolt protocol). Typical env/config surface is a neo4j connection block with host, port, username, password, and database name — keep it out of source control and mirror it in config/neo4j.php (publish the package config if the service provider exposes one).
composer require global-innovation/neo4j-laravel-eloquent
NEO4J_HOST=127.0.0.1
NEO4J_PORT=7687
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=secret
NEO4J_DATABASE=neo4j
  1. Look first at the package's base model and its relationship methods. The whole day-to-day surface is: a Neo4jModel base class, a label property, $fillable, CRUD statics (create, find, all), and relationship helpers (hasOne, hasMany, belongsTo, belongsToMany). Read those signatures before defining domain models; they are the contract you'll live inside.

  2. Define one node model and prove the loop.

namespace App\Graph;

use GlobalInnovation\Neo4jLaravelEloquent\Model\Neo4jModel;

class Person extends Neo4jModel
{
    protected $label = 'Person';   // confirm exact property name against your installed version
    protected $fillable = ['name', 'email'];
}
  1. First realistic use case: a "follows" graph. Create two people, connect them, and traverse back out. This exercises node creation, relationship creation, and relationship reads in one sitting.
$alice = Person::create(['name' => 'Alice']);
$bob   = Person::create(['name' => 'Bob']);

$alice->follows()->save($bob);   // or attach/create, depending on the relationship type

$alice->follows;                 // related People
$bob->followers;                 // inverse relationship, if defined

If that loop works, you have enough to build on. If it doesn't, stop and fix connection/config before writing more models — most early failures are connection or label mismatches, not ORM logic.

Implementation Patterns

Model-per-label, with relationships as methods. Keep one class per node label and express edges as methods that return the package's relationship objects. The relationship type is a string (the Cypher edge name) and should be treated as part of your schema.

class Person extends Neo4jModel
{
    protected $label = 'Person';
    protected $fillable = ['name', 'email'];

    public function follows()
    {
        return $this->hasMany(Person::class, 'FOLLOWS');
    }

    public function followers()
    {
        return $this->belongsToMany(Person::class, 'FOLLOWS'); // inverse direction
    }

    public function company()
    {
        return $this->hasOne(Company::class, 'WORKS_AT');
    }
}

CRUD and simple querying. Use the Eloquent-style statics for straightforward node work, and chain where for property filters. Anything that involves multi-hop traversal, aggregation, or path finding should drop to raw Cypher.

$people = Person::where('name', 'Alice')->get();
$person = Person::find($id);
$person->update(['email' => '[email protected]']);
$person->delete();

Hybrid MySQL + Neo4j architecture. The most common production pattern with a package like this is: relational data stays in MySQL, the graph lives in Neo4j, and a thin sync layer mirrors the relevant fields. Do the mirroring in model observers or queued events, not in controllers.

class UserObserver
{
    public function created(User $user): void
    {
        Person::create([
            'uuid'  => $user->uuid,
            'name'  => $user->name,
            'email' => $user->email,
        ]);
    }
}

Store a stable external key (UUID) on the graph node and never rely on Neo4j's internal id for cross-system references.

Repository/service layer for graph operations. Keep traversal and recommendation logic in a dedicated service, not in controllers or models. Models stay declarative; services own the Cypher and the transaction boundaries.

class RecommendationService
{
    public function friendsOfFriends(Person $person): array
    {
        // raw Cypher via the underlying client is usually clearer here
        return $this->client->run(
            'MATCH (p:Person {uuid: $uuid})-[:FOLLOWS]->()-[:FOLLOWS]->(rec)
             WHERE rec.uuid <> $uuid
             RETURN DISTINCT rec.name AS name LIMIT 25',
            ['uuid' => $person->uuid]
        )->toArray();
    }
}

Bulk writes go through UNWIND, not save(). Looping create() is fine for seeds and tests, but for imports or sync jobs, batch into a single Cypher statement. The ORM's per-node round-trip cost will dominate otherwise.

UNWIND $rows AS row
MERGE (p:Person {uuid: row.uuid})
SET p.name = row.name, p.email = row.email

Testing pattern. Point the test suite at a dedicated Neo4j database, and clean up per test with MATCH (n) DETACH DELETE n or by using unique labels per test class. Neo4j has no migrations to roll back, so cleanup is entirely your responsibility.

Gotchas and Tips

  • There are no migrations. Labels, relationship types, indexes, and constraints are schema you manage manually with Cypher. Put them in a setup script or an artisan command and run it on deploy: CREATE CONSTRAINT person_uuid IF NOT EXISTS FOR (p:Person) REQUIRE p.uuid IS UNIQUE.
  • Neo4j internal ids are not stable identifiers. They can be reused after deletion and shouldn't leak into URLs or foreign keys. Use a UUID property and expose that.
  • Not every Eloquent feature is implemented. $casts, timestamps, soft deletes, eager loading, and global scopes may behave differently or be absent. Verify each against the package source for your pinned version; don't assume parity with SQL Eloquent.
  • Timestamps. If $timestamps isn't honored automatically, set created_at/updated_at in model events or in the write path. The same applies to any "auto-updating" column you'd normally take for granted.
  • Transactions don't span databases. You cannot atomically write MySQL and Neo4j. Design for eventual consistency: write to the source of truth first, queue the graph sync, and make the sync idempotent (MERGE rather than CREATE).
  • Direction and cardinality are schema. FOLLOWS and FOLLOWED_BY are different edge types; decide the canonical direction, define inverses explicitly, and document it. Getting this wrong produces relationships that "disappear" when queried from the other side.
  • Property types are constrained. Neo4j properties are primitives, arrays of primitives, or null. Nested objects and PHP objects won't map cleanly — cast/encode before writing, and decode on read.
  • N+1 traversal is real. Iterating a collection and touching a relationship per item issues a query per item. For anything beyond trivial sizes, write one Cypher query that returns the shape you need, or eagerly load if the package supports it.
  • Debug the Cypher, not just the model. Enable Neo4j query logging (or log from the client), and reproduce failing queries in Neo4j Browser with EXPLAIN/PROFILE. Most "model doesn't work" bugs are actually "generated Cypher doesn't match the data."
  • Package maturity is a factor. Small star count, a single maintainer, and a 2024 release mean you should pin the version in composer.json, test the upgrade path yourself, and be prepared to read the source. Keep an abstraction boundary (your repository layer) so you can swap the ORM without rewriting domain logic.
  • Extension points to lean on. The underlying Neo4j PHP client (for raw Cypher and transactions), the package service provider (for binding a configured client), a custom base model (for shared UUID/timestamp behavior), and Laravel model events/observers (for MySQL→graph mirroring). Those four give you most of the leverage without forking the package.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource