Indexes and constraints

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.

This page describes how to use indexes and constraints in the Neo4j GraphQL Library.

@fulltext

Definition

You can use the @fulltext directive to specify a full-text index inside Neo4j. For example:

input FullTextInput {
  indexName: String!
  queryName: String!
  fields: [String]!
}

"""
Informs @neo4j/graphql that there should be a fulltext index in the database, allows users to search by the index in the generated schema.
"""
directive @fulltext(indexes: [FullTextInput]!) on OBJECT

Using this directive does not automatically ensure the existence of these indexes. They must be created manually.

Usage

The @fulltext directive can be used on nodes. In this example, a full-text index called "ProductName", for the name field, on the Product node, is specified:

type Product @fulltext(indexes: [{ indexName: "ProductName", fields: ["name"] }]) @node {
    name: String!
    color: Color! @relationship(type: "OF_COLOR", direction: OUT)
}

This index can be created in the database by running the following Cypher:

CREATE FULLTEXT INDEX ProductName FOR (n:Product) ON EACH [n.name]

For every index specified, a new top level query is generated by the library. For example, for the previous type definitions, the following query and types are generated:

type Query {
    productsFulltextProductName(phrase: String!, where: ProductFulltextWhere, sort: [ProductFulltextSort!],
    limit: Int, offset: Int): [ProductFulltextResult!]!
}

"""The result of a fulltext search on an index of Product"""
type ProductFulltextResult {
  score: Float
  product: Product
}

"""The input for filtering a fulltext query on an index of Product"""
input ProductFulltextWhere {
  score: FloatWhere
  product: ProductWhere
}

"""The input for sorting a fulltext query on an index of Product"""
input ProductFulltextSort {
  score: SortDirection
  product: ProductSort
}

"""The input for filtering the score of a fulltext search"""
input FloatWhere {
  min: Float
  max: Float
}

This query can then be used to perform a Lucene full-text query to match and return products. Here is an example of this:

query {
  productsFulltextProductName(phrase: "Hot sauce", where: { score: { min: 1.1 } } sort: [{ product: { name: ASC } }]) {
    score
    product {
      name
    }
  }
}

This query produces results in the following format:

{
  "data": {
    "productsFulltextProductName": [
      {
        "score": 2.1265015602111816,
        "product": {
          "name": "Louisiana Fiery Hot Pepper Sauce"
        }
      },
      {
        "score": 1.2077560424804688,
        "product": {
          "name": "Louisiana Hot Spiced Okra"
        }
      },
      {
        "score": 1.3977186679840088,
        "product": {
          "name": "Northwoods Cranberry Sauce"
        }
      }
    ]
  }
}

Additionally, it is possible to define a custom query name as part of the @fulltext directive by using the queryName argument:

type Product @fulltext(indexes: [{ queryName: "CustomProductFulltextQuery", indexName: "ProductName", fields: ["name"] }]) @node {
    name: String!
    color: Color! @relationship(type: "OF_COLOR", direction: OUT)
}

This produces the following top-level query:

type Query {
    CustomProductFulltextQuery(phrase: String!, where: ProductFulltextWhere, sort: [ProductFulltextSort!],
    limit: Int, offset: Int): [ProductFulltextResult!]!
}

This query can then be used like this:

query {
  CustomProductFulltextQuery(phrase: "Hot sauce", sort: [{ score: ASC }]) {
    score
    product {
      name
    }
  }
}

With the @vector GraphQL directive you can query your database to perform a vector index search. Queries are performed by passing in either a vector index or a query phrase.

A query by vector index finds nodes with a vector embedding similar to that index. That is, the query performs a nearest neighbor search.

In contrast, a query by phrase (a string of text) forwards the phrase to the Neo4j GenAI plugin and the plugin generates a vector embedding for it. This embedding is then compared to the node vector embeddings in the database.

Prerequisites
  • The database must be Neo4j version 5.15 or higher.

  • The node vector embeddings already exist in the database. See Vector indexes to learn more about vector indexes in Cypher and Neo4j.

  • The embeddings must have been created using the same method, that is, the same provider and model. See Embeddings & Vector Indexes Tutorial to learn about vector embeddings in Cypher and Neo4j.

  • Queries by vector index cannot be performed across multiple labels.

  • Queries by phrase require credentials for the Neo4j GenAI plugin.

Vector index searches are read-only in the sense that the data which the queries operate on are retrieved from the database but not altered or written back to the database.

Definition

"""Informs @neo4j/graphql that there should be a vector index in the database, allows users to search by the index in the generated schema."""
directive @vector(indexes: [VectorIndexInput]!) on OBJECT

VectorIndexInput is defined as follows:

input VectorIndexInput {
  """(Required) The name of the vector index."""
  indexName: String!
  """(Required) The name of the embedding property on the node."""
  embeddingProperty: String!
  """(Required) The name of the query."""
  queryName: String
  """(Optional) The name of the provider."""
  provider: String
}

If the optional field provider is set, the type is used for a query by phrase, otherwise for a query by vector. Allowed values for the provider field are defined by the available GenAI providers.

Usage

Query by vector index

Perform a nearest neighbor search by passing a vector to find nodes with a vector embedding similar to that vector.

Type definition
type Product @node @vector(indexes: [{
  indexName: "productDescriptionIndex",
  embeddingProperty: "descriptionVector",
  queryName: "searchByDescription"
}]) {
  id: ID!
  name: String!
  description: String!
}

This defines the query to be performed on all Product nodes which have a vector index named productDescriptionIndex for the property descriptionVector, implying that a vector embedding has been created for the description property of each node.

Example query
query FindSimilarProducts($vector: [Float]!) {
  searchByDescription(vector: $vector) {
    edges {
      cursor
      score
      node {
          id
          name
          description
      }
    }
  }
}

The input $vector is a list of FLOAT values and should look similar to this:

An example vector
{
  "vector": [
    0.123456,
    ...,
    0.654321,
  ]
}

The query returns all Product nodes with a vector embedding on their descriptionVector property which is similar to the query argument $vector.

Query by phrase

Perform a query which utilizes the Neo4j GenAI plugin to create a vector embedding for a search phrase and then compare it to existing vector embeddings on nodes in the database.

Requires credentials for the plugin.

Ensure your provider credentials are set in the call to Neo4jGraphQL, for example:

Feature configuration
const neoSchema = new Neo4jGraphQL({
    typeDefs,
    driver,
    features: {
        vector: {
            OpenAI: {
                token: "my-open-ai-token",
                model: "text-embedding-3-small",
            },
        },
    },
});

OpenAI is one of the GenAI providers for generating vector embeddings. See GenAI providers for the full list of providers and their respective identifiers.

Type definition
type Product @node @vector(indexes: [{
  indexName: "productDescriptionIndex",
  embeddingProperty: "descriptionVector",
  provider: OPEN_AI,  # Assuming this is configured in the server
  queryName: "searchByPhrase"
}]) {
  id: ID!
  name: String!
  description: String!
}

This defines the query to be performed on all Product nodes which have a vector index named productDescriptionIndex for the property descriptionVector, implying that a vector embedding has been created for the description property of each node.

Example query
query SearchProductsByPhrase($phrase: String!) {
  searchByPhrase(phrase: $phrase) {
    edges {
      cursor
      score
      node {
          id
          name
          description
      }
    }
  }
}

First, the query passes the query phrase argument $phrase to the GenAI plugin and lets it generate a vector embedding for the phrase. Then it returns all Product nodes with a vector embedding on their descriptionVector property which are similar to the vector embedding generated by the plugin.