Filtering

When querying for data, a number of operators are available for different types in the where argument of a query or mutation.

Equality operators

All types can be tested for either equality. For non-equality, you must use the NOT logical operator. For example:

Filtering all users named John
query {
  users(where: {name: "John" })
    id
    name
  }

For the Boolean type, equality operators are the only ones available.

Numerical operators

These are the operators available for numeric (Int, Float, BigInt), temporal and spatial types:

  • _LT

  • _LTE

  • _GT

  • _GTE

Here is an example of how to use them:

Filtering users younger than 50 years old
query {
  users(where: {age_LT: 50 }) {
    id
    name
    age
  }
}

Spatial types use numerical filtering differently and they also have additional options. See Filtering spatial types for more information.

Logical operators

All operators can be combined using the logical operators AND, OR, and NOT. They can also be standalone operators, which means that they can be used as such and not be appended to field names.

These operators accept an array argument with items of the same format as the where argument, which means they can also be nested to form complex combinations.

For example, if you want to match all actors by the name of either "Keanu" or not belonging to the "Pantoliano" family, that played in "The Matrix" movie, here is how you can query that:

query {
    actors(where: {
        AND: [
            {
                OR: [
                    { name_CONTAINS: "Keanu" },
                    { NOT: { name_ENDS_WITH: "Pantoliano" } }
                ]
            },
            {
                movies_SOME: { title: "The Matrix" }
            }
        ]}
    ) {
        name
        movies {
            title
        }
    }
}

String comparison

The following case-sensitive comparison operators are only available for use on String and ID types:

  • _STARTS_WITH

  • _ENDS_WITH

  • _CONTAINS

Here is an example of how to use them:

Filtering users with name starting with "J"
query {
  users(where: { name_STARTS_WITH: "J" }) {
    id
    name
  }
}

Additionally, numerical operators can be used for String comparisons. They are disabled default. To enable them, add them in the filters features options for String:

const { Neo4jGraphQL } = require("@neo4j/graphql");
const neo4j = require("neo4j-driver");

const typeDefs = `
    type User {
        name: String
    }
`;

const driver = neo4j.driver(
    "bolt://localhost:7687",
    neo4j.auth.basic("username", "password")
);

const features = {
    filters: {
        String: {
            LT: true,
            GT: true,
            LTE: true,
            GTE: true
        }
    }
};

const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver });

RegEx matching

The filter _MATCHES is also available for comparison of String and ID types. It accepts RegEx strings as an argument and returns any matches.

Note that RegEx matching filters are disabled by default. This is because, on an unprotected API, they could potentially be used to execute a ReDoS attack against the backing Neo4j database.

If you want to enable them, set the features configuration object for each:

const features = {
    filters: {
        String: {
            MATCHES: true,
        }
    }
};

const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver });

For ID:

const features = {
    filters: {
        String: {
            ID: true,
        }
    }
};

const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver });

For both String and ID:

const features = {
    filters: {
        String: {
            MATCHES: true,
        },
        ID: {
            MATCHES: true,
        }
    }
};

const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver });

Array comparison

The following operator is available on non-array fields, and accepts an array argument:

  • _IN

Conversely, the following operator is available on array fields, and accepts a single argument:

  • _INCLUDES

These operators are available for all types apart from Boolean.

Filtering spatial types

Both the Point and the CartesianPoint types use numerical operators and have an additional _DISTANCE filter. Here is a list of what each filter does for the two types:

  • _LT: checks if a point is less than the distance in the distance field away (in meters) from the point specified by the point field.

  • _LTE: checks if a point is less than or equal to the distance in the distance field away (in meters) from the point specified by the point field.

  • _DISTANCE: checks if a point is the exact distance in the distance field away (in meters) from the point specified by the point field.

  • _GT: checks if a point is greater than the distance in the distance field away (in meters) from the point specified by the point field.

  • _GTE: checks if a point is greater than or equal to the distance in the distance field away (in meters) from the point specified by the point field.

For a Point type, all filters take the following type as an argument:

input PointDistance {
    point: Point!
    distance: Float!
}

In practice, you can construct queries like the following, which finds all users within a 5km (5000m) radius of a Point:

query CloseByUsers($longitude: Float!, $latitude: Float!) {
    users(where: { location_LTE: { point: { longitude: $longitude, latitude: $latitude }, distance: 5000 } }) {
        name
        location {
            longitude
            latitude
        }
    }
}

Similarly, for a CartesianPoint type, all filters take the following type as an argument:

input CartesianPointDistance {
    point: CartesianPoint!
    distance: Float!
}

The same query for a CartesianPoint:

query CloseByUsers($x: Float!, $y: Float!) {
  users(where: { location_LTE: { point: { x: $x, y: $y }, distance: 5000 } }) {
    name
    location {
      x
      y
    }
  }
}

Querying an interface

You can use the typename_IN filter to filter interfaces. Refer to Type definitions → Type → Interface for more details and an example.

Relationship filtering

Relationship filtering depends on the type of relationship that you have:

  • n..1: filtering done on equality or inequality of the related nodes by specifying a filter on field.

  • n..m: filtering is done on the list of related nodes and is based on the list predicates available in Cypher:

As an example, take these type definitions:

type User {
    id: ID!
    name: String
    posts: [Post!]! @relationship(type: "HAS_POST", direction: OUT)
}

type Post {
    id: ID!
    content: String
    author: User! @relationship(type: "HAS_POST", direction: IN)
    likes: [User!]! @relationship(type: "LIKES", direction: IN)
}

n..1 relationships

An author represents an n..1 relationship on Post, where a given Post is authored by one, and only one, author. The available filters here will be author. For example:

Find all posts by a desired author
query {
    posts(where: { author: { id: "7CF1D9D6-E527-4ACD-9C2A-207AE0F5CB8C" } }) {
        content
    }
}
Find all posts NOT by an undesired author
query {
    posts(where: { NOT: { author: { id: "7CF1D9D6-E527-4ACD-9C2A-207AE0F5CB8C" } } }) {
        content
    }
}

n..m relationships

In the previous example, posts represents a n..m relationship on User, where a given User can have any number of posts. Here are some query examples:

Find all users where all of their posts contain search term: "neo4j"
query {
    users(where: { posts_ALL: { content_CONTAINS: "neo4j" } }) {
        name
    }
}
Find all users where none of their posts contains search term: "cypher"
query {
    users(where: { posts_NONE: { content_CONTAINS: "cypher" } }) {
        name
    }
}
Find all users where some of their posts contain search term: "graphql"
query {
    users(where: { posts_SOME: { content_CONTAINS: "graphql" } }) {
        name
    }
}
Find all users where only one of their posts contain search term: "graph"
query {
    users(where: { posts_SINGLE: { content_CONTAINS: "graph" } }) {
        name
    }
}

Aggregation filtering

This library offers, for each relationship, an aggregation key inside the where argument. It can be used both on the node and edge of a relationship.

Here are some examples on how to apply this kind of filtering:

  1. Find posts where the number of likes are greater than 5

    Schema example
    type User {
        name: String
    }
    
    type Post {
        content: String
        likes: [User!]! @relationship(type: "LIKES", direction: IN)
    }
    Query
    query {
        posts(where: { likesAggregate: { count_GT: 5 } }) {
            content
        }
    }
  2. Find flights where the average age of passengers is greater than or equal to 18

    Schema example
    type Passenger {
        name: String
        age: Int
    }
    
    type Flight {
        code: String
        passengers: [Passenger!]! @relationship(type: "FLYING_ON", direction: IN)
    }
    Query
    query {
        flights(where: { passengersAggregate: { node: { age_AVERAGE_GTE: 18 } } }) {
            code
        }
    }
  3. Find movies where the shortest actor screen time is less than 10 minutes

    Schema example
    type Movie {
        title: String
        actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
    }
    
    type Person {
        name: String
    }
    
    type ActedIn @relationshipProperties {
        screenTime: Int
    }
    Query
    query {
        movies(where: { actorsAggregate: { edge: { screenTime_MIN_LT: 10 } } }) {
            title
        }
    }

Operators

Aggregation filtering can also be done with operators. They provide autogenerated filters available for each type on the node and edge of the specified relationship.

Field type Description Operators Example

count

A special 'top level' key inside the where aggregation and will be available for all relationships. This is used to count the amount of relationships the parent node is connected to.

count_EQUAL, count_GT, count_GTE, count_LT, count_LTE

query {
    posts(where: { likesAggregate: { count_GT: 5 } }) {
        content
    }
}

String

These operators are calculated against the length of each string.

_AVERAGE_LENGTH_EQUAL _AVERAGE_LENGTH_GT _AVERAGE_LENGTH_GTE _AVERAGE_LENGTH_LT _AVERAGE_LENGTH_LTE _SHORTEST_LENGTH_EQUAL _SHORTEST_LENGTH_GT _SHORTEST_LENGTH_GTE _SHORTEST_LENGTH_LT _SHORTEST_LENGTH_LTE _LONGEST_LENGTH_EQUAL _LONGEST_LENGTH_GT _LONGEST_LENGTH_GTE _LONGEST_LENGTH_LT _LONGEST_LENGTH_LTE

query {
    posts(where: { likesAggregate: { node: { name_LONGEST_LENGTH_GT: 5 } } }) {
        content
    }
}

Numerical

Used in the case of Int, Float, and BigInt.

_AVERAGE_EQUAL, _AVERAGE_GT, _AVERAGE_GTE, _AVERAGE_LT, _AVERAGE_LTE, _SUM_EQUAL, _SUM_GT, _SUM_GTE, _SUM_LT, _SUM_LTE, _MIN_EQUAL, _MIN_GT, _MIN_GTE, _MIN_LT, _MIN_LTE, _MAX_EQUAL, _MAX_GT, _MAX_GTE, _MAX_LT, _MAX_LTE

query {
    movies(where: { actorsAggregate: { edge: { screenTime_MIN_LT: 10 } } }) {
        title
    }
}

Temporal

Used in the case of DateTime, LocalDateTime, LocalTime, Time, and Duration.

_MIN_EQUAL, _MIN_GT, _MIN_GTE, _MIN_LT, _MIN_LTE, _MAX_EQUAL, _MAX_GT, _MAX_GTE, _MAX_LT, _MAX_LTE

Type definitions
type Event {
    title: String!
    startTime: DateTime!
}
Query
query EventsAggregate {
    users(where: { eventsAggregate: { node: { startTime_GT: "2022-08-14T15:00:00Z" } } }) {
        name
    }
}

Duration

Description.

_AVERAGE_EQUAL, _AVERAGE_GT, _AVERAGE_GTE, _AVERAGE_LT, _AVERAGE_LTE

Type definitions
type Event {
    title: String!
    duration: Duration!
}
Query
query EventsAggregate {
    users(where: { eventsAggregate: { node: { duration_AVERAGE_LT: "PT2H" } } }) {
        name
    }
}

ID

No aggregation filters are available for ID.

-

-