@farming-labs/orm

Neo4j

Neo4j integration is runtime-first.

Use @farming-labs/orm-neo4j when:

Supported Neo4j runtime inputs

The current runtime uses those official driver/session shapes directly. It does not invent another graph client layer.

Runtime setup

neo4j-runtime.ts
import neo4j from "neo4j-driver";
import { createOrm } from "@farming-labs/orm";
import { createNeo4jDriver } from "@farming-labs/orm-neo4j";
import { appSchema } from "./schema";

const driver = neo4j.driver(
  process.env.NEO4J_URI!,
  neo4j.auth.basic(process.env.NEO4J_USERNAME!, process.env.NEO4J_PASSWORD!),
);

const orm = createOrm({
  schema: appSchema,
  driver: createNeo4jDriver({
    client: driver,
    database: "neo4j",
  }),
});

From there, shared code keeps using the same unified API:

const user = await orm.user.findUnique({
  where: {
    email: "ada@farminglabs.dev",
  },
  select: {
    id: true,
    email: true,
    sessions: {
      select: {
        token: true,
      },
    },
  },
});

What the Neo4j driver is doing

The Neo4j driver keeps the shared ORM surface and stores records through Neo4j-managed nodes plus ORM-owned lookup metadata.

It:

  1. accepts the app's real Neo4j driver or session
  2. executes writes through Neo4j transactions
  3. uses ORM-managed unique lookups and follow-up relation reads
  4. keeps the same normalized error and capability surface as the other runtimes

That means a package can write its storage layer once while each app decides whether the actual execution stack is Neo4j, Prisma, Drizzle, Kysely, MikroORM, TypeORM, Sequelize, direct SQL, Firestore, DynamoDB, Redis, MongoDB, or Mongoose.

Runtime helper path

If a framework or shared package wants to accept the raw Neo4j client directly, use the runtime helpers:

import { createOrmFromRuntime } from "@farming-labs/orm-runtime";

const orm = await createOrmFromRuntime({
  schema: appSchema,
  client: driver,
});

You can also pass a Neo4j session directly:

const session = driver.session({ database: "neo4j" });

const orm = await createOrmFromRuntime({
  schema: appSchema,
  client: session,
});

That is the cleanest path for higher-level integrations that do not want to branch on Neo4j specifically.

Setup helpers

The setup helpers work with Neo4j too:

import { bootstrapDatabase, pushSchema } from "@farming-labs/orm-runtime/setup";

await pushSchema({
  schema: appSchema,
  client: driver,
});

const orm = await bootstrapDatabase({
  schema: appSchema,
  client: driver,
});

For Neo4j runtimes, that setup path creates the ORM-owned constraints and indexes needed for record and unique-lock storage.

That is useful when a package or framework wants:

Relation support

The Neo4j runtime is graph-backed, but the current ORM layer still keeps relation loading conservative:

Those reads work through the shared relation resolver and follow-up lookups. This runtime is not trying to expose a Cypher-native graph traversal planner through the ORM API.

That makes it a strong fit for:

while still preserving the same shared query surface as the rest of the repo.

Transactions and mutations

Neo4j write transactions map into the unified ORM transaction surface:

await orm.transaction(async (tx) => {
  const user = await tx.user.create({
    data: {
      email: "ada@farminglabs.dev",
      name: "Ada",
    },
    select: {
      id: true,
    },
  });

  await tx.session.upsert({
    where: {
      token: "session-token",
    },
    create: {
      userId: user.id,
      token: "session-token",
      expiresAt: new Date("2027-01-01T00:00:00.000Z"),
    },
    update: {
      expiresAt: new Date("2027-01-01T00:00:00.000Z"),
    },
  });
});

The same runtime also supports:

Verification

Run the fast local Neo4j suite with:

terminal
pnpm test:local:neo4j

That package-local suite uses a Neo4j-shaped harness so you can verify the runtime, setup helpers, and transaction behavior without running a local Neo4j service first.

If you want a real Bolt-backed run against your own local or shared Neo4j instance, use:

terminal
export FARM_ORM_LOCAL_NEO4J_URI=bolt://127.0.0.1:7687
export FARM_ORM_LOCAL_NEO4J_USERNAME=neo4j
export FARM_ORM_LOCAL_NEO4J_PASSWORD=your-password

pnpm test:neo4j:real

Important boundaries

Why it fits well

Neo4j already gives apps a strong graph runtime.

Farming Labs ORM sits one layer above that:

That is the main value: Neo4j apps can participate in the same package-level storage contract as Prisma, Drizzle, Kysely, MikroORM, TypeORM, Sequelize, direct SQL, Firestore, DynamoDB, Redis, MongoDB, and Mongoose apps.