neo4j-driver-js

Neo4j JavaScript Driver

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "neo4j-driver-js" with this command: npx skills add michaelkeevildown/claude-agents-skills/michaelkeevildown-claude-agents-skills-neo4j-driver-js

Neo4j JavaScript Driver

When to Use

Use this skill when working with the neo4j-driver package in JavaScript/TypeScript. Covers driver initialization, session management, the impersonation security model, transaction functions with retries, Neo4j type handling (Integer, DateTime, Node, Relationship), and mapping driver results to UI-consumable shapes.

  1. Installation

npm install neo4j-driver

  1. Driver Initialization

Create a single driver instance for the application lifetime. The driver manages a connection pool internally.

import neo4j, { Driver } from "neo4j-driver";

let driver: Driver | null = null;

export const initDriver = ( uri: string, user: string, password: string, ): Driver => { driver = neo4j.driver(uri, neo4j.auth.basic(user, password), { maxConnectionPoolSize: 50, connectionAcquisitionTimeout: 60000, // 60s maxTransactionRetryTime: 30000, // 30s }); return driver; };

export const getDriver = (): Driver => { if (!driver) throw new Error("Driver not initialized. Call initDriver() first."); return driver; };

export const closeDriver = async (): Promise<void> => { if (driver) { await driver.close(); driver = null; } };

Environment Configuration

export const NEO4J_CONFIG = { uri: import.meta.env.VITE_NEO4J_URI || "neo4j://localhost:7687", user: import.meta.env.VITE_NEO4J_USER || "neo4j", password: import.meta.env.VITE_NEO4J_PASSWORD || "password", database: import.meta.env.VITE_NEO4J_DATABASE || "neo4j", };

Verify Connectivity

const driver = initDriver(config.uri, config.user, config.password); await driver.verifyConnectivity(); console.log("Connected to Neo4j");

  1. Session Management

Sessions are lightweight and should be created per unit of work, then closed.

import { Session } from "neo4j-driver";

export const getSession = (mode: "READ" | "WRITE" = "READ"): Session => { return getDriver().session({ database: NEO4J_CONFIG.database, defaultAccessMode: mode === "READ" ? neo4j.session.READ : neo4j.session.WRITE, }); };

Session Helper (Auto-Close)

export const withSession = async <T>( fn: (session: Session) => Promise<T>, mode: "READ" | "WRITE" = "READ", ): Promise<T> => { const session = getSession(mode); try { return await fn(session); } finally { await session.close(); } };

  1. Impersonation (Two-Tier Security)

The application connects as a service account and impersonates the actual user. Neo4j RBAC enforces data-level permissions for the impersonated user.

export const getImpersonatedSession = (userEmail: string): Session => { return getDriver().session({ database: NEO4J_CONFIG.database, impersonatedUser: userEmail, // Executes as this user's Neo4j RBAC role }); };

export const withImpersonation = async <T>( userEmail: string, fn: (session: Session) => Promise<T>, ): Promise<T> => { const session = getImpersonatedSession(userEmail); try { return await fn(session); } finally { await session.close(); } };

Usage

// User only sees data their Neo4j role permits const transactions = await withImpersonation( "john.smith@company.com", async (session) => { const result = await session.run( MATCH (t:Transaction) WHERE t.timestamp >= $startDate RETURN t ORDER BY t.timestamp DESC LIMIT 100, { startDate }, ); return result.records.map((r) => r.get("t").properties); }, );

Prerequisites

  • Neo4j Enterprise Edition 5.x

  • Service account with GRANT IMPERSONATE privilege

  • Users created in Neo4j mapped to IdP identities

  1. Transaction Functions

Prefer executeRead / executeWrite over raw session.run() . They provide automatic retries for transient errors (network blips, leader changes).

// Read transaction with automatic retries const persons = await withSession(async (session) => { return session.executeRead(async (tx) => { const result = await tx.run( MATCH (p:Person {id: $personId})-[:OWNS]->(a:Account) RETURN p, collect(a) AS accounts, { personId }, ); return result.records.map((record) => ({ person: record.get("p").properties, accounts: record.get("accounts").map((a: any) => a.properties), })); }); });

// Write transaction with automatic retries await withSession(async (session) => { return session.executeWrite(async (tx) => { await tx.run(CREATE (p:Person $props), { props: { id: crypto.randomUUID(), name: "Jane Doe" }, }); }); }, "WRITE");

  1. Neo4j Type Handling

The driver returns Neo4j-specific types that must be converted before use in UI code.

Integer

Neo4j integers are 64-bit. The driver wraps them in neo4j.Integer objects.

import neo4j from "neo4j-driver";

// Check if a value is a Neo4j Integer if (neo4j.isInt(value)) { const jsNumber = value.toNumber(); // Safe for values < Number.MAX_SAFE_INTEGER const jsString = value.toString(); // Safe for any value const jsBigInt = value.toBigInt(); // Native BigInt }

DateTime

if (neo4j.isDateTime(value)) { const isoString = value.toString(); // ISO 8601 string const jsDate = value.toStandardDate(); // JavaScript Date object }

if (neo4j.isDate(value)) { const isoString = value.toString(); // YYYY-MM-DD }

if (neo4j.isDuration(value)) { const str = value.toString(); // ISO 8601 duration }

Node, Relationship, Path

// Node record.get("n").identity; // neo4j.Integer (internal ID) record.get("n").elementId; // string (stable element ID — use this) record.get("n").labels; // string[] record.get("n").properties; // Record<string, any>

// Relationship record.get("r").identity; // neo4j.Integer record.get("r").elementId; // string record.get("r").type; // string (relationship type) record.get("r").startNodeElementId; // string record.get("r").endNodeElementId; // string record.get("r").properties; // Record<string, any>

// Path record.get("path").start; // Node record.get("path").end; // Node record.get("path").segments; // Array<{ start, relationship, end }> record.get("path").length; // number

  1. Result-to-UI Data Mapping

Convert Neo4j driver results into shapes that Zustand stores and NVL components expect.

Property Mapping (Handle Neo4j Types)

function mapProperties( props: Record<string, unknown>, ): Record<string, unknown> { const mapped: Record<string, unknown> = {}; for (const [key, value] of Object.entries(props)) { if (neo4j.isInt(value)) { mapped[key] = value.toNumber(); } else if (neo4j.isDateTime(value) || neo4j.isDate(value)) { mapped[key] = value.toString(); } else if (neo4j.isDuration(value)) { mapped[key] = value.toString(); } else { mapped[key] = value; } } return mapped; }

Neo4j Node → Application GraphNode

interface GraphNode { id: string; labels: string[]; properties: Record<string, unknown>; }

function toGraphNode(neo4jNode: any): GraphNode { return { id: neo4jNode.elementId, labels: neo4jNode.labels, properties: mapProperties(neo4jNode.properties), }; }

Neo4j Relationship → Application GraphRelationship

interface GraphRelationship { id: string; type: string; source: string; target: string; properties: Record<string, unknown>; }

function toGraphRelationship(neo4jRel: any): GraphRelationship { return { id: neo4jRel.elementId, type: neo4jRel.type, source: neo4jRel.startNodeElementId, target: neo4jRel.endNodeElementId, properties: mapProperties(neo4jRel.properties), }; }

Full Query → Store Data

export const expandNetwork = async (personId: string, hops: number = 2) => { return withSession(async (session) => { const result = await session.executeRead(async (tx) => { return tx.run( MATCH path = (p:Person {id: $personId})-[*1..${hops}]-(connected) RETURN nodes(path) AS nodes, relationships(path) AS rels LIMIT 500, { personId }, ); });

const nodesMap = new Map&#x3C;string, GraphNode>();
const relsMap = new Map&#x3C;string, GraphRelationship>();

for (const record of result.records) {
  for (const node of record.get("nodes")) {
    nodesMap.set(node.elementId, toGraphNode(node));
  }
  for (const rel of record.get("rels")) {
    relsMap.set(rel.elementId, toGraphRelationship(rel));
  }
}

return {
  nodes: Array.from(nodesMap.values()),
  relationships: Array.from(relsMap.values()),
};

}); };

  1. Parameterized Queries

Always use parameters. Never concatenate user input into Cypher strings.

// ✅ Parameterized — safe, enables query plan caching const result = await session.run( MATCH (p:Person {id: $personId})-[r*1..2]-(connected) WHERE connected.timestamp >= $startDate RETURN p, r, connected LIMIT $limit, { personId: id, startDate: startDate.toISOString(), limit: neo4j.int(100) }, );

// ❌ String interpolation — Cypher injection risk const result = await session.run( MATCH (p:Person {id: '${id}'})-[r*1..2]-(connected) RETURN p, );

  1. Error Handling

import { Neo4jError } from "neo4j-driver";

export const handleNeo4jError = (error: unknown): never => { if (error instanceof Neo4jError) { switch (error.code) { case "Neo.ClientError.Security.Unauthorized": throw new Error("Invalid Neo4j credentials"); case "Neo.ClientError.Security.Forbidden": throw new Error("User does not have permission for this operation"); case "Neo.ClientError.Statement.SyntaxError": throw new Error(Invalid Cypher query: ${error.message}); case "Neo.ClientError.Schema.ConstraintValidationFailed": throw new Error(Constraint violation: ${error.message}); case "Neo.TransientError.Transaction.DeadlockDetected": throw new Error("Deadlock detected — retry the operation"); default: throw new Error(Neo4j error [${error.code}]: ${error.message}); } } throw error; };

Transient Error Retry

executeRead /executeWrite handle transient retries automatically. If using raw session.run() , catch and retry transient errors:

const isTransientError = (error: unknown): boolean => error instanceof Neo4jError && error.code.startsWith("Neo.TransientError");

  1. Connection Lifecycle

// App startup const driver = initDriver(config.uri, config.user, config.password); await driver.verifyConnectivity();

// App shutdown (or React cleanup) useEffect(() => { return () => { closeDriver(); }; }, []);

Anti-Patterns

Anti-Pattern Why It Fails Fix

Storing the driver in React state useState(neo4j.driver(...)) — driver is not serializable, gets recreated on re-render Store driver in a module-level variable

Creating a new driver per query Bypasses connection pooling, exhausts resources Create one driver at app startup, reuse it

Not closing sessions Connection pool exhaustion — queries start timing out Always close in finally or use withSession helper

String concatenation in Cypher Cypher injection vulnerability Always use parameterized queries

Ignoring neo4j.Integer type toNumber() silently overflows for values > Number.MAX_SAFE_INTEGER

Use toString() for display, toBigInt() for arithmetic on large values

Using record.get('n').identity

Internal numeric ID — can change across database restarts Use record.get('n').elementId (stable string identifier)

Not mapping Neo4j types before storing in Zustand Zustand stores (and JSON serialization) can't handle neo4j.Integer or neo4j.DateTime

Always map with toNumber() / toString() before storing

Using session.run() for multi-statement transactions No automatic retry on transient errors, no transaction boundary Use session.executeRead(tx => ...) or session.executeWrite(tx => ...)

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

neo4j-data-models

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

neo4j-cypher

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

git-workflow

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

fastapi

No summary provided by upstream source.

Repository SourceNeeds Review