# Introduction (/docs)
Neogma is a fully type-safe Object-Graph-Mapping (OGM) framework for Neo4j and TypeScript. Define graph models using decorators, build complex queries with a fluent API, and manage relationships automatically - all with complete type safety.
## What's new in v2 [#whats-new-in-v2]
Neogma v2 introduces a **decorator-based model definition** with simple, readable class decorators:
* **`@Node`, `@PrimaryKey`, `@Property`, `@Relationship` decorators** - define models with plain classes
* **[TypeBox](https://github.com/sinclairzx81/typebox) validation** - use TypeBox schemas for powerful, type-safe validation (supports complex validations like unions, patterns, and custom constraints)
* **Less boilerplate** - no need for separate type interfaces; types are inferred directly from your class
* **Full backwards compatibility** - the `ModelFactory` API still works; only the old revalidator validation schema is deprecated and will be removed in a future major release
See the [migration guide](/docs/migration) for step-by-step instructions on upgrading from v1.
Looking for v1 documentation? The v1 docs are available at
[themetalfleece.github.io/neogma](https://themetalfleece.github.io/neogma/),
and the v1 source is on the [`release/v1`
branch](https://github.com/themetalfleece/neogma/tree/release/v1).
## Key features [#key-features]
* 🔒 **Fully type-safe** - complete TypeScript support with automatic type inference
* 🎀 **Decorator-based models** - define nodes, properties, and relationships with `@Node`, `@Property`, `@Relationship`
* 🔍 **Flexible queries** - use models, the QueryBuilder, or raw Cypher
* 🔗 **Automatic relationships** - create and manage complex graph structures in a single operation
* 🚀 **Eager loading** - load nested relationships in one query to avoid N+1 problems
* ✅ **[TypeBox](https://github.com/sinclairzx81/typebox) validation** - powerful schema validation (included, no extra install)
* 🛡️ **Parameterized queries** - all user input is passed as query parameters, never interpolated into Cypher strings, preventing injection attacks
* 🔄 **Session and transaction management** - built-in helpers for sessions, transactions, and automatic rollback on errors
* 🏗️ **NestJS integration** - first-class support via the `@neogma/nest` module
## Quick example [#quick-example]
```typescript
import {
Neogma,
Node,
PrimaryKey,
Property,
Relationship,
NodeEntity,
Type,
} from 'neogma';
import type { Related } from 'neogma';
const neogma = new Neogma({
url: 'bolt://localhost:7687',
username: 'neo4j',
password: 'password',
});
@Node({ label: 'Order' })
class OrderNode extends NodeEntity {
@PrimaryKey(Type.String()) id!: string;
@Property(Type.String()) status!: string;
}
@Node({ label: 'User' })
class UserNode extends NodeEntity {
@PrimaryKey(Type.String()) id!: string;
@Property(Type.String({ minLength: 1 }))
name!: string;
@Relationship({ name: 'PLACED', direction: 'out', model: () => OrderNode })
Orders!: Related;
}
// Register models (referenced models first)
const Orders = neogma.model(OrderNode);
const Users = neogma.model(UserNode);
// Create a user with a related order - fully typed
const user = await Users.createOne({
id: '1',
name: 'Alice',
Orders: {
attributes: [{ id: 'order-1', status: 'confirmed' }],
},
});
// Eager-load relationships in one query
const found = await Users.findOne({
where: { name: 'Alice' },
relationships: { Orders: { where: { target: { status: 'confirmed' } } } },
});
console.log(found?.name); // string
console.log(found?.Orders[0].node.status); // string
// console.log(found?.bogusValue); // TypeScript error
```
## Example applications [#example-applications]
Check out the example applications in the repository's [`/examples`](https://github.com/themetalfleece/neogma/tree/main/examples) directory:
| Example | Description |
| ------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------- |
| [`basic-app-decorators`](https://github.com/themetalfleece/neogma/tree/main/examples/basic-app-decorators) | TC39 decorator-based models (recommended) |
| [`basic-app-legacy-decorators`](https://github.com/themetalfleece/neogma/tree/main/examples/basic-app-legacy-decorators) | Experimental (legacy) decorator-based models |
| [`basic-app-js`](https://github.com/themetalfleece/neogma/tree/main/examples/basic-app-js) | JavaScript with ModelFactory |
| [`nestjs-app`](https://github.com/themetalfleece/neogma/tree/main/examples/nestjs-app) | NestJS integration with `@neogma/nest` |
## For AI/LLM agents [#for-aillm-agents]
This documentation is available in machine-readable formats:
* [`/llms.txt`](/llms.txt) - page index with titles and URLs
* [`/llms-full.txt`](/llms-full.txt) - complete documentation content as plaintext markdown
## Next steps [#next-steps]
* [Installation](/docs/getting-started/installation) - install neogma and set up your project
* [Defining models](/docs/models/decorator-based) - learn the decorator-based model API
* [Query Builder](/docs/query-builder) - build complex Cypher queries
* [Migration from v1](/docs/migration) - upgrade from neogma v1
# TC39 Decorators (/docs/examples/decorators)
# TC39 Decorators Example [#tc39-decorators-example]
**Source**: [`examples/basic-app-decorators`](https://github.com/themetalfleece/neogma/tree/main/examples/basic-app-decorators)
This is the **recommended** approach for new TypeScript projects. It uses TC39 standard decorators (stage 3) with no special tsconfig flags needed.
## What it demonstrates [#what-it-demonstrates]
* Model definition with `@Node`, `@PrimaryKey`, `@Property`, `@Relationship`
* TypeBox validation (`Type.String()`, `Type.Number()`, `Type.Optional()`)
* Type inference with `Related`
* Model registration with `neogma.model()`
* CRUD operations (create, find, update, delete)
* Eager loading of relationships
* QueryBuilder with all clause types
* Sessions and transactions (including rollback)
* Error handling
## Model definition [#model-definition]
```typescript
import {
Node,
PrimaryKey,
Property,
Relationship,
NodeEntity,
Type,
} from 'neogma';
import type { Related } from 'neogma';
@Node({ label: 'User' })
class UserNode extends NodeEntity {
@PrimaryKey(Type.String()) id!: string;
@Property(Type.String({ minLength: 1 })) name!: string;
@Property(Type.Optional(Type.Number({ minimum: 0 }))) age?: number;
@Relationship({ name: 'HAS_ORDER', direction: 'out', model: () => OrderNode })
Orders!: Related;
}
```
## Running [#running]
```bash
cd examples/basic-app-decorators
pnpm install
pnpm start
```
# Overview (/docs/examples)
# Example Applications [#example-applications]
Neogma includes several example applications in the [`/examples`](https://github.com/themetalfleece/neogma/tree/main/examples) directory. Each app demonstrates the same core features using a different model definition style.
## Running an example [#running-an-example]
All examples require a local Neo4j instance. Start one with Docker:
```bash
docker compose up -d
```
Then run any example:
```bash
cd examples/basic-app-decorators
pnpm install
pnpm start
```
## Available examples [#available-examples]
| Example | Style | Language |
| ----------------------------------------------------- | ---------------------------------------------------- | ---------- |
| [TC39 Decorators](/docs/examples/decorators) | `@Node`, `@PrimaryKey`, `@Property`, `@Relationship` | TypeScript |
| [Legacy Decorators](/docs/examples/legacy-decorators) | `experimentalDecorators` | TypeScript |
| [JavaScript](/docs/examples/javascript) | `ModelFactory` | JavaScript |
| [NestJS](/docs/examples/nestjs) | `@neogma/nest` module | TypeScript |
# JavaScript (/docs/examples/javascript)
# JavaScript [#javascript]
For JavaScript projects that cannot use decorators, neogma provides `ModelFactory`. See the [ModelFactory documentation](/docs/models/model-factory) for details.
```javascript
const { Neogma, ModelFactory, Type } = require('neogma');
const neogma = new Neogma({
url: 'bolt://localhost:7687',
username: 'neo4j',
password: 'password',
});
const Users = ModelFactory(
{
label: 'User',
schema: {
id: Type.String(),
name: Type.String(),
age: Type.Optional(Type.Number()),
},
primaryKeyField: 'id',
},
neogma,
);
const user = await Users.createOne({
id: '1',
name: 'Alice',
age: 30,
});
```
# Legacy Decorators (/docs/examples/legacy-decorators)
# Legacy Decorators Example [#legacy-decorators-example]
**Source**: [`examples/basic-app-legacy-decorators`](https://github.com/themetalfleece/neogma/tree/main/examples/basic-app-legacy-decorators)
This example uses TypeScript's experimental decorators. Use this approach when your project already has `experimentalDecorators: true` in its tsconfig (common with NestJS, Angular, etc.).
## Differences from TC39 decorators [#differences-from-tc39-decorators]
The only difference is the import path and tsconfig:
```json
// tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
```
```typescript
// Import from 'neogma/legacy' instead of 'neogma'
import {
Node,
PrimaryKey,
Property,
Relationship,
NodeEntity,
Type,
} from 'neogma/legacy';
```
Everything else (model definition, CRUD, QueryBuilder, etc.) is identical to the TC39 decorators example.
## Running [#running]
```bash
cd examples/basic-app-legacy-decorators
pnpm install
pnpm start
```
# NestJS (/docs/examples/nestjs)
# NestJS Example [#nestjs-example]
**Source**: [`examples/nestjs-app`](https://github.com/themetalfleece/neogma/tree/main/examples/nestjs-app)
This example demonstrates a complete NestJS application using the `@neogma/nest` module for dependency injection, model registration, and CRUD endpoints.
## What it demonstrates [#what-it-demonstrates]
* `NeogmaModule.forRoot()` for connection setup
* `NeogmaModule.forFeature()` for model registration
* `@InjectModel()` for injecting models into services
* `ModelOf` type helper for typed model injection
* CRUD REST endpoints backed by neogma models
* Health check endpoint
## Key setup [#key-setup]
```typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { NeogmaModule } from '@neogma/nest';
import { UserNode, OrderNode } from './models';
@Module({
imports: [
NeogmaModule.forRoot({
connection: {
url: 'bolt://localhost:7687',
username: 'neo4j',
password: 'password',
},
}),
NeogmaModule.forFeature([UserNode, OrderNode]),
],
})
export class AppModule {}
```
## Running [#running]
```bash
cd examples/nestjs-app
pnpm install
pnpm start:dev
```
See the [NestJS integration docs](/docs/integrations/nestjs) for full API reference.
# Connecting to Neo4j (/docs/getting-started/connection)
# Connecting to Neo4j [#connecting-to-neo4j]
## Creating a Neogma instance [#creating-a-neogma-instance]
The `Neogma` class manages your connection to Neo4j. Create an instance with your database credentials:
```typescript
import { Neogma } from 'neogma';
const neogma = new Neogma({
url: 'bolt://localhost:7687',
username: 'neo4j',
password: 'password',
});
```
### Connection options [#connection-options]
The constructor accepts the same options as the official Neo4j JavaScript driver:
```typescript
const neogma = new Neogma(
{
url: 'bolt://localhost:7687',
username: 'neo4j',
password: 'password',
database: 'my-database', // optional, defaults to 'neo4j'
},
{
// optional: log all Cypher queries
logger: console.log,
},
);
```
### Environment variables [#environment-variables]
A common pattern is to read connection details from environment variables:
```typescript
import { Neogma } from 'neogma';
const neogma = new Neogma({
url: process.env.NEO4J_URL || 'bolt://localhost:7687',
username: process.env.NEO4J_USERNAME || 'neo4j',
password: process.env.NEO4J_PASSWORD || 'password',
});
```
## Verifying the connection [#verifying-the-connection]
You can verify the connection using the Neo4j driver's `verifyConnectivity` method:
```typescript
await neogma.driver.verifyConnectivity();
console.log('Connected to Neo4j');
```
## Closing the connection [#closing-the-connection]
Always close the connection when your application shuts down:
```typescript
await neogma.driver.close();
```
## Query runner [#query-runner]
Every `Neogma` instance exposes a `queryRunner` that you can use to execute raw Cypher:
```typescript
const result = await neogma.queryRunner.run(
'MATCH (u:User) WHERE u.name = $name RETURN u',
{ name: 'Alice' },
);
for (const record of result.records) {
console.log(record.get('u').properties);
}
```
# Installation (/docs/getting-started/installation)
# Installation [#installation]
## Install neogma [#install-neogma]
```bash
npm install neogma
# or
pnpm add neogma
# or
yarn add neogma
```
Neogma includes TypeBox as a dependency, so you do not need to install it separately. You can import `Type`, `Value`, and `TSchema` directly from `neogma`.
## TypeScript configuration [#typescript-configuration]
Neogma v2 uses decorators. You can use either **TC39 standard decorators** (recommended) or **legacy experimental decorators**.
### TC39 decorators (recommended) [#tc39-decorators-recommended]
No extra configuration is needed. Make sure your `tsconfig.json` does **not** have `experimentalDecorators` enabled:
```json title="tsconfig.json"
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"strict": true
// Do NOT set "experimentalDecorators": true
}
}
```
### Legacy experimental decorators [#legacy-experimental-decorators]
If you prefer or need to use legacy experimental decorators (for example, in an existing NestJS project), enable them in your `tsconfig.json`:
```json title="tsconfig.json"
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"strict": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
```
Then import decorators from `neogma/legacy` instead of `neogma`:
```typescript
// TC39 (default)
import { Node, PrimaryKey, Property, Relationship, NodeEntity } from 'neogma';
// Legacy experimental decorators
import {
Node,
PrimaryKey,
Property,
Relationship,
NodeEntity,
} from 'neogma/legacy';
```
Both paths produce the same models and are fully interchangeable at runtime.
## Neo4j setup [#neo4j-setup]
You need a running Neo4j instance. The easiest way to get started locally is with Docker:
```yaml title="docker-compose.yml"
services:
neo4j:
image: neo4j:5-enterprise
ports:
- '7474:7474'
- '7687:7687'
environment:
- NEO4J_AUTH=neo4j/password
- NEO4J_ACCEPT_LICENSE_AGREEMENT=yes
```
```bash
docker compose up -d
```
## Node.js version [#nodejs-version]
Neogma requires Node.js 22.13.0 or later.
# Quick Start (/docs/getting-started/quick-start)
# Quick start [#quick-start]
This guide walks you through creating a simple application with neogma. You will define two models, create nodes with a relationship, and query them.
## 1. Set up the connection [#1-set-up-the-connection]
```typescript
import {
Neogma,
Node,
PrimaryKey,
Property,
Relationship,
NodeEntity,
Type,
Op,
} from 'neogma';
import type { Related } from 'neogma';
const neogma = new Neogma({
url: 'bolt://localhost:7687',
username: 'neo4j',
password: 'password',
});
```
## 2. Define your models [#2-define-your-models]
```typescript
@Node({ label: 'Order' })
class OrderNode extends NodeEntity {
@PrimaryKey(Type.String())
id!: string;
@Property(Type.String())
status!: string;
}
@Node({ label: 'User' })
class UserNode extends NodeEntity {
@PrimaryKey(Type.String())
id!: string;
@Property(Type.String({ minLength: 1 }))
name!: string;
@Property(Type.Optional(Type.Number({ minimum: 0 })))
age?: number;
@Relationship({
name: 'PLACED',
direction: 'out',
model: () => OrderNode,
})
Orders!: Related;
}
```
## 3. Register models [#3-register-models]
Models must be registered with the neogma instance. Register them in dependency order (referenced models first):
```typescript
const Orders = neogma.model(OrderNode);
const Users = neogma.model(UserNode);
```
## 4. Create nodes [#4-create-nodes]
```typescript
// Create a user with a related order in a single operation
const user = await Users.createOne({
id: '1',
name: 'Alice',
age: 30,
Orders: {
attributes: [{ id: 'order-1', status: 'confirmed' }],
},
});
console.log(user.name); // 'Alice'
```
## 5. Query nodes [#5-query-nodes]
```typescript
// Find a single user
const found = await Users.findOne({
where: { name: 'Alice' },
});
console.log(found?.age); // 30
// Find multiple users with filtering
const adults = await Users.findMany({
where: { age: { [Op.gte]: 18 } },
order: [['name', 'ASC']],
limit: 10,
});
```
## 6. Load relationships [#6-load-relationships]
```typescript
const usersWithOrders = await Users.findMany({
where: { name: 'Alice' },
relationships: {
Orders: {
where: { target: { status: 'confirmed' } },
},
},
});
for (const u of usersWithOrders) {
for (const order of u.Orders) {
console.log(order.node.status); // 'confirmed'
}
}
```
## 7. Clean up [#7-clean-up]
```typescript
await neogma.driver.close();
```
## Next steps [#next-steps]
* [Defining models with decorators](/docs/models/decorator-based) - full decorator API reference
* [Relationships](/docs/relationships) - relationship definitions and operations
* [Query Builder](/docs/query-builder) - build complex Cypher queries programmatically
* [Example applications](https://github.com/themetalfleece/neogma/tree/main/examples) - runnable examples
# NestJS (/docs/integrations/nestjs)
# NestJS [#nestjs]
The `@neogma/nest` package provides a first-class NestJS module for neogma. It handles connection lifecycle, dependency injection, and model registration.
## Installation [#installation]
```bash
npm install @neogma/nest neogma
# or
pnpm add @neogma/nest neogma
```
`@neogma/nest` requires `@nestjs/common` and `@nestjs/core` version 10 or later as peer dependencies.
## Setup [#setup]
### Basic configuration [#basic-configuration]
Import `NeogmaModule` in your root module and call `forRoot`:
```typescript
import { Module } from '@nestjs/common';
import { NeogmaModule } from '@neogma/nest';
@Module({
imports: [
NeogmaModule.forRoot({
connection: {
url: 'bolt://localhost:7687',
username: 'neo4j',
password: 'password',
},
}),
],
})
export class AppModule {}
```
### Async configuration [#async-configuration]
Use `forRootAsync` for dynamic configuration (e.g., loading from a config service):
```typescript
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { NeogmaModule } from '@neogma/nest';
@Module({
imports: [
ConfigModule.forRoot(),
NeogmaModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
connection: {
url: config.get('NEO4J_URL', 'bolt://localhost:7687'),
username: config.get('NEO4J_USERNAME', 'neo4j'),
password: config.get('NEO4J_PASSWORD', 'password'),
},
}),
}),
],
})
export class AppModule {}
```
### Configuration options [#configuration-options]
```typescript
interface NeogmaModuleOptions {
connection: {
url: string;
username: string;
password: string;
database?: string; // optional Neo4j database name
};
options?: {
logger?: (message: string) => void;
[key: string]: unknown;
};
}
```
## Registering models [#registering-models]
Define your models with decorators, then register them with `forFeature`. Since NestJS projects typically have `experimentalDecorators: true` in tsconfig, import from `neogma/legacy`:
```typescript
// models/user.model.ts
import { Node, PrimaryKey, Property, NodeEntity, Type } from 'neogma/legacy';
import { getModelToken } from '@neogma/nest';
import type { ModelOf } from '@neogma/nest';
@Node({ label: 'User' })
export class UserNode extends NodeEntity {
@PrimaryKey(Type.String()) id!: string;
@Property(Type.String()) name!: string;
}
export type UsersModel = ModelOf;
export const USERS_TOKEN = getModelToken(UserNode);
```
```typescript
// models/order.model.ts
import { Node, PrimaryKey, Property, NodeEntity, Type } from 'neogma/legacy';
import { getModelToken } from '@neogma/nest';
import type { ModelOf } from '@neogma/nest';
@Node({ label: 'Order' })
export class OrderNode extends NodeEntity {
@PrimaryKey(Type.String()) id!: string;
@Property(Type.String()) status!: string;
}
export type OrdersModel = ModelOf;
export const ORDERS_TOKEN = getModelToken(OrderNode);
```
Register models in a feature module:
```typescript
import { Module } from '@nestjs/common';
import { NeogmaModule } from '@neogma/nest';
import { UserNode } from './models/user.model';
import { OrderNode } from './models/order.model';
import { UserService } from './user.service';
@Module({
imports: [NeogmaModule.forFeature([UserNode, OrderNode])],
providers: [UserService],
exports: [UserService],
})
export class UserModule {}
```
If your NestJS project does not use `experimentalDecorators`, you can import
from `neogma` instead of `neogma/legacy`. The decorator API is identical
either way.
## Injecting models [#injecting-models]
Use the exported token and type from your model file with `@Inject`:
```typescript
import { Injectable, Inject } from '@nestjs/common';
import { UsersModel, USERS_TOKEN } from './models/user.model';
@Injectable()
export class UserService {
constructor(
@Inject(USERS_TOKEN)
private readonly Users: UsersModel,
) {}
async findAll() {
return this.Users.findMany({});
}
async findById(id: string) {
return this.Users.findOne({ where: { id } });
}
async create(data: { id: string; name: string }) {
return this.Users.createOne(data);
}
}
```
Since each model file already exports a `UsersModel` type and a `USERS_TOKEN` constant, you do not need to import `getModelToken` or `ModelOf` from `@neogma/nest` in your services.
## Injecting NeogmaService [#injecting-neogmaservice]
For direct access to the neogma instance (e.g., for raw queries), inject `NeogmaService`:
```typescript
import { Injectable } from '@nestjs/common';
import { NeogmaService } from '@neogma/nest';
@Injectable()
export class DatabaseService {
constructor(private readonly neogmaService: NeogmaService) {}
async runCustomQuery(cypher: string, params: Record) {
return this.neogmaService.neogma.queryRunner.run(cypher, params);
}
}
```
## Lifecycle [#lifecycle]
`NeogmaModule` handles the full connection lifecycle:
* **`onModuleInit`** - creates the Neo4j driver and verifies connectivity
* **`onApplicationShutdown`** - closes the driver cleanly
You do not need to manage the connection manually.
## Example Application [#example-application]
See the [`nestjs-app` example](https://github.com/themetalfleece/neogma/tree/main/examples/nestjs-app) in the repository for a complete working NestJS application with neogma.
# Migrating from v1 (/docs/migration)
# Migrating from v1 [#migrating-from-v1]
This guide walks you through upgrading from neogma v1 to v2.
## What changed in v2 [#what-changed-in-v2]
* **Decorator-based models** - define models with `@Node`, `@PrimaryKey`, `@Property`, `@Relationship` instead of `ModelFactory`
* **TypeBox validation** - use TypeBox schemas instead of revalidator JSON Schema
* **Type inference** - no more separate interface declarations; types are inferred from the class
* **`neogma.model()`** - register decorated classes instead of calling `ModelFactory` directly
* **TypeBox re-exported** - `Type`, `Value`, and `TSchema` are available directly from `neogma`
## Step-by-step migration [#step-by-step-migration]
### 1. Update the package [#1-update-the-package]
```bash
npm install neogma@latest
```
### 2. Convert model definitions [#2-convert-model-definitions]
**Before (v1):**
```typescript
import { Neogma, ModelFactory } from 'neogma';
import type { ModelRelatedNodesI, NeogmaInstance } from 'neogma';
type UserProperties = {
id: string;
name: string;
age?: number;
};
interface UserRelatedNodes {
Orders: ModelRelatedNodesI;
}
type UserInstance = NeogmaInstance;
const Users = ModelFactory(
{
label: 'User',
schema: {
id: { type: 'string', required: true },
name: { type: 'string', required: true, minLength: 3 },
age: { type: 'number', required: false, minimum: 0 },
},
primaryKeyField: 'id',
relationships: {
Orders: {
model: Orders,
direction: 'out',
name: 'PLACED',
},
},
},
neogma,
);
```
**After (v2):**
```typescript
import {
Neogma,
Node,
PrimaryKey,
Property,
Relationship,
NodeEntity,
Type,
} from 'neogma';
import type { Related } from 'neogma';
@Node({ label: 'User' })
class UserNode extends NodeEntity {
@PrimaryKey(Type.String())
id!: string;
@Property(Type.String({ minLength: 3 }))
name!: string;
@Property(Type.Optional(Type.Number({ minimum: 0 })))
age?: number;
@Relationship({
name: 'PLACED',
direction: 'out',
model: () => OrderNode,
})
Orders!: Related;
}
const Users = neogma.model(UserNode);
```
Key differences:
* No separate `UserProperties`, `UserRelatedNodes`, or `UserInstance` types needed
* Primary key uses `@PrimaryKey()` instead of `primaryKeyField` in the config
* Validation is defined inline with `Type.String()`, `Type.Number()`, etc.
* Relationships use lazy references (`() => OrderNode`) instead of direct model references
* The class extends `NodeEntity`
* Registration uses `neogma.model(UserNode)` instead of `ModelFactory(..., neogma)`
### 3. Convert validation schemas [#3-convert-validation-schemas]
| Revalidator (v1) | TypeBox (v2) |
| ----------------------------------------- | ---------------------------------------------------- |
| `{ type: 'string', required: true }` | `Type.String()` |
| `{ type: 'string', required: false }` | `Type.Optional(Type.String())` |
| `{ type: 'string', minLength: 3 }` | `Type.String({ minLength: 3 })` |
| `{ type: 'number', minimum: 0 }` | `Type.Number({ minimum: 0 })` |
| `{ type: 'number', maximum: 100 }` | `Type.Number({ maximum: 100 })` |
| `{ type: 'boolean' }` | `Type.Boolean()` |
| `{ type: 'string', enum: ['a', 'b'] }` | `Type.Union([Type.Literal('a'), Type.Literal('b')])` |
| `{ type: 'string', pattern: '^[a-z]+$' }` | `Type.String({ pattern: '^[a-z]+$' })` |
### 4. Update imports [#4-update-imports]
```typescript
// v1
import { Neogma, ModelFactory, QueryBuilder, BindParam } from 'neogma';
import type { ModelRelatedNodesI, NeogmaInstance } from 'neogma';
// v2
import {
Neogma,
Node,
PrimaryKey,
Property,
Relationship,
NodeEntity,
Type,
QueryBuilder,
BindParam,
} from 'neogma';
import type { Related } from 'neogma';
```
### 5. Register models with `neogma.model()` [#5-register-models-with-neogmamodel]
```typescript
// v1
const Orders = ModelFactory({/* ... */}, neogma);
const Users = ModelFactory(
{/* ... */},
neogma,
);
// v2
const Orders = neogma.model(OrderNode);
const Users = neogma.model(UserNode);
```
Models can be registered in any order - neogma handles circular dependencies automatically.
## Gradual migration [#gradual-migration]
You can migrate incrementally. Both `ModelFactory` and decorator-based models work in the same application. This lets you convert one model at a time:
```typescript
// Already migrated
const Orders = neogma.model(OrderNode);
// Not yet migrated - still works
const Users = ModelFactory({/* ... */}, neogma);
```
`ModelFactory` continues to work without any deprecation warnings. It remains the recommended approach for JavaScript projects.
## Migrate with AI assistance [#migrate-with-ai-assistance]
Copy the prompt below and paste it into your AI assistant (Claude, ChatGPT, etc.) to get help migrating your codebase from neogma v1 to v2:
```text
I need to migrate a codebase from neogma v1 to neogma v2.
Read the full neogma v2 migration guide and documentation:
https://neogma.themetalfleece.dev/llms-full.txt
Key changes:
- Convert ModelFactory definitions to @Node, @PrimaryKey, @Property, @Relationship decorators (ModelFactory still works but decorators offer less boilerplate in TypeScript)
- Replace revalidator schemas (type: 'string') with TypeBox (Type.String())
- Remove separate *Properties, *RelatedNodes, *Instance type interfaces
- Primary key uses @PrimaryKey() decorator instead of primaryKeyField in @Node options
- Classes extend NodeEntity and use neogma.model(MyClass) for registration
- Relationships use Related and lazy refs: model: () => OtherNode
- Import decorators from 'neogma' (TC39) or 'neogma/legacy' (experimentalDecorators)
- Type, Value, TSchema are re-exported from neogma (no need to install typebox)
Please analyze my code and convert each ModelFactory call to the decorator-based API.
Keep QueryBuilder, session, and CRUD usage unchanged - only model definitions need updating.
```
### 6. Use object syntax for relationship properties [#6-use-object-syntax-for-relationship-properties]
The v1 **array syntax** with `alias` is deprecated and will be removed in a future release. Use the object syntax instead.
**Before (v1 -- deprecated):**
```typescript
// v1 array syntax -- DEPRECATED, will be removed
relationships: {
Orders: {
model: Orders,
direction: 'out',
name: 'PLACED',
properties: [
{
alias: 'Rating',
property: 'rating',
schema: { type: 'number', minimum: 1, maximum: 5 },
},
],
},
}
```
**After (v2):**
Use `defineRelationshipProperties()` with object syntax to define the config once and share it with both `@Relationship` and `Related<>`:
```typescript
import { defineRelationshipProperties, Type } from 'neogma';
const orderRelProps = defineRelationshipProperties({
Rating: {
property: 'rating',
schema: Type.Number({ minimum: 1, maximum: 5 }),
},
});
@Node({ label: 'User' })
class UserNode extends NodeEntity {
@Relationship({
name: 'PLACED',
direction: 'out',
model: () => OrderNode,
properties: orderRelProps,
})
Orders!: Related;
}
```
### 7. Use TypeBox schemas instead of revalidator [#7-use-typebox-schemas-instead-of-revalidator]
Revalidator JSON Schema validation is deprecated. Use TypeBox schemas for all property and relationship validation going forward.
**Deprecated:**
```typescript
schema: { type: 'string', required: true, minLength: 3 }
```
**Use instead:**
```typescript
import { Type } from 'neogma';
@Property(Type.String({ minLength: 3 }))
name!: string;
```
### 8. Prefer `neogma.model()` for TypeScript projects [#8-prefer-neogmamodel-for-typescript-projects]
For TypeScript projects, the decorator-based API with `neogma.model()` provides a better developer experience with less boilerplate. `ModelFactory` remains fully supported and is the recommended approach for JavaScript projects.
**ModelFactory (JavaScript and TypeScript):**
```typescript
const Users = ModelFactory(
{/* ... */},
neogma,
);
```
**Decorator API (TypeScript only):**
```typescript
const Users = neogma.model(UserNode);
```
## Breaking changes [#breaking-changes]
### Sessions export removal [#sessions-export-removal]
The `Sessions` module (`getSession`, `getTransaction`, `getRunnable`) is no longer exported as standalone functions from `neogma`. Use the instance methods on your `Neogma` instance instead.
**Before (v1):**
```typescript
import { getSession, getTransaction, getRunnable } from 'neogma';
await getSession(neogma, async (session) => {
// ...
});
```
**After (v2):**
```typescript
await neogma.getSession(async (session) => {
// ...
});
await neogma.getTransaction(async (tx) => {
// ...
});
const runnable = neogma.getRunnable(sessionOrTransaction);
```
### Utils API changes [#utils-api-changes]
Several utility functions are no longer exported publicly:
* `assertValidCypherIdentifier` -- removed
* `escapeCypherIdentifier` -- removed
* `isAlreadyEscaped` -- removed
* `sanitizeParamName` -- removed
* `isEmptyObject` -- removed
* `isPlainObject` -- removed
New exports that replace some of the above:
* `escapeIfNeeded` -- escapes a Cypher identifier only when necessary
* `escapeLabelIfNeeded` -- escapes a label string only when necessary
* `isValidCypherIdentifier` -- checks whether a string is a valid Cypher identifier
If your code imported any of the removed functions, update it to use the new equivalents or inline the logic.
### FOREACH keyword fix [#foreach-keyword-fix]
The QueryBuilder's `forEach` method now generates the correct Cypher keyword `FOREACH` instead of the previously incorrect `FOR EACH` (two words). `FOR EACH` is not valid Cypher and would cause query failures in most Neo4j versions.
If you had workarounds for the previous incorrect output (for example, string replacements or manual query patching), you should remove them. The generated output is now correct.
## v1 resources [#v1-resources]
* **v1 documentation**: [themetalfleece.github.io/neogma](https://themetalfleece.github.io/neogma/)
* **v1 source code**: [`release/v1` branch](https://github.com/themetalfleece/neogma/tree/release/v1)
# Decorator-Based Models (/docs/models/decorator-based)
# Decorator-based models [#decorator-based-models]
Define models using decorators instead of separate type declarations and configuration objects.
## Overview [#overview]
A model is defined as a class with four decorators:
* `@Node` -- marks the class as a Neo4j node and sets the label
* `@PrimaryKey` -- marks a field as the primary key (also registers it as a property)
* `@Property` -- marks a field as a stored property, with optional TypeBox validation
* `@Relationship` -- declares a relationship to another model
```typescript
import {
Node,
PrimaryKey,
Property,
Relationship,
NodeEntity,
Type,
} from 'neogma';
import type { Related } from 'neogma';
@Node({ label: 'User' })
class UserNode extends NodeEntity {
@PrimaryKey(Type.String())
id!: string;
@Property(Type.String({ minLength: 3 }))
name!: string;
@Property(Type.Optional(Type.Number({ minimum: 0 })))
age?: number;
@Relationship({
name: 'PLACED',
direction: 'out',
model: () => OrderNode,
})
Orders!: Related;
}
// Register the model with neogma
const Users = neogma.model(UserNode);
```
## The `@Node` decorator [#the-node-decorator]
`@Node` marks a class as a Neo4j node. It accepts:
| Option | Type | Description |
| ------- | -------------------- | --------------------------------------------------------------------- |
| `label` | `string \| string[]` | The Neo4j label (or labels) for this node. Defaults to the class name |
```typescript
// Explicit label
@Node({ label: 'User' })
// Multiple labels
@Node({ label: ['User', 'Person'] })
// Label defaults to the class name ('UserNode')
@Node()
```
The class must extend `NodeEntity`:
```typescript
import { NodeEntity } from 'neogma';
@Node({ label: 'User' })
class UserNode extends NodeEntity {
// ...
}
```
## The `@PrimaryKey` decorator [#the-primarykey-decorator]
`@PrimaryKey` marks a field as the primary key for the model. It also registers the field as a property, so you do not need to add `@Property` separately.
```typescript
// With validation schema
@PrimaryKey(Type.String())
id!: string;
// With stricter validation
@PrimaryKey(Type.String({ format: 'uuid' }))
id!: string;
// Without validation (tracked but not validated)
@PrimaryKey()
id!: string;
```
Each model must have exactly one `@PrimaryKey` field. The primary key is used for lookups and relationship matching.
## The `@Property` decorator [#the-property-decorator]
`@Property` marks a field as a Neo4j property. It optionally takes a TypeBox schema for validation:
```typescript
// With validation schema
@Property(Type.String({ minLength: 3 }))
name!: string;
// Optional property
@Property(Type.Optional(Type.Number()))
age?: number;
// Without validation (tracked but not validated)
@Property()
externalId!: string;
```
Use the definite assignment operator (`!`) for required properties, not
`declare`. The `declare` keyword cannot be used with TC39 decorators.
### Supported property types [#supported-property-types]
Neogma supports all types that Neo4j can store natively:
* `string`
* `number`
* `boolean`
* `string[]`, `number[]`, `boolean[]`
Use TypeBox to define the validation:
```typescript
import { Type } from 'neogma';
@Property(Type.String()) // string
@Property(Type.Number()) // number
@Property(Type.Boolean()) // boolean
@Property(Type.Array(Type.String())) // string[]
@Property(Type.Optional(Type.String())) // string | undefined
```
## The `@Relationship` decorator [#the-relationship-decorator]
`@Relationship` declares a relationship between two nodes:
```typescript
@Relationship({
name: 'PLACED', // Neo4j relationship type
direction: 'out', // 'in', 'out', or 'none'
model: () => OrderNode, // lazy reference to the target model
})
Orders!: Related;
```
### Relationship options [#relationship-options]
| Option | Type | Description |
| ------------ | --------------------------------- | ---------------------------------------------------------------------------- |
| `name` | `string` | The Neo4j relationship type (e.g., `'PLACED'`) |
| `direction` | `'in' \| 'out' \| 'none'` | The relationship direction |
| `model` | `() => NodeEntityClass \| 'self'` | Lazy reference to the target model class, or `'self'` for self-relationships |
| `properties` | `object \| array` | Optional relationship property definitions (see below) |
### Relationship properties [#relationship-properties]
Relationships can have their own properties. Use `defineRelationshipProperties()` to define the property config once and share it between `@Relationship` and `Related<>`:
```typescript
import { defineRelationshipProperties, Type } from 'neogma';
const ratedProductRelProps = defineRelationshipProperties({
Rating: { property: 'rating', schema: Type.Number({ minimum: 1, maximum: 5 }) },
Review: { property: 'review', schema: Type.Optional(Type.String()) },
});
@Relationship({
name: 'RATED',
direction: 'out',
model: () => ProductNode,
properties: ratedProductRelProps,
})
RatedProducts!: Related;
```
Each entry maps an **alias** (the key you use when creating relationships, e.g. `Rating`) to a **property** (the name stored in Neo4j, e.g. `rating`) with a TypeBox **schema** for validation.
#### Co-locating properties as static class members [#co-locating-properties-as-static-class-members]
You can define relationship properties as a static member of the class itself using the function form for `properties`:
```typescript
@Node({ label: 'User' })
class UserNode extends NodeEntity {
@PrimaryKey(Type.String()) id!: string;
static readonly orderRelProps = defineRelationshipProperties({
Rating: {
property: 'rating',
schema: Type.Number({ minimum: 1, maximum: 5 }),
},
});
@Relationship({
name: 'PLACED',
direction: 'out',
model: () => OrderNode,
properties: () => UserNode.orderRelProps,
})
Orders!: Related;
}
```
The `() => UserNode.orderRelProps` function form defers evaluation until `neogma.model()` runs, when static fields are guaranteed to be initialized.
#### Shorthand syntax [#shorthand-syntax]
When the alias and Neo4j property name are the same, pass just the schema:
```typescript
const relProps = defineRelationshipProperties({
rating: Type.Number({ minimum: 1, maximum: 5 }),
});
```
#### Explicit type form [#explicit-type-form]
You can also specify both types manually:
```typescript
RatedProducts!: Related;
```
### Self-relationships [#self-relationships]
Use `'self'` for relationships that point back to the same model:
```typescript
@Node({ label: 'Person' })
class PersonNode extends NodeEntity {
@PrimaryKey(Type.String())
id!: string;
@Relationship({
name: 'KNOWS',
direction: 'out',
model: 'self',
})
Friends!: Related;
}
```
### Circular references [#circular-references]
Relationships use lazy references (`() => TargetClass`) to handle circular dependencies. Neogma resolves them when models are registered:
```typescript
@Node({ label: 'User' })
class UserNode extends NodeEntity {
@Relationship({ name: 'PLACED', direction: 'out', model: () => OrderNode })
Orders!: Related;
}
@Node({ label: 'Order' })
class OrderNode extends NodeEntity {
@Relationship({ name: 'PLACED', direction: 'in', model: () => UserNode })
User!: Related;
}
// Register in any order
const Orders = neogma.model(OrderNode);
const Users = neogma.model(UserNode);
```
## The `Related` type [#the-related-type]
The `Related` type is used for relationship fields. It carries type information for the target model and optional relationship properties:
```typescript
import type { Related } from 'neogma';
import { defineRelationshipProperties, Type } from 'neogma';
// No relationship properties
Orders!: Related;
// With relationship properties (recommended):
const relProps = defineRelationshipProperties({
Rating: { property: 'rating', schema: Type.Number() },
});
RatedProducts!: Related;
// With explicit types:
RatedProducts!: Related<
typeof ProductNode,
{ Rating: number }, // properties used when creating the relationship
{ rating: number } // properties stored on the relationship
>;
```
## Registering models [#registering-models]
After defining your model classes, register them with your neogma instance using `neogma.model()`:
```typescript
const neogma = new Neogma({/* connection */});
const Orders = neogma.model(OrderNode);
const Users = neogma.model(UserNode);
```
The returned value is a fully typed `NeogmaModel` with all CRUD methods and relationship operations.
## Adding custom static and instance methods [#adding-custom-static-and-instance-methods]
Define static and instance methods directly on your class:
```typescript
@Node({ label: 'User' })
class UserNode extends NodeEntity {
@PrimaryKey(Type.String()) id!: string;
@Property(Type.String()) name!: string;
// Instance method
getDisplayName(): string {
return `User: ${this.name}`;
}
// Static method
static async findByName(this: typeof Users, name: string) {
return this.findOne({ where: { name } });
}
}
const Users = neogma.model(UserNode);
```
## Using legacy experimental decorators [#using-legacy-experimental-decorators]
If your project uses TypeScript's `experimentalDecorators` (common in NestJS projects), import from `neogma/legacy`:
```typescript
import {
Node,
PrimaryKey,
Property,
Relationship,
NodeEntity,
} from 'neogma/legacy';
import { Type } from 'neogma';
import type { Related } from 'neogma';
@Node({ label: 'User' })
class UserNode extends NodeEntity {
@PrimaryKey(Type.String())
id!: string;
@Property(Type.String())
name!: string;
}
```
The model definition syntax is identical. The only difference is the import path. Both produce the same `NeogmaModel` when registered.
# ModelFactory (JavaScript) (/docs/models/model-factory)
# ModelFactory [#modelfactory]
`ModelFactory` is the API for defining models in JavaScript projects where decorators are not available.
## Defining a model [#defining-a-model]
```javascript
const { Neogma, ModelFactory, Type } = require('neogma');
const neogma = new Neogma({
url: 'bolt://localhost:7687',
username: 'neo4j',
password: 'password',
});
const Users = ModelFactory(
{
label: 'User',
schema: {
id: Type.String(),
name: Type.String(),
age: Type.Optional(Type.Number()),
},
primaryKeyField: 'id',
relationships: {
Orders: {
model: () => Orders,
direction: 'out',
name: 'HAS_ORDER',
},
},
},
neogma,
);
// Create a user
const user = await Users.createOne({
id: '1',
name: 'Alice',
age: 30,
});
```
## Configuration options [#configuration-options]
| Option | Type | Description |
| ----------------- | -------------------- | ----------------------------------------- |
| `label` | `string \| string[]` | Neo4j label(s) for this node |
| `schema` | `object` | Property definitions with TypeBox schemas |
| `primaryKeyField` | `string` | The property used as the primary key |
| `relationships` | `object` | Relationship definitions |
| `statics` | `object` | Static methods added to the model |
| `methods` | `object` | Instance methods added to model instances |
For TypeScript projects, use the [decorator-based API](/docs/models/decorator-based) instead.
# Properties and Schema (/docs/models/properties-and-schema)
# Properties and schema [#properties-and-schema]
## Property types [#property-types]
Neo4j supports a specific set of property types. Neogma maps TypeScript types to their Neo4j equivalents:
| TypeScript | Neo4j | TypeBox |
| ----------- | --------------- | ---------------------------- |
| `string` | String | `Type.String()` |
| `number` | Integer / Float | `Type.Number()` |
| `boolean` | Boolean | `Type.Boolean()` |
| `string[]` | List of String | `Type.Array(Type.String())` |
| `number[]` | List of Number | `Type.Array(Type.Number())` |
| `boolean[]` | List of Boolean | `Type.Array(Type.Boolean())` |
## Primary key [#primary-key]
Every model must define a primary key field using the `@PrimaryKey` decorator. This field is used for lookups and relationship matching. `@PrimaryKey` also registers the field as a property, so you do not need to add `@Property` separately:
```typescript
@Node({ label: 'User' })
class UserNode extends NodeEntity {
@PrimaryKey(Type.String())
id!: string;
// ... other properties
}
```
You can pass a TypeBox schema for validation, or omit it for an unvalidated primary key:
```typescript
// With validation
@PrimaryKey(Type.String({ format: 'uuid' }))
id!: string;
// Without validation
@PrimaryKey()
id!: string;
```
## Required vs. optional properties [#required-vs-optional-properties]
Use TypeScript's optional field syntax alongside `Type.Optional()`:
```typescript
@Node({ label: 'User' })
class UserNode extends NodeEntity {
// Required: must be provided on create, always present on read
@PrimaryKey(Type.String())
id!: string;
@Property(Type.String({ minLength: 1 }))
name!: string;
// Optional: can be undefined
@Property(Type.Optional(Type.String()))
bio?: string;
@Property(Type.Optional(Type.Number()))
age?: number;
}
```
## Properties without validation [#properties-without-validation]
You can use `@Property()` without a schema argument. The property is tracked (included in creates and updates) but not validated:
```typescript
@Property()
externalId!: string; // tracked, but no validation
```
# Validation (/docs/models/validation)
# Validation [#validation]
Neogma validates node properties before they are persisted to Neo4j using [TypeBox](https://github.com/sinclairzx81/typebox) schemas.
## TypeBox validation [#typebox-validation]
TypeBox provides type-safe validation schemas. Import `Type` directly from neogma:
```typescript
import { Type } from 'neogma';
```
### Basic types [#basic-types]
```typescript
@Property(Type.String()) // must be a string
@Property(Type.Number()) // must be a number
@Property(Type.Boolean()) // must be a boolean
@Property(Type.Array(Type.String())) // must be an array of strings
```
### Constraints [#constraints]
```typescript
// String constraints
@Property(Type.String({ minLength: 3, maxLength: 100 }))
name!: string;
// Number constraints
@Property(Type.Number({ minimum: 0, maximum: 150 }))
age!: number;
// Pattern matching
@Property(Type.String({ pattern: '^[a-zA-Z0-9]+$' }))
username!: string;
```
### Optional properties [#optional-properties]
Use `Type.Optional()` for properties that may be undefined:
```typescript
@Property(Type.Optional(Type.String()))
nickname?: string;
@Property(Type.Optional(Type.Number({ minimum: 0 })))
score?: number;
```
### Enum values [#enum-values]
```typescript
@Property(Type.Union([
Type.Literal('active'),
Type.Literal('inactive'),
Type.Literal('pending'),
]))
status!: string;
```
### Relationship property validation [#relationship-property-validation]
TypeBox schemas can also validate relationship properties:
```typescript
@Relationship({
name: 'RATED',
direction: 'out',
model: () => ProductNode,
properties: [
{
property: 'rating',
alias: 'Rating',
schema: Type.Number({ minimum: 1, maximum: 5 }),
},
],
})
RatedProducts!: Related;
```
## Validation errors [#validation-errors]
When validation fails, neogma throws a `NeogmaInstanceValidationError`:
```typescript
import { NeogmaInstanceValidationError } from 'neogma';
try {
await Users.createOne({ id: '1', name: '' }); // fails: minLength: 1
} catch (error) {
if (error instanceof NeogmaInstanceValidationError) {
console.log(error.message);
console.log(error.errors); // detailed validation errors
}
}
```
# Clauses (/docs/query-builder/clauses)
# QueryBuilder clauses [#querybuilder-clauses]
All examples assume `Users` and `Orders` are registered models with labels `User` and `Order`.
## `match` [#match]
Match nodes and patterns. Returns `QueryBuilder` for chaining.
```typescript
// Match a node by model
qb.match({ identifier: 'u', model: Users });
// Generated: MATCH (u:`User`)
// Match with a where clause
qb.match({
identifier: 'u',
model: Users,
where: { name: 'Alice' },
});
// Generated: MATCH (u:`User` { name: $name })
// Params: { name: 'Alice' }
// Match a relationship pattern
qb.match({
related: [
{ identifier: 'u' },
{ direction: 'out', name: 'PLACED' },
{ identifier: 'o', model: Orders },
],
});
// Generated: MATCH (u)-[:PLACED]->(o:`Order`)
```
## `optionalMatch` [#optionalmatch]
Like `match`, but returns `null` for missing patterns instead of filtering out the row. Returns `QueryBuilder`.
```typescript
qb.optionalMatch({
identifier: 'u',
model: Users,
});
// Generated: OPTIONAL MATCH (u:`User`)
// Equivalent to:
qb.match({
optional: true,
identifier: 'u',
model: Users,
});
```
## `where` [#where]
Add WHERE conditions. Returns `QueryBuilder`.
```typescript
// String form (use $paramName for parameters)
qb.where('u.age >= $minAge');
// Generated: WHERE u.age >= $minAge
// Object form
qb.where({ identifier: 'u', property: 'status', value: 'active' });
// Generated: WHERE u.status = $status
// Params: { status: 'active' }
```
## `return` [#return]
Specify what to return from the query. Returns `QueryBuilder`.
```typescript
// String form
qb.return('u.name AS name, count(o) AS total');
// Generated: RETURN u.name AS name, count(o) AS total
// Array form
qb.return(['u', 'o']);
// Generated: RETURN u, o
// Object form with aliases
qb.return([{ identifier: 'u', property: 'name', alias: 'userName' }]);
// Generated: RETURN u.name AS userName
```
## `create` [#create]
Create nodes and relationships. Returns `QueryBuilder`.
```typescript
qb.create({
identifier: 'u',
label: 'User',
properties: { name: 'Alice', id: '1' },
});
// Generated: CREATE (u:`User` { name: $name, id: $id })
// Params: { name: 'Alice', id: '1' }
```
## `merge` [#merge]
Match an existing node or create it if it does not exist. Returns `QueryBuilder`.
```typescript
qb.merge('(u:User { id: $uid })');
// Generated: MERGE (u:User { id: $uid })
```
## `set` [#set]
Set properties on nodes or relationships. Returns `QueryBuilder`.
```typescript
qb.set('u.name = $newName');
// Generated: SET u.name = $newName
```
## `delete` [#delete]
Delete nodes or relationships. `detachDelete` also removes all relationships. Returns `QueryBuilder`.
```typescript
qb.delete('u');
// Generated: DELETE u
qb.detachDelete('u');
// Generated: DETACH DELETE u
```
## `remove` [#remove]
Remove properties or labels from a node. Returns `QueryBuilder`.
```typescript
qb.remove('u.tempField');
// Generated: REMOVE u.tempField
```
## `orderBy` [#orderby]
Order results by a property. Returns `QueryBuilder`.
```typescript
qb.orderBy('u.name ASC');
// Generated: ORDER BY u.name ASC
qb.orderBy('u.createdAt DESC');
// Generated: ORDER BY u.createdAt DESC
```
## `limit` [#limit]
Limit the number of returned results. Returns `QueryBuilder`.
```typescript
qb.limit(10);
// Generated: LIMIT 10
```
## `skip` [#skip]
Skip a number of results, typically used with `limit` for pagination. Returns `QueryBuilder`.
```typescript
qb.skip(20);
// Generated: SKIP 20
```
## `with` [#with]
Project intermediate results into new variables. Useful for aggregation or filtering between match clauses. Returns `QueryBuilder`.
```typescript
qb.with('u, count(o) AS orderCount');
// Generated: WITH u, count(o) AS orderCount
```
## `unwind` [#unwind]
Expand a list into individual rows. Returns `QueryBuilder`.
```typescript
qb.unwind('[1, 2, 3] AS num');
// Generated: UNWIND [1, 2, 3] AS num
```
## `forEach` [#foreach]
Iterate over a list and execute mutating operations for each element. Returns `QueryBuilder`.
```typescript
qb.forEach('(name IN $names | CREATE (:User { name: name }))');
// Generated: FOREACH (name IN $names | CREATE (:User { name: name }))
```
## `call` [#call]
Execute a subquery. The subquery must use the same `BindParam` instance as the parent. Returns `QueryBuilder`.
```typescript
const subquery = new QueryBuilder(bp)
.match({ identifier: 'o', model: Orders })
.return('o');
qb.call(subquery);
// Generated: CALL { MATCH (o:`Order`) RETURN o }
```
Subqueries passed to `.call()` must use the same `BindParam` instance as the
parent query to avoid parameter name collisions.
## `raw` [#raw]
Inject raw Cypher. Use sparingly and only with trusted values. Returns `QueryBuilder`.
```typescript
qb.raw('CALL db.labels() YIELD label RETURN label');
// Generated: CALL db.labels() YIELD label RETURN label
```
## Combining clauses [#combining-clauses]
Build complex queries by chaining multiple clauses. Here is a complete example with the generated Cypher:
```typescript
const bp = new BindParam({ minAge: 18, minOrders: 3 });
const qb = new QueryBuilder(bp)
.match({ identifier: 'u', model: Users })
.where('u.age >= $minAge')
.match({
related: [
{ identifier: 'u' },
{ direction: 'out', name: 'PLACED' },
{ identifier: 'o', model: Orders },
],
})
.with('u, count(o) AS orderCount')
.where('orderCount > $minOrders')
.return('u.name AS name, orderCount')
.orderBy('orderCount DESC')
.limit(10);
console.log(qb.getStatement());
// MATCH (u:`User`)
// WHERE u.age >= $minAge
// MATCH (u)-[:PLACED]->(o:`Order`)
// WITH u, count(o) AS orderCount
// WHERE orderCount > $minOrders
// RETURN u.name AS name, orderCount
// ORDER BY orderCount DESC
// LIMIT 10
const { records } = await qb.run(neogma.queryRunner);
// Params: { minAge: 18, minOrders: 3 }
```
## `getStatement` [#getstatement]
Returns the generated Cypher string as a `string`. Useful for debugging, logging, or inspecting what the QueryBuilder will execute.
```typescript
const qb = new QueryBuilder()
.match({ identifier: 'u', model: Users })
.return('u');
const statement = qb.getStatement();
console.log(statement);
// MATCH (u:`User`) RETURN u
```
## Using `getRelationshipByAlias` [#using-getrelationshipbyalias]
Returns the relationship configuration (name, direction) for a model's relationship alias. This avoids hardcoding relationship names and directions in QueryBuilder patterns.
```typescript
const qb = new QueryBuilder()
.match({
related: [
{ identifier: 'u', model: Users },
{ ...Users.getRelationshipByAlias('Orders'), identifier: 'r' },
{ identifier: 'o', model: Orders },
],
})
.return('u, r, o');
// Generated: MATCH (u:`User`)-[r:PLACED]->(o:`Order`) RETURN u, r, o
```
## `Literal` class [#literal-class]
Use `Literal` for raw Cypher expressions that should not be parameterized (e.g., Neo4j built-in functions like `datetime()`, `timestamp()`, `randomUUID()`).
```typescript
import { Literal, QueryBuilder, BindParam } from 'neogma';
const bp = new BindParam({
updatedAt: new Literal('datetime()'),
});
const qb = new QueryBuilder(bp)
.match({ identifier: 'u', model: Users })
.set('u.updatedAt = $updatedAt')
.return('u');
// Generated: MATCH (u:`User`) SET u.updatedAt = datetime() RETURN u
// Note: datetime() is injected directly, not as a parameter
```
`Literal` values are injected directly into the Cypher query without
parameterization. Only use `Literal` for trusted values like Neo4j built-in
functions. Never use it with user input.
# Query Builder (/docs/query-builder)
# Query Builder [#query-builder]
The `QueryBuilder` provides a fluent API for constructing Cypher queries programmatically. It handles parameter binding, prevents Cypher injection, and generates optimized queries.
## Basic usage [#basic-usage]
```typescript
import { QueryBuilder } from 'neogma';
const qb = new QueryBuilder()
.match({ identifier: 'u', model: Users })
.where('u.age >= 18')
.return('u');
// Generated Cypher: MATCH (u:`User`) WHERE u.age >= 18 RETURN u
const { records } = await qb.run(neogma.queryRunner);
// records is a neo4j-driver Record[] - each record has .get('u') etc.
```
## Chaining clauses [#chaining-clauses]
QueryBuilder methods return `this`, so you can chain them:
```typescript
const qb = new QueryBuilder()
.match({ identifier: 'u', model: Users })
.match({
related: [
{ identifier: 'u' },
{ direction: 'out', name: 'PLACED' },
{ identifier: 'o', model: Orders },
],
})
.where('o.status = $status')
.return('u.name AS name, count(o) AS orderCount')
.orderBy('orderCount DESC')
.limit(10);
```
## Running queries [#running-queries]
### With the query runner [#with-the-query-runner]
```typescript
const bindParam = new BindParam({ status: 'confirmed' });
const { records } = await qb.run(neogma.queryRunner, bindParam);
for (const record of records) {
console.log(record.get('name'));
console.log(record.get('orderCount').toNumber());
}
```
### Getting the Cypher string [#getting-the-cypher-string]
Use `getStatement()` to inspect the generated Cypher before executing. This is useful for debugging, logging, or building queries that you run through the Neo4j driver directly.
```typescript
const cypher = qb.getStatement();
// Returns: string - the generated Cypher query
console.log(cypher);
// MATCH (u:`User`) WHERE u.age >= 18 RETURN u
```
## BindParam [#bindparam]
`BindParam` manages query parameters to prevent Cypher injection:
```typescript
import { BindParam } from 'neogma';
// Create with initial values
const bp = new BindParam({ minAge: 18, status: 'active' });
// Generate unique parameter names
const paramName = bp.getUniqueName('score');
bp.add({ [paramName]: 100 });
// Get all parameters
console.log(bp.get()); // { minAge: 18, status: 'active', score: 100 }
```
### Shared BindParam [#shared-bindparam]
When using subqueries (e.g., `CALL`), the subquery must share the parent's `BindParam` to avoid parameter name collisions:
```typescript
const bp = new BindParam({ minAge: 18 });
const subquery = new QueryBuilder(bp)
.match({ identifier: 'o', model: Orders })
.return('o');
const qb = new QueryBuilder(bp)
.match({ identifier: 'u', model: Users })
.call(subquery)
.return('u, o');
```
## Available clauses [#available-clauses]
See [Clauses](/docs/query-builder/clauses) for a full list of available QueryBuilder methods.
# Parameters (/docs/query-builder/parameters)
# Query parameters [#query-parameters]
## Why use parameters? [#why-use-parameters]
All user input should go through parameterized queries to prevent Cypher injection attacks. Neogma's `BindParam` class handles this automatically.
## Using BindParam [#using-bindparam]
```typescript
import { BindParam, QueryBuilder } from 'neogma';
// Create with initial parameters
const bp = new BindParam({ minAge: 18, status: 'active' });
const qb = new QueryBuilder()
.match({ identifier: 'u', model: Users })
.where('u.age >= $minAge AND u.status = $status')
.return('u');
const { records } = await qb.run(neogma.queryRunner, bp);
```
## Generating unique names [#generating-unique-names]
When building dynamic queries, use `getUniqueName` to avoid parameter name collisions:
```typescript
const bp = new BindParam();
const nameParam = bp.getUniqueName('name');
bp.add({ [nameParam]: 'Alice' });
const ageParam = bp.getUniqueName('age');
bp.add({ [ageParam]: 30 });
const qb = new QueryBuilder()
.match({ identifier: 'u', model: Users })
.where(`u.name = $${nameParam} AND u.age = $${ageParam}`)
.return('u');
```
## Getting the parameter map [#getting-the-parameter-map]
```typescript
const bp = new BindParam({ x: 1, y: 2 });
console.log(bp.get()); // { x: 1, y: 2 }
```
## Raw queries with parameters [#raw-queries-with-parameters]
You can also use parameters with the query runner directly:
```typescript
const result = await neogma.queryRunner.run(
'MATCH (u:User) WHERE u.age >= $minAge RETURN u.name AS name',
{ minAge: 18 },
);
```
# Creating Relationships (/docs/relationships/creating-relationships)
# Creating relationships [#creating-relationships]
## During node creation [#during-node-creation]
When creating a node that references another node that does not exist yet, you can create both the node and the relationship in a single operation. Pass related node data in the create payload:
```typescript
// Create a user and their orders in a single operation
const user = await Users.createOne({
id: '1',
name: 'Alice',
Orders: {
attributes: [
{ id: 'order-1', status: 'confirmed' },
{ id: 'order-2', status: 'pending' },
],
},
});
```
This creates the User node, both Order nodes, and the `PLACED` relationships in a single Cypher statement.
### With relationship properties [#with-relationship-properties]
When the relationship has properties, include them alongside each target node:
```typescript
const user = await Users.createOne({
id: '1',
name: 'Alice',
Orders: {
attributes: [{ id: 'order-1', status: 'confirmed' }],
properties: [{ Quantity: 3, PlacedAt: '2024-01-15' }],
},
});
```
The `properties` array is positional - each entry corresponds to the node at the same index in `attributes`.
## Using `relateTo` [#using-relateto]
If the target node already exists, `relateTo` creates a relationship to it by matching on properties. Use the `relateTo` instance method:
```typescript
const user = await Users.findOne({ where: { id: '1' } });
const order = await Orders.findOne({ where: { id: 'order-5' } });
if (user && order) {
const result = await user.relateTo({
alias: 'Orders',
where: { id: order.id },
});
console.log(result.summary.counters.updates().relationshipsCreated); // 1
}
```
### With relationship properties [#with-relationship-properties-1]
Attach properties to the relationship by passing a `properties` object with aliased keys:
```typescript
const result = await user.relateTo({
alias: 'Orders',
where: { id: order.id },
properties: { Quantity: 2 },
});
console.log(result.summary.counters.updates().relationshipsCreated); // 1
```
## Using `createRelationship` (static) [#using-createrelationship-static]
The static `createRelationship` method creates a relationship between nodes matched by their properties and returns a count of created relationships:
```typescript
const count = await Users.createRelationship({
source: { label: 'User' },
target: { label: 'Order' },
relationship: { name: 'PLACED', direction: 'out' },
where: {
source: { id: '1' },
target: { id: 'order-5' },
},
});
console.log(count); // 1
```
## Bulk creation with `createMany` [#bulk-creation-with-createmany]
Create multiple nodes along with their relationships in a single call, where each item can include nested related nodes:
```typescript
await Users.createMany([
{
id: '1',
name: 'Alice',
Orders: {
attributes: [{ id: 'order-1', status: 'confirmed' }],
},
},
{
id: '2',
name: 'Bob',
Orders: {
attributes: [
{ id: 'order-2', status: 'pending' },
{ id: 'order-3', status: 'confirmed' },
],
},
},
]);
```
# Defining Relationships (/docs/relationships/defining-relationships)
# Defining relationships [#defining-relationships]
Relationships in Neo4j connect nodes. In neogma, you define them on your model classes using the `@Relationship` decorator.
```typescript
import {
Node,
PrimaryKey,
Property,
Relationship,
NodeEntity,
Type,
} from 'neogma';
import type { Related } from 'neogma';
@Node({ label: 'Order' })
class OrderNode extends NodeEntity {
@PrimaryKey(Type.String()) id!: string;
@Property(Type.String()) status!: string;
}
@Node({ label: 'User' })
class UserNode extends NodeEntity {
@PrimaryKey(Type.String()) id!: string;
@Property(Type.String()) name!: string;
@Relationship({
name: 'PLACED',
direction: 'out',
model: () => OrderNode,
})
Orders!: Related;
}
```
## Direction [#direction]
The `direction` option specifies the relationship direction from the perspective of the current node:
* `'out'` - the relationship goes **from** this node **to** the target: `(User)-[:PLACED]->(Order)`
* `'in'` - the relationship goes **from** the target **to** this node: `(User)<-[:PLACED]-(Order)`
* `'none'` - no direction: `(User)-[:PLACED]-(Order)`
## Relationship properties [#relationship-properties]
Relationships can carry their own properties. Define them using the object syntax (recommended) or the array syntax.
### Object syntax (recommended) [#object-syntax-recommended]
Keys are aliases. Each value is either `{ property, schema? }` when the alias differs from the Neo4j property name, or a bare TypeBox schema as shorthand when they match:
```typescript
@Relationship({
name: 'PLACED',
direction: 'out',
model: () => OrderNode,
properties: {
// Full form: alias "PlacedAt" maps to Neo4j property "placedAt"
PlacedAt: { property: 'placedAt', schema: Type.String() },
// Full form: alias "Quantity" maps to Neo4j property "quantity"
Quantity: { property: 'quantity', schema: Type.Number({ minimum: 1 }) },
},
})
Orders!: Related<
typeof OrderNode,
{ PlacedAt: string; Quantity: number }, // CreateRelationshipProperties
{ placedAt: string; quantity: number } // RelationshipProperties
>;
```
When the alias and the Neo4j property name are the same, you can use the shorthand form with just the schema:
```typescript
@Relationship({
name: 'PLACED',
direction: 'out',
model: () => OrderNode,
properties: {
// Shorthand: alias and property name are both "quantity"
quantity: Type.Number({ minimum: 1 }),
},
})
Orders!: Related;
```
### Array syntax (deprecated) [#array-syntax-deprecated]
The array syntax is still supported but deprecated. It will emit a one-time console warning at model registration time. Prefer the object syntax above.
```typescript
@Relationship({
name: 'PLACED',
direction: 'out',
model: () => OrderNode,
properties: [
{
property: 'placedAt', // Neo4j property name
alias: 'PlacedAt', // alias used in create/update operations
schema: Type.String(), // optional TypeBox validation
},
{
property: 'quantity',
alias: 'Quantity',
schema: Type.Number({ minimum: 1 }),
},
],
})
Orders!: Related<
typeof OrderNode,
{ PlacedAt: string; Quantity: number }, // CreateRelationshipProperties
{ placedAt: string; quantity: number } // RelationshipProperties
>;
```
The `Related` type accepts three type parameters:
| Parameter | Purpose | Example |
| ----------------------------------- | ---------------------------------------------------------------- | ---------------------- |
| 1st: Target model | The decorated class of the target node | `typeof OrderNode` |
| 2nd: `CreateRelationshipProperties` | Shape used when creating relationships (uses **aliases**) | `{ PlacedAt: string }` |
| 3rd: `RelationshipProperties` | Shape of properties as stored in Neo4j (uses **property names**) | `{ placedAt: string }` |
The second parameter (aliases) is what you pass to `relateTo()` or nested `createOne()`. The third parameter (property names) is what you get back when reading relationships from the database.
If your relationship has no properties, use `Related` with no extra parameters.
## Bidirectional relationships [#bidirectional-relationships]
Define the relationship on both sides to enable navigation in either direction:
```typescript
@Node({ label: 'User' })
class UserNode extends NodeEntity {
@Relationship({ name: 'PLACED', direction: 'out', model: () => OrderNode })
Orders!: Related;
}
@Node({ label: 'Order' })
class OrderNode extends NodeEntity {
@Relationship({ name: 'PLACED', direction: 'in', model: () => UserNode })
PlacedBy!: Related;
}
```
## Self-referencing relationships [#self-referencing-relationships]
Use `'self'` as the model reference:
```typescript
@Node({ label: 'Category' })
class CategoryNode extends NodeEntity {
@PrimaryKey(Type.String()) id!: string;
@Property(Type.String()) name!: string;
@Relationship({
name: 'PARENT_OF',
direction: 'out',
model: 'self',
})
Children!: Related;
}
```
## Registration order [#registration-order]
Models can be registered in any order. Neogma handles circular dependencies automatically through deferred relationship resolution:
```typescript
// These can be registered in any order
const Users = neogma.model(UserNode);
const Orders = neogma.model(OrderNode);
```
When a model references another model that has not been registered yet, neogma queues the relationship and patches it when the target model is registered. This means circular references (User -> Order -> User) work without any special handling.
The `model: () => OtherNode` arrow function syntax enables this lazy resolution - the reference is only evaluated during `neogma.model()`, not when the class is defined.
# Eager Loading (/docs/relationships/eager-loading)
# Eager loading [#eager-loading]
Eager loading lets you fetch nodes along with their related nodes in a single query, avoiding N+1 query problems.
## Basic eager loading [#basic-eager-loading]
Pass a `relationships` option to `findMany` or `findOne`:
```typescript
const users = await Users.findMany({
where: { name: 'Alice' },
relationships: {
Orders: true, // load all related Orders
},
});
for (const user of users) {
for (const order of user.Orders) {
console.log(order.node.status); // target node properties
console.log(order.relationship); // relationship properties (if any)
}
}
```
## Filtering related nodes [#filtering-related-nodes]
Filter which related nodes are loaded:
```typescript
const users = await Users.findMany({
relationships: {
Orders: {
where: {
target: { status: 'confirmed' },
},
},
},
});
```
## Ordering and pagination [#ordering-and-pagination]
Apply ordering, limits, and skip to related nodes:
```typescript
const users = await Users.findMany({
relationships: {
Orders: {
order: [{ on: 'target', property: 'createdAt', direction: 'DESC' }],
limit: 5,
skip: 0,
},
},
});
```
## Nested relationships [#nested-relationships]
Load relationships to arbitrary depth:
```typescript
const users = await Users.findMany({
relationships: {
Orders: {
relationships: {
Items: {
limit: 10,
relationships: {
Tags: true,
},
},
},
},
},
});
// Access deeply nested data
const tag = users[0].Orders[0].node.Items[0].node.Tags[0].node;
```
## Result shape [#result-shape]
Each eager-loaded relationship returns an array of objects with two properties:
* `node` - the related node's properties (as a model instance)
* `relationship` - the relationship's properties (if any)
```typescript
const users = await Users.findMany({
relationships: {
Orders: {
where: { target: { status: 'confirmed' } },
},
},
});
// Type: { node: OrderInstance; relationship: { rating?: number } }[]
const orders = users[0].Orders;
```
## Performance [#performance]
Eager loading uses `CALL` subqueries internally, so all data is fetched in a single database round-trip. This is significantly more efficient than making separate queries for each relationship.
# Managing Relationships (/docs/relationships/managing-relationships)
# Managing relationships [#managing-relationships]
## `findRelationships` (static) [#findrelationships-static]
Returns an array of `{ source, target, relationship }` objects matching the given criteria. Each entry contains the source node instance, target node instance, and the relationship properties object.
```typescript
const relationships = await Users.findRelationships({
alias: 'Orders',
where: {
source: { id: '1' },
target: { status: 'confirmed' },
},
});
// Returns: Array<{ source: UserInstance, target: OrderInstance, relationship: RelProps }>
for (const rel of relationships) {
console.log(rel.source.id); // source node properties
console.log(rel.target.status); // target node properties
console.log(rel.relationship); // relationship properties (e.g. { quantity: 5 })
}
```
Returns an empty array if no matching relationships are found.
### Filtering by relationship properties [#filtering-by-relationship-properties]
```typescript
import { Op } from 'neogma';
const relationships = await Users.findRelationships({
alias: 'Orders',
where: {
source: { id: '1' },
relationship: { quantity: { [Op.gte]: 5 } },
},
});
```
## Deleting relationships [#deleting-relationships]
### `deleteRelationships` (static) [#deleterelationships-static]
Deletes relationships matching the given criteria. Returns the number of deleted relationships as a `number`.
```typescript
const deletedCount = await Users.deleteRelationships({
alias: 'Orders',
where: {
source: { id: '1' },
target: { id: 'order-5' },
},
});
// Returns: number - count of deleted relationships
console.log(`Deleted ${deletedCount} relationships`);
```
### Delete all relationships of a type [#delete-all-relationships-of-a-type]
Omit the `target` filter to delete all relationships of that type from the source node. This removes every relationship matching the alias from the specified source, regardless of target.
```typescript
const deletedCount = await Users.deleteRelationships({
alias: 'Orders',
where: {
source: { id: '1' },
},
});
// Deletes ALL 'PLACED' relationships from user '1'
```
## Updating relationship properties [#updating-relationship-properties]
### `updateRelationship` (static) [#updaterelationship-static]
Updates properties on existing relationships that match the given criteria and returns a tuple of `[relationships[], QueryResult]`. Matches by source and target node, then sets the specified properties on the relationship.
```typescript
const [relationships, queryResult] = await Users.updateRelationship({
alias: 'Orders',
where: {
source: { id: '1' },
target: { id: 'order-1' },
},
properties: {
quantity: 10,
},
});
console.log(queryResult.summary.counters.updates().propertiesSet); // 1
```
By default the `relationships` array is empty. Pass `return: true` to populate it with the updated relationship properties:
```typescript
const [relationships] = await Users.updateRelationship({
alias: 'Orders',
where: {
source: { id: '1' },
target: { id: 'order-1' },
},
properties: {
quantity: 10,
},
return: true,
});
console.log(relationships[0].quantity); // 10
```
If no relationship matches the `where` criteria, no update is performed and no error is thrown.
# Sessions and Transactions (/docs/sessions-and-transactions)
# Sessions and transactions [#sessions-and-transactions]
Neogma provides instance methods for managing Neo4j sessions and transactions. These handle lifecycle, cleanup, and error handling automatically.
## Sessions [#sessions]
Use `neogma.getSession` to acquire a session that is automatically closed when the callback completes:
```typescript
await neogma.getSession(async (session) => {
const user = await Users.createOne({ id: '1', name: 'Alice' }, { session });
console.log(user.name);
});
// session is closed automatically
```
## Transactions [#transactions]
Use `neogma.getTransaction` for operations that should be atomic. If the callback throws, the transaction is rolled back:
```typescript
await neogma.getTransaction(async (tx) => {
await Users.createOne({ id: '1', name: 'Alice' }, { session: tx });
await Orders.createOne({ id: 'order-1', status: 'pending' }, { session: tx });
// Both operations commit together, or both roll back on error
});
```
### Rollback on error [#rollback-on-error]
```typescript
try {
await neogma.getTransaction(async (tx) => {
await Users.createOne({ id: '1', name: 'Alice' }, { session: tx });
throw new Error('Something went wrong');
// Transaction is automatically rolled back
});
} catch (error) {
console.log('Transaction rolled back:', error.message);
}
```
## Runnables [#runnables]
`neogma.getRunnable` returns either a session or transaction depending on what was passed:
```typescript
async function createUser(
data: { id: string; name: string },
sessionOrTx?: any,
) {
return neogma.getRunnable(
sessionOrTx,
async (runnable) => {
return Users.createOne(data, { session: runnable });
},
);
}
// Works with or without a transaction
await createUser({ id: '1', name: 'Alice' }); // uses its own session
await neogma.getTransaction(async (tx) => {
await createUser({ id: '2', name: 'Bob' }, tx); // uses the transaction
});
```
## Passing sessions to model operations [#passing-sessions-to-model-operations]
Most model methods accept an optional `session` parameter:
```typescript
await neogma.getSession(async (session) => {
// Create
await Users.createOne({ id: '1', name: 'Alice' }, { session });
// Find
const users = await Users.findMany({ where: { name: 'Alice' }, session });
// Update
await Users.update({ name: 'Alicia' }, { where: { id: '1' }, session });
// Delete
await Users.delete({ where: { id: '1' }, detach: true, session });
});
```
## Database selection [#database-selection]
Specify a database when creating the Neogma instance:
```typescript
const neogma = new Neogma({
url: 'bolt://localhost:7687',
username: 'neo4j',
password: 'password',
database: 'my-database',
});
```
# Creating Nodes (/docs/crud-operations/creating-nodes)
# Creating nodes [#creating-nodes]
## `createOne` [#createone]
Create a single node:
```typescript
const user = await Users.createOne({
id: '1',
name: 'Alice',
age: 30,
});
console.log(user.id); // '1'
console.log(user.name); // 'Alice'
```
The returned value is a model instance with all properties and instance methods.
### With relationships [#with-relationships]
Create a node along with related nodes in a single operation:
```typescript
const user = await Users.createOne({
id: '1',
name: 'Alice',
Orders: {
attributes: [
{ id: 'order-1', status: 'confirmed' },
{ id: 'order-2', status: 'pending' },
],
},
});
console.log(user.id); // '1'
```
### With relationship properties [#with-relationship-properties]
Attach properties to the relationship itself by providing a `properties` array, where each entry corresponds to the node at the same index in `attributes`:
```typescript
const user = await Users.createOne({
id: '1',
name: 'Alice',
Orders: {
attributes: [{ id: 'order-1', status: 'confirmed' }],
properties: [{ Rating: 5, Quantity: 2 }],
},
});
console.log(user.name); // 'Alice'
```
### Using sessions and transactions [#using-sessions-and-transactions]
Pass a session or transaction to any create operation:
```typescript
await neogma.getSession(async (session) => {
const user = await Users.createOne({ id: '1', name: 'Alice' }, { session });
});
```
## `createMany` [#createmany]
Create multiple nodes at once:
```typescript
const users = await Users.createMany([
{ id: '1', name: 'Alice', age: 30 },
{ id: '2', name: 'Bob', age: 25 },
{ id: '3', name: 'Charlie', age: 35 },
]);
console.log(users.length); // 3
```
Each item can include nested relationships just like `createOne`.
## Merge (create or update) [#merge-create-or-update]
To create a node only if it does not already exist (matching by primary key), use the `merge` option:
```typescript
const user = await Users.createOne({ id: '1', name: 'Alice' }, { merge: true });
```
This generates a `MERGE` statement instead of `CREATE`.
# Deleting Nodes (/docs/crud-operations/deleting-nodes)
# Deleting nodes [#deleting-nodes]
## Instance `delete` [#instance-delete]
Delete a specific node instance and return the number of deleted nodes:
```typescript
const user = await Users.findOne({ where: { id: '1' } });
if (user) {
const deletedCount = await user.delete();
console.log(deletedCount); // 1
}
```
### Detach delete [#detach-delete]
By default, Neo4j cannot delete a node that has relationships. Use `detach: true` to delete the node and all its relationships:
```typescript
const deletedCount = await user.delete({ detach: true });
console.log(deletedCount); // 1
```
## Static `delete` [#static-delete]
Delete nodes matching a condition:
```typescript
const deletedCount = await Users.delete({
where: { status: 'inactive' },
detach: true,
});
console.log(`Deleted ${deletedCount} nodes`);
```
The static `delete` returns the count of deleted nodes.
### Using sessions and transactions [#using-sessions-and-transactions]
```typescript
await neogma.getTransaction(async (tx) => {
await Users.delete({
where: { status: 'archived' },
detach: true,
session: tx,
});
});
```
# Finding Nodes (/docs/crud-operations/finding-nodes)
# Finding nodes [#finding-nodes]
## `findOne` [#findone]
Find a single node matching the given criteria:
```typescript
const user = await Users.findOne({
where: { id: '1' },
});
if (user) {
console.log(user.name);
}
```
Returns the first matching instance, or `null` if no match is found.
## `findMany` [#findmany]
Find multiple nodes matching a filter and return them as an array of model instances:
```typescript
const users = await Users.findMany({
where: { status: 'active' },
});
console.log(users.length); // number of matching nodes
```
### Ordering [#ordering]
Sort results by one or more properties:
```typescript
const users = await Users.findMany({
where: { status: 'active' },
order: [['name', 'ASC']],
});
console.log(users[0].name); // first user alphabetically
```
### Pagination [#pagination]
Combine `limit` and `skip` to paginate through results:
```typescript
const users = await Users.findMany({
where: { status: 'active' },
order: [['name', 'ASC']],
limit: 10,
skip: 20,
});
console.log(users.length); // up to 10
```
## Where operators [#where-operators]
Neogma uses Symbol-based operators via the `Op` object. Import it from neogma:
```typescript
import { Op } from 'neogma';
```
| Operator | Description | Example |
| --------------- | --------------------- | ------------------------------------------------ |
| (direct value) | Equals | `{ name: 'Alice' }` |
| `[Op.eq]` | Equals (explicit) | `{ age: { [Op.eq]: 30 } }` |
| `[Op.ne]` | Not equals | `{ status: { [Op.ne]: 'inactive' } }` |
| `[Op.gt]` | Greater than | `{ age: { [Op.gt]: 18 } }` |
| `[Op.gte]` | Greater than or equal | `{ age: { [Op.gte]: 18 } }` |
| `[Op.lt]` | Less than | `{ age: { [Op.lt]: 65 } }` |
| `[Op.lte]` | Less than or equal | `{ age: { [Op.lte]: 65 } }` |
| `[Op.in]` | In list | `{ status: { [Op.in]: ['active', 'pending'] } }` |
| `[Op.contains]` | Contains substring | `{ name: { [Op.contains]: 'ali' } }` |
| `[Op.is]` | IS (for null checks) | `{ deletedAt: { [Op.is]: null } }` |
| `[Op.isNot]` | IS NOT | `{ deletedAt: { [Op.isNot]: null } }` |
### Combined operators [#combined-operators]
Apply multiple operators to the same property to create range queries:
```typescript
const users = await Users.findMany({
where: {
age: { [Op.gte]: 18, [Op.lte]: 65 },
status: 'active',
},
});
console.log(users.length); // users aged 18-65
```
## Using sessions and transactions [#using-sessions-and-transactions]
Pass a session or transaction to run the query within a managed session:
```typescript
await neogma.getSession(async (session) => {
const users = await Users.findMany({
where: { status: 'active' },
session,
});
console.log(users.length);
});
```
## With eager loading [#with-eager-loading]
Load related nodes alongside the query in a single database round-trip:
```typescript
const users = await Users.findMany({
where: { status: 'active' },
relationships: {
Orders: {
where: { target: { status: 'confirmed' } },
limit: 5,
},
},
});
```
See [Eager Loading](/docs/relationships/eager-loading) for more details.
# Updating Nodes (/docs/crud-operations/updating-nodes)
# Updating nodes [#updating-nodes]
## Instance `save` [#instance-save]
Modify an instance's properties and persist the changes:
```typescript
const user = await Users.findOne({ where: { id: '1' } });
if (user) {
user.name = 'Alicia';
user.age = 31;
await user.save();
}
```
The `save` method only updates properties that have changed. It also validates the instance against its schema before persisting, so any constraint violations will throw before the database is touched.
## Instance `getDataValues` [#instance-getdatavalues]
Get all current property values of an instance as a plain object:
```typescript
const user = await Users.findOne({ where: { id: '1' } });
const data = user?.getDataValues();
// { id: '1', name: 'Alice', age: 30 }
```
## Instance `validate` [#instance-validate]
Manually validate an instance against its schema:
```typescript
const user = await Users.findOne({ where: { id: '1' } });
user.name = ''; // violates minLength constraint
try {
await user.validate();
} catch (err) {
console.log(err.message); // Validation error
}
```
Validation also runs automatically during `save()`. Use `validate()` when you want to check without persisting.
## Static `update` [#static-update]
Update multiple nodes matching a condition:
```typescript
const [updatedInstances, queryResult] = await Users.update(
{ status: 'inactive' }, // properties to set
{ where: { age: { [Op.lt]: 18 } } }, // match condition
);
const propsSet = queryResult.summary.counters.updates().propertiesSet;
console.log(`Updated ${propsSet} properties`);
```
The static `update` returns a tuple of `[instances[], QueryResult]`.
### Using sessions and transactions [#using-sessions-and-transactions]
```typescript
await neogma.getTransaction(async (tx) => {
await Users.update(
{ status: 'archived' },
{ where: { lastLogin: { [Op.lt]: '2023-01-01' } }, session: tx },
);
});
```