Skip to content

@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.


Terminal window
npm install @mantlejs/neo4j neo4j-driver

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.

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.

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.

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.


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 node
const alice = await repo.createNode({ name: "Alice", age: 30 });
const bob = await repo.createNode({ name: "Bob", age: 25 });
// Create a relationship
await repo.createRelationship(alice.id, bob.id, "KNOWS", { since: "2024" });
// Traverse
const aliceFriends = await repo.traverse(alice.id, "KNOWS", 1);
// Filter nodes
const seniors = await repo.findNodes({ where: { age: { $gte: 30 } } });
// Raw Cypher via the adapter-neutral raw() escape hatch
const result = await repo.raw<Person>(
"MATCH (n:Person) WHERE n.name STARTS WITH $prefix RETURN n",
{ prefix: "A" },
);

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 Driver and stores it at app.get("neo4j")
  • Stores the resolved database name at app.get("neo4j:database")
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

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.

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)

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
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");
});
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
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.


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 }

Terminal window
npx nx build neo4j # compile
npx nx test neo4j # run tests
npx nx lint neo4j # lint

Build before publishing:

Terminal window
npx nx build neo4j

First publish (scoped packages require --access public):

Terminal window
cd packages/neo4j
npm publish --access public

Subsequent releases — bump version in packages/neo4j/package.json, then:

Terminal window
cd packages/neo4j
npm publish