Overview Defining a Model Instances Creating Nodes and Relationships Merging Nodes and Relationships Updating Nodes and Relationships Deleting Nodes Deleting Relationships Finding Nodes and Relationships Hooks Temporary Databases
An instance of the Where class can be used to easily create a statement and a bind parameter to be used in a query.
When using TypeScript with Models, where parameters are type-checked to ensure both property names AND value types match your model's schema. This catches typos and type mismatches at compile time rather than runtime.
1linkimport { Op } from 'neogma';
2link
3link// Assuming Users is a Model with properties: { id: string, name: string, age: number }
4link
5link// Valid - correct property names and matching value types
6linkawait Users.findMany({
7link where: { name: 'John', age: 25 }
8link});
9link
10link// TypeScript Error - 'nam' is not a valid property
11linkawait Users.findMany({
12link where: { nam: 'John' } // Error: 'nam' does not exist in type
13link});
14link
15link// TypeScript Error - age expects number, not string
16linkawait Users.findMany({
17link where: { age: 'twenty-five' } // Error: string is not assignable to number
18link});
19link
20link// TypeScript Error - operators also validate value types
21linkawait Users.findMany({
22link where: { age: { [Op.gt]: '18' } } // Error: Op.gt expects number, not string
23link});
For findRelationships, relateTo, and similar methods, the type system validates property names for source, target, and relationship separately:
1linkimport { Op } from 'neogma';
2link
3link// Assuming Users is a Model with an 'Orders' relationship
4linkawait Users.findRelationships({
5link alias: 'Orders',
6link where: {
7link source: { name: 'John' }, // User property
8link target: { orderNumber: 'ORD-123' }, // Order property
9link relationship: { rating: 5 }, // Relationship property
10link },
11link});
12link
13link// TypeScript Error - wrong property names
14linkawait Users.findRelationships({
15link alias: 'Orders',
16link where: {
17link source: { userName: 'John' }, // Error: should be 'name'
18link target: { grpName: 'Test' }, // Error: 'grpName' doesn't exist
19link },
20link});
This type safety works with all where operators (Op.eq, Op.in, Op.gt, Op.is, etc.) and validates both property names and value types at any nesting level.
When using where parameters, null and undefined are handled differently:
undefined: The property is ignored (no filter is applied for that property)null: Generates IS NULL check (filters for null values in the database)1link// undefined is ignored - no filter for 'deleted'
2linkawait Users.findMany({
3link where: { name: 'John', deleted: undefined }
4link});
5link// Generates: WHERE n.name = $name
6link
7link// null generates IS NULL check
8linkawait Users.findMany({
9link where: { name: 'John', deleted: null }
10link});
11link// Generates: WHERE n.name = $name AND n.deleted IS NULL
Creating a Where instance with values for the identifier n
1linkconst where = new Where({
2link /* --> the node identifier is the key */
3link n: {
4link /* --> the per-identifier where values are used */
5link x: 5,
6link y: 'bar'
7link }
8link});
9link
10link/* --> a statement can be generated to be used in the query */
11linkconsole.log(where.getStatement('text')); // "n.x = $x AND n.y = $y"
12link/* --> the "bindParam" property has a BindParam instance whose values can be used in the query */
13linkconsole.log(where.bindParam.get()); // { x: 5, y: 'bar' }
Creating a Where instance with values for the identifiers n and o
1linkconst where = new Where({
2link n: {
3link x: 5,
4link y: 'bar'
5link },
6link o: {
7link z: true,
8link }
9link});
10link
11linkconsole.log(where.getStatement('text')); // "n.x = $x AND n.y = $y AND o.z = $z"
12linkconsole.log(where.bindParam.get()); // { x: 5, y: 'bar', z: true }
Since Where uses a BindParam, non-unique keys can be used which will be automatically associated with a unique key
1linkconst where = new Where({
2link n: {
3link x: 5,
4link y: 'bar'
5link },
6link o: {
7link /* --> since it's for a different identifier, we can use any key we want, even if it's used in another identifier */
8link x: true,
9link }
10link});
11link
12linkconsole.log(where.getStatement('text')); // "n.x = $x AND n.y = $y AND o.x = $x__aaaa"
13linkconsole.log(where.bindParam.get()); // { x: 5, y: 'bar', x_aaaa: true }
An existing BindParam instance can be used, to ensure unique keys. The same instance will be used in the Where instance, so it will be mutated.
1linkconst existingBindParam = new BindParam({
2link x: 4,
3link});
4link
5linkconst where = new Where(
6link {
7link n: {
8link /* --> the same key as in the bind param can be used */
9link x: 5,
10link y: 'bar'
11link }
12link },
13link existingBindParam
14link);
15link
16link/* --> the "x" key already exists in the bind param, so a new one is used */
17linkconsole.log(where.getStatement('text')); // "n.x = $x__aaaa AND n.y = $y"
18linkconsole.log(where.bindParam.get()); // { x: 4, x_aaaa: 5, y: 'bar' }
19linkconsole.log(where.bindParam === existingBindParam); // true
An existing Where instance can be used. In this case, the parameters of it will be merged with the one that's being created. The existing Where instance won't be mutated at all.
1linkconst existingWhere = new Where(
2link {
3link n: {
4link x: 4
5link },
6link o: {
7link z: true
8link }
9link }
10link);
11link
12linkconsole.log(existingWhere.getStatement('text')); // "n.x = $x AND o.z = $z"
13linkconsole.log(existingWhere.bindParam.get()); // { x: 4, z: true }
14link
15linkconst newWhere = new Where(
16link {
17link n: {
18link y: 'bar'
19link },
20link m: {
21link z: 'foo'
22link }
23link },
24link existingWhere
25link);
26link
27linkconsole.log(newWhere.getStatement('text')); // "n.x = $x AND n.y = $y AND o.z = $z AND m.z = $z__aaaa"
28linkconsole.log(newWhere.bindParam.get()); // { x: 4, y: 'bar', z: true, z__aaaa: 'foo' }
Similarly to creating a new Where instance, parameters can be added to an existing instance.
1linkconst where = new Where({
2link n: {
3link x: 5,
4link y: 'bar'
5link }
6link});
7link
8linkwhere.addParams({
9link n: {
10link z: true
11link },
12link o: {
13link x: 4
14link }
15link});
16link
17linkconsole.log(where.getStatement('text')); // "n.x = $x AND n.y = $y AND n.z = $z AND o.x = $x__aaaa"
18linkconsole.log(where.bindParam.get()); // { x: 5, y: 'bar', z: true, x__aaaa: 4 }
A Where instance can easily be used in a query using its statement and bindParam properties
This is how the statement can be used in a "text" form
1linkconst where = new Where({
2link n: {
3link x: 5,
4link y: 'bar'
5link },
6link o: {
7link z: true,
8link }
9link});
10link
11linkconst textStatement = where.getStatement('text'); // n.x = $x AND n.y = $y AND o.z = $z
12linkconst bindParamProperties = where.bindParam.get(); // { x: 5, y: 'bar', z: true }
13link
14linkawait queryRunner.run(
15link `MATCH (n), (o) WHERE ${textStatement} RETURN n, o`,
16link bindParamProperties
17link);
This is how the statement can be used in an "object" form
1linkconst where = new Where({
2link n: {
3link x: 5,
4link y: 'bar'
5link },
6link});
7link
8linkconst objectStatement = where.getStatement('object'); // { x: $x, y: $y }
9linkconst bindParamProperties = where.bindParam.get(); // { x: 5, y: 'bar' }
10link
11linkawait queryRunner.run(
12link `MATCH (n ${objectStatement}) RETURN n`,
13link bindParamProperties
14link);
This ignores the identifier, and is only available for the "equals" operator. So, it's recommended that it's used with only 1 identifier.
While some of the operators can be used with plain objects, some others need to use the exported Op variable. It contains symbols for the operators.
| Operator | Cypher | Description | Available For |
|---|---|---|---|
Op.eq | = / IS NULL | Equality (null → IS NULL) | All types |
Op.ne | <> / IS NOT NULL | Not equal (null → IS NOT NULL) | All types |
Op.in | property IN [values] | Property is one of values | All types |
Op._in | value IN property | Value exists in array property | All types (use for array membership) |
Op.gt | > | Greater than | Scalar types only |
Op.gte | >= | Greater than or equal | Scalar types only |
Op.lt | < | Less than | Scalar types only |
Op.lte | <= | Less than or equal | Scalar types only |
Op.contains | CONTAINS | Substring matching | String only |
Op.is | IS NULL | Property is null | All types |
Op.isNot | IS NOT NULL | Property is not null | All types |
null | IS NULL | Shorthand for { [Op.is]: null } | All types |
Type Safety Note: When using TypeScript with typed models, operators are constrained to appropriate types. For example,
Op.containsonly accepts string values and is only valid for string properties.
A direct object value corresponds to equality.
1linkconst where = new Where({
2link n: {
3link x: 5
4link },
5link});
6link
7linkconsole.log(where.getStatement('text')); // n.x = $x
8linkconsole.log(where.getStatement('object')); // { x: $x }
9linkconsole.log(where.bindParam.get()); // { x: 5 }
Alternatively, the operator "eq" can be used.
1linkconst where = new Where({
2link n: {
3link x: {
4link [Op.eq]: 5,
5link }
6link },
7link});
8link
9linkconsole.log(where.getStatement('text')); // n.x = $x
10linkconsole.log(where.getStatement('object')); // { x: $x }
11linkconsole.log(where.bindParam.get()); // { x: 5 }
The values of the parameters object are separated by an "and" operator
1linkconst where = new Where({
2link n: {
3link x: 5,
4link y: 'bar'
5link },
6link});
7link
8linkconsole.log(where.getStatement('text')); // n.x = $x AND n.y = $y
9linkconsole.log(where.getStatement('object')); // { x: $x, y: $y }
10linkconsole.log(where.bindParam.get()); // { x: 5, y: 'bar' }
The Op.in operator checks if a property value is one of several values. This generates Cypher's IN clause.
1linkconst where = new Where({
2link n: {
3link x: {
4link [Op.in]: [1, 2, 3],
5link },
6link y: 2
7link },
8link o: {
9link z: {
10link [Op.in]: [4, 5, 6],
11link }
12link }
13link});
14link
15linkconsole.log(where.getStatement('text')); // n.x IN $x AND n.y = $y AND o.z IN $z
16link// "object" statement not available
17linkconsole.log(where.bindParam.get()); // { x: [1, 2, 3], y: 2, z: [4, 5, 6] }
Important - Direct Arrays vs Op.in: A direct array value like
{ id: ['1', '2'] }is treated as equality at runtime (id = ['1', '2']), NOT as an IN query. Always use{ [Op.in]: ['1', '2'] }explicitly when you want IN behavior.
The Op._in operator checks if a given value exists within an array property. This is the correct way to check array membership in Cypher (not Op.contains, which is for string substring matching).
1link// Check if 'admin' is in the user's roles array
2linkconst where = new Where({
3link n: {
4link roles: {
5link [Op._in]: 'admin',
6link },
7link },
8link});
9link
10linkconsole.log(where.getStatement('text')); // $roles IN n.roles
11linkconsole.log(where.bindParam.get()); // { roles: 'admin' }
This generates $value IN property which checks if the element exists in the array:
1linkconst where = new Where({
2link n: {
3link x: {
4link [Op._in]: 1,
5link },
6link y: 2
7link },
8link o: {
9link z: {
10link [Op._in]: 2,
11link }
12link }
13link});
14link
15linkconsole.log(where.getStatement('text')); // $x IN n.x AND n.y = $y AND $z IN o.z
16link// "object" statement not available
17linkconsole.log(where.bindParam.get()); // { x: 1, y: 2, z: 2 }
When querying array-typed properties (like string[] or number[]), a limited set of operators is available:
1link// Assuming Users has: { tags: string[], scores: number[] }
2link
3link// Check if an element exists in the array (use Op._in)
4linkawait Users.findMany({
5link where: { tags: { [Op._in]: 'admin' } } // generates: $tags IN u.tags
6link});
7link
8link// Exact array match
9linkawait Users.findMany({
10link where: { tags: { [Op.eq]: ['admin', 'user'] } }
11link});
12link
13link// Array not equal
14linkawait Users.findMany({
15link where: { tags: { [Op.ne]: ['guest'] } }
16link});
17link
18link// TypeScript Error - Op.contains is for string substring matching, not array membership
19linkawait Users.findMany({
20link where: { tags: { [Op.contains]: 'admin' } } // Error!
21link});
22link
23link// TypeScript Error - comparison operators don't apply to arrays
24linkawait Users.findMany({
25link where: { scores: { [Op.gt]: [50] } } // Error!
26link});
Key Point: To check if an element exists in an array property, use
Op._in(which generatesvalue IN arrayProperty), NOTOp.contains(which is for string substring matching).
The following operators are available:
| Operator | Description | Results in |
|---|---|---|
| ne | not equals | <> |
| gt | greater than | > |
| gte | greater than or equals | >= |
| lt | less than | < |
| lte | less than or equals | <= |
The Op.contains operator performs substring matching on strings only. It generates Cypher's CONTAINS clause, which checks if a string property contains a given substring.
Important:
Op.containsis NOT for checking array membership. To check if an element exists in an array property, useOp._ininstead (see below).
1linkconst where = new Where({
2link n: {
3link // Finds nodes where n.name contains 'xyz'
4link name: {
5link [Op.contains]: 'xyz',
6link },
7link },
8link});
9link
10linkconsole.log(where.getStatement('text')); // n.name CONTAINS $name
11link// "object" statement not available
12linkconsole.log(where.bindParam.get()); // { name: 'xyz' }
When using TypeScript, Op.contains is only available for string-typed properties:
1link// Valid - string property
2linkawait Users.findMany({ where: { name: { [Op.contains]: 'John' } } });
3link
4link// TypeScript Error - number property
5linkawait Users.findMany({ where: { age: { [Op.contains]: 5 } } }); // Error!
Check if a property is null or not null using Op.is and Op.isNot:
1linkconst where = new Where({
2link n: {
3link deleted: { [Op.is]: null },
4link createdAt: { [Op.isNot]: null },
5link },
6link});
7link
8linkconsole.log(where.getStatement('text'));
9link// n.deleted IS NULL AND n.createdAt IS NOT NULL
For convenience, you can use null directly as shorthand for { [Op.is]: null }:
1linkconst where = new Where({
2link n: {
3link deleted: null, // Equivalent to { [Op.is]: null }
4link },
5link});
You can also use { [Op.eq]: null } for IS NULL and { [Op.ne]: null } for IS NOT NULL:
1linkconst where = new Where({
2link n: {
3link deleted: { [Op.eq]: null }, // Generates: n.deleted IS NULL
4link active: { [Op.ne]: null }, // Generates: n.active IS NOT NULL
5link },
6link});
Note: These operators are only available in "text" mode (WHERE clause), not "object" mode (bracket syntax).
When using operators in QueryBuilder's match() method for nodes or relationships, all operators are supported. The QueryBuilder automatically separates equality operators (which can use Neo4j's bracket syntax { prop: $val }) from non-equality operators (which require a WHERE clause).
1linkconst queryBuilder = new QueryBuilder().match({
2link identifier: 'u',
3link label: 'User',
4link where: {
5link name: 'John', // eq operator - uses bracket syntax
6link age: { [Op.gte]: 18 }, // non-eq - generates WHERE clause
7link },
8link});
9link
10link// Generates: MATCH (u:User { name: $name }) WHERE u.age >= $age
11linkconsole.log(queryBuilder.getStatement());
12link// { name: 'John', age: 18 }
13linkconsole.log(queryBuilder.getBindParam().get());
1linkconst queryBuilder = new QueryBuilder().match({
2link identifier: 'u',
3link label: 'User',
4link where: {
5link age: { [Op.gte]: 18, [Op.lte]: 65 },
6link },
7link});
8link
9link// Generates: MATCH (u:User) WHERE u.age >= $age AND u.age <= $age__aaaa
10linkconsole.log(queryBuilder.getStatement());
1linkconst queryBuilder = new QueryBuilder().match({
2link related: [
3link { identifier: 'u', label: 'User' },
4link {
5link direction: 'out',
6link name: 'FOLLOWS',
7link identifier: 'r',
8link where: { since: { [Op.gte]: 2020 } },
9link },
10link { identifier: 'p', label: 'Post' },
11link ],
12link});
13link
14link// Generates: MATCH (u:User)-[r:FOLLOWS]->(p:Post) WHERE r.since >= $since
15linkconsole.log(queryBuilder.getStatement());
When using non-equality operators without an explicit identifier, QueryBuilder automatically generates a unique identifier for the WHERE clause:
1link// With explicit identifier
2linknew QueryBuilder().match({
3link identifier: 'n',
4link label: 'Node',
5link where: { age: { [Op.gt]: 18 } },
6link});
7link// Result: MATCH (n:Node) WHERE n.age > $age
8link
9link// Without identifier - one is auto-generated
10linknew QueryBuilder().match({
11link label: 'Node',
12link where: { age: { [Op.gt]: 18 } },
13link});
14link// Result: MATCH (__n:Node) WHERE __n.age > $age
The same applies to relationships - if no identifier is provided and non-equality operators are used, a unique identifier (like __r) is generated automatically.
You can pass a custom BindParam instance to the QueryBuilder constructor. The auto-generated identifiers will use this shared BindParam, which is useful for:
1linkimport { BindParam, QueryBuilder, Op } from 'neogma';
2link
3linkconst bindParam = new BindParam();
4link
5linkconst queryBuilder = new QueryBuilder(bindParam).match({
6link label: 'Node',
7link where: { age: { [Op.gt]: 18 } },
8link});
9link
10link// Result: MATCH (__n:Node) WHERE __n.age > $age
11linkconsole.log(queryBuilder.getStatement());
12link
13link// Access bind parameters from either reference
14linkconsole.log(bindParam.get()); // { age: 18 }
15linkconsole.log(queryBuilder.getBindParam().get()); // { age: 18 }
The class Literal can be used to use any given string in a Where condition.
1linkconst where = new Where({
2link n: {
3link x: 2,
4link y: new Literal('n.x')
5link },
6link o: {
7link z: {
8link [Op.gt]: new Literal('n.y'),
9link }
10link }
11link});
12link
13linkconsole.log(where.getStatement('text')); // n.x = $x AND n.y = n.x AND o.z >= n.y
14link// "object" statement not available
15linkconsole.log(where.bindParam.get()); // { x: 2 }
The acquire static can be used to ensure that a Where instance is at hand. If one is passed, it will be returned as is. If an object with where parameters is passed, a new Where instance will be created with them. Else, a new one will be created.
1linkconst whereFirst = Where.acquire(null);
2linkconsole.log(whereFirst instanceof Where); // true
3link
4linkconst whereSecond = Where.acquire(whereFirst);
5linkconsole.log(whereFirst === whereSecond); // true
6link
7link/* --> an object with where parameters can be used */
8linkconst whereWithPlainParams = Where.acquire({
9link n: {
10link x: 5
11link }
12link});
13linkconsole.log(whereWithPlainParams instanceof Where); // true
14link
15link/* --> a BindParam instance can be passed to be used */
16linkconst existingBindParam = new BindParam({ x: 4 });
17linkconst whereWithBindParams = Where.acquire(
18link {
19link n: {
20link x: 5
21link }
22link },
23link existingBindParam
24link);
25link
26linkconsole.log(whereWithBindParams instanceof Where); // true
27linkconsole.log(whereWithBindParams.statement); // "n.x = $x__aaaa"
28linkconsole.log(whereWithBindParams.bindParam.get()); // { x: 4, x__aaaa: 5 }