Neo4j’s full-text search isn’t about fuzzy matching strings; it’s about indexing and querying the meaning within your text.

Let’s see it in action. Imagine we have a movie graph with Movie nodes that have a title and a description.

CREATE (m1:Movie {title: "The Matrix", description: "A computer hacker learns from mysterious rebels about the true nature of his reality and his role in the war against its controllers."})
CREATE (m2:Movie {title: "The Lord of the Rings: The Fellowship of the Ring", description: "A meek Hobbit from the Shire and eight companions set out on a journey to destroy the powerful One Ring and save Middle-earth from the Dark Lord Sauron."})
CREATE (m3:Movie {title: "Inception", description: "A thief who steals corporate secrets through the use of dream-sharing technology is given the inverse task of planting an idea into the mind of a C.E.O."})
CREATE (m4:Movie {title: "The Matrix Reloaded", description: "Neo and his allies plan a desperate stand to fight the“““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““GIVEN: Neo4j Full-Text Search: Index and Search Text Properties

Neo4j's full-text search isn't about fuzzy matching strings; it's about indexing and querying the *meaning* within your text.

Let's see it in action. Imagine we have a movie graph with `Movie` nodes that have a `title` and a `description`.

```cypher
CREATE (m1:Movie {title: "The Matrix", description: "A computer hacker learns from mysterious rebels about the true nature of his reality and his role in the war against its controllers."})
CREATE (m2:Movie {title: "The Lord of the Rings: The Fellowship of the Ring", description: "A meek Hobbit from the Shire and eight companions set out on a journey to destroy the powerful One Ring and save Middle-earth from the Dark Lord Sauron."})
CREATE (m3:Movie {title: "Inception", description: "A thief who steals corporate secrets through the use of dream-sharing technology is given the inverse task of planting an idea into the mind of a C.E.O."})
CREATE (m4:Movie {title: "The Matrix Reloaded", description: "Neo and his allies plan a desperate stand to fight the machines."})
CREATE (m5:Movie {title: "The Dark Knight", description: "When the menace known as the Joker wreaks havoc and chaos on the people of Gotham, Batman must accept one of the greatest psychological and physical tests of his ability to fight injustice."})

To enable full-text search on the description property of Movie nodes, you need to create a full-text index. This index is built using Neo4j’s built-in fulltext index provider.

First, let’s create the index:

CREATE FULLTEXT INDEX movieDescriptions FOR ON Movie(description)

This command tells Neo4j to create an index named movieDescriptions that will index the description property on all Movie nodes. Neo4j will automatically tokenize the text, remove stop words (like "a", "the", "is"), and stem the words (e.g., "controllers" becomes "controll").

Now, you can query this index using the CALL db.index.fulltext.queryNodes() procedure. Let’s find movies related to "hacker" and "reality":

CALL db.index.fulltext.queryNodes("movieDescriptions", "hacker AND reality")
YIELD node, score
RETURN node.title, score

This query will return The Matrix because its description contains both "hacker" and "reality". The score indicates the relevance of the match, with higher scores meaning more relevant.

You can also use boolean operators like OR and NOT:

To find movies about "hobbit" OR "ring":

CALL db.index.fulltext.queryNodes("movieDescriptions", "hobbit OR ring")
YIELD node, score
RETURN node.title, score

This would return The Lord of the Rings: The Fellowship of the Ring.

To find movies about "matrix" but NOT "reloaded":

CALL db.index.fulltext.queryNodes("movieDescriptions", "matrix NOT reloaded")
YIELD node, score
RETURN node.title, score

This query would return The Matrix.

The full-text index uses Lucene under the hood, which provides powerful search capabilities. When you create the index, Neo4j processes the text. It converts text to lowercase, removes punctuation, splits text into individual terms (tokens), removes common words (stop words), and reduces words to their root form (stemming). For example, "controllers" becomes "controll".

When you query, your search terms go through the same process. This ensures that searches like "control" will match documents containing "controllers".

You can also search for exact phrases using double quotes:

CALL db.index.fulltext.queryNodes("movieDescriptions", '"dark lord"')
YIELD node, score
RETURN node.title, score

This query will find The Lord of the Rings: The Fellowship of the Ring.

For more advanced searching, you can use fuzzy matching with a tilde (~) followed by a distance. For example, to find "hacker" with a fuzzy match of up to 2 characters difference:

CALL db.index.fulltext.queryNodes("movieDescriptions", "hacker~2")
YIELD node, score
RETURN node.title, score

This could potentially match variations of "hacker" like "hackers" or even "trackers" if the distance is set appropriately, though it’s important to be mindful of the trade-off between recall and precision.

The score returned by queryNodes is a relevance score. It’s calculated based on factors like term frequency (how often a term appears in a document) and inverse document frequency (how rare a term is across all documents). This helps rank results, showing the most relevant matches first.

When you delete a node or property that was indexed, Neo4j automatically updates the full-text index. Similarly, when you create new nodes or properties that match the index definition, they are automatically indexed.

The full-text index is a powerful tool for making your graph data searchable by its textual content, enabling applications that need to find information based on keywords, concepts, or even semantic relationships within the text. It transforms unstructured text within your graph into structured, queryable data.

For more complex scenarios, you can create multiple full-text indexes, perhaps one for movie titles and another for descriptions, or even indexes on different languages if your data supports it. You can also specify different analyzers if needed, though the default is often sufficient.

Once you’ve mastered full-text search, you’ll want to explore how to combine full-text queries with graph traversals to find not just nodes with relevant text, but also nodes connected to them in specific ways.

Want structured learning?

Take the full Neo4j course →