@mantlejs/neo4j
Neo4j graph database adapter for Mantle JS. Provides Neo4jRepository<T> — an abstract GraphRepository<T> base class that maps Mantle entities to Neo4j nodes, with Cypher query generation from QueryParams.where.
Installation
Section titled “Installation”npm install @mantlejs/neo4j neo4j-driverConcepts
Section titled “Concepts”Graph model
Section titled “Graph model”Neo4j stores data as nodes and directed relationships. Neo4jRepository<T> maps one Mantle entity type to one Neo4j node label. Each node stores a UUID id property plus all other entity fields as node properties.
QueryParams.where → Cypher WHERE
Section titled “QueryParams.where → Cypher WHERE”findNodes(params?) translates QueryParams.where to a parameterised Cypher WHERE clause. All standard Mantle operators are supported: equality, $lt/$lte/$gt/$gte, $ne, $in/$nin, $like/$ilike/$notlike, $or, $and.
Relationships and traversal
Section titled “Relationships and traversal”createRelationship creates a directed (a)-[:TYPE]->(b) edge between two nodes of the same label. traverse walks relationships up to a configurable depth and returns the reached nodes.
Transactions
Section titled “Transactions”withTransaction(fn) wraps a callback in a Neo4j write transaction. A transaction-scoped repository instance is passed to the callback so all operations inside share the same transaction.
Quick start
Section titled “Quick start”import { mantle } from "@mantlejs/mantle";import { neo4j, Neo4jRepository } from "@mantlejs/neo4j";
interface Person extends Record<string, unknown> { id: string; name: string; age: number;}
class PersonRepository extends Neo4jRepository<Person> { readonly label = "Person";}
const app = mantle().configure( neo4j({ uri: process.env.NEO4J_URI, auth: { username: "neo4j", password: process.env.NEO4J_PASSWORD! }, }),);
const repo = new PersonRepository(app);
// Create a nodeconst alice = await repo.createNode({ name: "Alice", age: 30 });const bob = await repo.createNode({ name: "Bob", age: 25 });
// Create a relationshipawait repo.createRelationship(alice.id, bob.id, "KNOWS", { since: "2024" });
// Traverseconst aliceFriends = await repo.traverse(alice.id, "KNOWS", 1);
// Filter nodesconst seniors = await repo.findNodes({ where: { age: { $gte: 30 } } });
// Raw Cypher via the adapter-neutral raw() escape hatchconst result = await repo.raw<Person>( "MATCH (n:Person) WHERE n.name STARTS WITH $prefix RETURN n", { prefix: "A" },);neo4j(options)
Section titled “neo4j(options)”Returns a MantlePlugin. Call via app.configure(neo4j(options)).
app.configure( neo4j({ uri: "bolt://localhost:7687", // optional — default bolt://localhost:7687 auth: { username: "neo4j", password: "…" }, // required database: "neo4j", // optional — default "neo4j" }),);Side effects:
- Opens a Neo4j
Driverand stores it atapp.get("neo4j") - Stores the resolved database name at
app.get("neo4j:database")
Neo4jOptions
Section titled “Neo4jOptions”| Field | Type | Default | Description |
|---|---|---|---|
uri |
string |
NEO4J_URI env or bolt://localhost:7687 |
Bolt connection URI |
auth |
{ username: string; password: string } |
— | Neo4j credentials (required) |
database |
string |
NEO4J_DATABASE env or "neo4j" |
Target database name |
Neo4jRepository<T> (abstract class)
Section titled “Neo4jRepository<T> (abstract class)”Implements GraphRepository<T> from @mantlejs/mantle.
abstract class Neo4jRepository<T extends Record<string, unknown>> implements GraphRepository<T> { abstract readonly label: string;}Subclasses must declare label. All GraphRepository<T> methods are provided as concrete implementations.
GraphRepository<T> methods
Section titled “GraphRepository<T> methods”| Method | Cypher pattern |
|---|---|
createNode(data) |
CREATE (n:Label $props) RETURN n |
findNodeById(id) |
MATCH (n:Label {id: $id}) RETURN n |
findNodes(params?) |
MATCH (n:Label) WHERE … RETURN n ORDER BY … SKIP … LIMIT … |
createRelationship(fromId, toId, type, props?) |
MATCH (a:Label {id: $from}), (b:Label {id: $to}) CREATE (a)-[r:TYPE $props]->(b) |
traverse(startId, relation, depth?) |
MATCH (start)-[r:TYPE*1..depth]->(n) RETURN n |
deleteNode(id) |
MATCH (n:Label {id: $id}) DETACH DELETE n — throws NotFound if absent |
raw<R>(query, params?) |
Raw Cypher passthrough (the GraphRepository escape hatch) |
Error mapping
Section titled “Error mapping”The Neo4j driver doesn’t expose a rich exception taxonomy the way DynamoDB or MongoDB do, so
wrapError() is intentionally simple:
| Thrown value | Error thrown |
|---|---|
Already a MantleError (e.g. NotFound from a missing node, or thrown by a hook or custom repository method) |
passed through unchanged |
Any other Error |
GeneralError |
A non-Error throw |
GeneralError |
withTransaction(fn)
Section titled “withTransaction(fn)”await repo.withTransaction(async (txRepo) => { const alice = await txRepo.createNode({ name: "Alice", age: 30 }); await txRepo.createNode({ name: "Bob", age: 25 }); await txRepo.createRelationship(alice.id, "…", "KNOWS");});Instance properties
Section titled “Instance properties”| Property | Type | Default | Description |
|---|---|---|---|
label |
string |
— | Required. Neo4j node label for this repository |
idField |
string |
"id" |
Node property used as the entity identifier |
timestamps |
boolean |
true |
Auto-write createdAt / updatedAt ISO-8601 fields |
QueryParams.where operators
Section titled “QueryParams.where operators”| Mantle operator | Cypher equivalent |
|---|---|
{ field: value } |
n.field = $p |
{ field: null } |
n.field IS NULL |
{ field: [a, b] } |
n.field IN $p |
$gt / $gte / $lt / $lte |
>, >=, <, <= |
$ne: value |
n.field <> $p |
$ne: null |
n.field IS NOT NULL |
$in |
n.field IN $p |
$nin |
NOT n.field IN $p |
$like: '%x%' |
CONTAINS / STARTS WITH / ENDS WITH |
$ilike |
toLower(n.field) CONTAINS $p |
$notlike |
NOT (n.field CONTAINS $p) |
$or |
(a OR b OR …) |
$and |
(a AND b AND …) |
$contains and dot-path nested fields ("metadata.owner.name") are not supported —
architectural, not a missing translator feature: Neo4j node properties can only hold flat
scalars/scalar-arrays, never nested objects, so there’s no nested structure to address or check
containment against in the first place. describe().capabilities.nestedPaths reports false
accordingly.
toNeo4jWhere(where, alias?)
Section titled “toNeo4jWhere(where, alias?)”Converts a Mantle QueryParams.where clause into a parameterised Cypher WHERE expression and
parameter map. Used internally by findNodes(); exported for writing raw Cypher in a custom
repository method. The node alias defaults to "n".
import { toNeo4jWhere } from "@mantlejs/neo4j";
const { clause, params } = toNeo4jWhere({ age: { $gte: 30 } });// clause: "n.age >= $_w_0"// params: { _w_0: 30 }import type { Neo4jOptions, WhereClause, WhereResult } from "@mantlejs/neo4j";| Type | Description |
|---|---|
Neo4jOptions |
Options passed to neo4j() plugin |
WhereClause |
QueryParams.where clause type |
WhereResult |
Return value of toNeo4jWhere — { clause, params } |
Development
Section titled “Development”npx nx build neo4j # compilenpx nx test neo4j # run testsnpx nx lint neo4j # lintPublishing
Section titled “Publishing”Build before publishing:
npx nx build neo4jFirst publish (scoped packages require --access public):
cd packages/neo4jnpm publish --access publicSubsequent releases — bump version in packages/neo4j/package.json, then:
cd packages/neo4jnpm publish