Filtering

This is the documentation of the GraphQL Library version 7. For the long-term support (LTS) version 5, refer to GraphQL Library version 5 LTS.

You can apply filters when you query or aggregate data as well as use filtering rules for authorization.

When querying for data, a number of operators are available for the types in the where argument of a query or mutation, allowing you to filter query results or specify the set of objects a mutation applies to.

Operators can either be standalone operators (see Boolean operators) or they are appended to field names (for example, String comparison).

All operators can be combined using the Boolean operators AND, OR, and NOT.

Operators

Boolean operators

As standalone operators, Boolean operators accept an array argument with items of the same format as the where argument. This way, they can be nested to form complex Boolean expressions.

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

query {
  actors(
    where: {
      AND: [
        {
          OR: [
            { name: { contains: "Keanu" } }
            { NOT: { name: { endsWith: "Pantoliano" } } }
          ]
        }
        { movies: { some: { title: { eq: "The Matrix" } } } }
      ]
    }
  ) {
    name
    movies {
      title
    }
  }
}

contains and endsWith are String comparisons while some is a relationship filter.

Equality operators

All types can be tested for equality or non-equality.

For example:

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

For non-equality, you must use the NOT logical operator.

Filtering all users which are not named John
query {
  users(where: { NOT: { name: { eq: "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 Spatial type filtering for more information.

Spatial type filtering

Spatial filters are available for both Point and Cartesian fields. They allow you to filter spatial data either by exact equality or based on a distance criterion.

For exact matching, use the eq operator:

query {
  users(
    where: {
      location: {
        eq: { longitude: 9, latitude: 10, height: 11 }
      }
    }
  ) {
    name
    location {
      longitude
      latitude
    }
  }
}

For distance-based filtering, combine numerical operators operators with the distance operator:

query CloseByUsers {
  users(
    where: {
      location: {
        distance: { lte: 5000, from: { longitude: 9, latitude: 10, height: 11 } }
      }
    }
  ) {
    name
    location {
      longitude
      latitude
    }
  }
}

For Cartesian points, use:

query CloseByUsers {
  users(
    where: {
      location: {
        distance: { lte: 5000, from: { x: 9, y: 10, z: 11 } }
      }
    }
  ) {
    name
    location {
      x
      y
      z
    }
  }
}

Type comparison

String comparison

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

  • startsWith

  • endsWith

  • contains

Here is an example of how to use them:

Filtering users with name starting with "J"
query {
  users(where: { name: { startsWith: "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 @node {
        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 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 RegEx matching, update the features configuration object.

For String:

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

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

For ID:

const features = {
    filters: {
        ID: {
            MATCHES: 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

Consider the following type definitions:

type Movie @node {
    id: ID!
    title: String!
    genres: [String!]
    year: Int!
    actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN)
}

type Actor @node {
    id: ID!
    name: String!
    movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT)
}

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

query {
  movies(where: { year: { in: [1999, 2000, 2001] } }) {
    title
    year
  }
}

The query returns all movies released in the years 1999, 2000 and 2001.

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

query {
  movies(where: { genres: { includes: "Action" } }) {
    title
    genres
  }
}

The query returns all movies which have "Action" as one of their genres.

in and includes are available for all types except Boolean.

Interface filtering

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

Relationship filtering

The filtering is done on the list of related nodes and is based on the list predicates available in Cypher: all - all none - none some - any single - single

For example, take these type definitions:

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

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

In the type definitions example, posts represents a relationship on User, where a given User can have any number of posts.

For example:

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

The Neo4j GraphQL Library offers an aggregation key inside the where argument of each relationship. You can use it 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 @node {
        name: String
    }
    
    type Post @node {
        content: String
        likes: [User!]! @relationship(type: "LIKES", direction: IN)
    }
    Query
    query {
      posts(
        where: { likesConnection: { aggregate: { count: { nodes: { gt: 5 } } } } }
      ) {
        content
      }
    }
  2. Find flights where the average age of passengers is greater than or equal to 18

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

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

With 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.

nodes, edges

query {
  posts(
    where: { likesConnection: { aggregate: { count: { nodes: { gt: 5 } } } } }
  ) {
    content
  }
}

String

These operators are calculated against the length of each string.

averageLength shortestLength longestLength

query {
  posts(
    where: {
      likesConnection: {
        aggregate: { node: { name: { longestLength: { gt: 5 } } } }
      }
    }
  ) {
    content
  }
}

Numerical

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

average, min, max, sum

query {
  movies(
    where: {
      actorsConnection: {
        aggregate: { edge: { screenTime: { min: { lt: 10 } } } }
      }
    }
  ) {
    title
  }
}

Temporal

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

min, max

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

Duration

Description.

average

Type definitions
type Event @node {
    title: String!
    duration: Duration!
}
Query
query EventsAggregate {
    users(where: { eventsConnection: { aggregate: { node: { duration: { average: { lt: "PT2H" } } } } } }) {
        name
    }
}

ID

No aggregation filters are available for ID.

-

-