Backend Development 12 min read

Using Redis Stack for Full‑Text Search with RediSearch and JSON

This tutorial explains how to leverage Redis Stack modules—especially RediSearch and RedisJSON—to store movie data, build a searchable JSON index, and perform advanced full‑text queries, highlighting, sorting, pagination, custom tokenization, scoring, and index aliasing, with Go code examples.

System Architect Go
System Architect Go
System Architect Go
Using Redis Stack for Full‑Text Search with RediSearch and JSON

Preface

Redis is widely known as a cache, but its additional modules such as RedisJSON , RediSearch , RedisTimeSeries , and RedisBloom extend its capabilities to handle JSON data, full‑text search (including fuzzy, vector, and geo queries), time‑series data, and probabilistic calculations. All these modules can be imported on demand or used together via Redis Stack .

This article demonstrates how to use Redis for full‑text search.

Redis Full‑Text Search

Full‑Text Search Basics

Full‑text search is a technique that quickly finds relevant documents based on user‑provided words or sentences.

The core concepts include:

Tokenization: splitting a document into individual terms.

Inverted Index: a structure that maps terms to the documents containing them.

Relevance Scoring: assigning a score to each result based on its relevance.

Example Dataset

We will use a public movie dataset and keep only a few fields for simplicity:

_id: unique identifier

title: movie title

directors: director names

genres: movie genres

summary: brief description

rating: numeric rating

Data is stored in Redis as JSON using the JSON.SET command:

JSON.SET movieID:1 $ '{"directors":"马丁·里特","genres":["剧情","动作","西部"],"rating":8.0,"title":"野狼 Hombre","summary":"约翰·罗塞尔自幼是老罗塞尔先生从战俘中带回来并抚养他长大的,但是他生性豪放不羁……"}'

Note that Redis keys are always strings; in this tutorial we use the pattern movieID:12345 .

Batch import with Go:

func BuildDataset() {
    movies, _ := ReadMovieJSON()

    rds := getRedisClient()
    ctx := context.Background()
    for _, v := range movies {
        b, _ := json.Marshal(v)
        if r := rds.JSONSet(ctx, "movieID:"+v.ID, "$", b); r.Err() != nil {
            panic(r.Err())
        }
    }
}

Creating the Index

To enable full‑text search we create an index with FT.CREATE :

FT.CREATE movies ON JSON
PREFIX 1 movieID:
LANGUAGE Chinese
SCHEMA
    $.title as title TEXT WEIGHT 3
    $.directors.*.name as directors TAG
    $.genres.* as genres TAG
    $.summary as summary TEXT
    $.rating.average as rating NUMERIC

The command means:

We create a JSON‑backed index named movies .

The index applies to all keys with the prefix movieID: .

Chinese tokenization is used.

Indexed fields: title (TEXT, weight 3) directors (TAG) genres (TAG) summary (TEXT) rating (NUMERIC)

Indexes are independent objects; dropping an index does not delete the underlying key‑value data. New or updated documents are indexed automatically, while existing documents are indexed asynchronously after the index is created.

Using Full‑Text Search

Basic Queries

Search any field for the word "爱情": FT.SEARCH movies '爱情'

Return only specific fields: FT.SEARCH movies '爱情' RETURN 2 title directors

Highlight matches in the title field: FT.SEARCH movies '爱情' RETURN 2 title directors HIGHLIGHT FIELDS 1 title TAGS <span> </span>

Sort results by rating descending: FT.SEARCH movies '爱情' RETURN 3 title directors rating SORTBY rating DESC

Paginate results (first 10): FT.SEARCH movies '爱情' RETURN 3 title directors rating SORTBY rating DESC LIMIT 0 10

Field‑specific text search (title contains "爱情"): FT.SEARCH movies '@title:爱情' RETURN 2 title directors

Tag field match (director is "马丁·里特"): FT.SEARCH movies '@directors:{马丁·里特}' RETURN 2 title directors

Compound Conditions

OR – genre is drama or action: FT.SEARCH movies '@genres:{剧情|动作}' RETURN 2 title directors

AND – genre is drama or action AND rating ≥ 8.0: FT.SEARCH movies '(@genres:{剧情|动作})(@rating:[8.0,+inf])' RETURN 3 title directors rating

Prefix, Suffix & Fuzzy Search

FT.SEARCH movies '@title:爱*' RETURN 1 title

FT.SEARCH movies '@title:*情' RETURN 1 title

FT.SEARCH movies '@title:*命*' RETURN 1 title

FT.SEARCH movies '@title:%人生%' RETURN 1 title

Custom Tokenization

Redis offers limited custom tokenization compared with Elasticsearch:

Stopwords can be set via the STOPWORDS option of FT.CREATE .

Synonyms are added with FT.SYNUPDATE . Example: FT.SYNUPDATE movies group1 爱情 凌虚 After that, FT.SEARCH movies '爱情' is equivalent to FT.SEARCH movies '凌虚' .

Custom Scoring

Redis supports several scoring algorithms:

TFIDF (default)

TFIDF.DOCNORM

BM25 (used by Elasticsearch)

DISMAX

DOCSCORE

HAMMING

FT.SEARCH movies '爱情' RETURN 0 WITHSCORES SCORER BM25

For other custom scoring methods you need to write a Redis module in C or a language with a C interface.

Index Alias

Creating an alias allows you to switch the underlying index without changing client code:

Create alias: FT.ALIASADD aliasName movies

Search via alias: FT.SEARCH aliasName '爱情' RETURN 0

Update alias to a new index: FT.ALIASUPDATE aliasName anotherIndex

Delete alias: FT.ALIASDEL aliasName

Go Helper Functions

Utility functions for executing arbitrary Redis commands using the go-redis client:

func cmdStringToArgs(rdscmd string) (result []interface{}) {
    re := regexp.MustCompile(`\s+`)
    slice := re.Split(rdscmd, -1)
    for _, v := range slice {
        if v != "" && v != " " {
            result = append(result, v)
        }
    }
    return
}

func ExcuteCommand(rdscmd string) {
    rds := getRedisClient()
    ctx := context.Background()
    args := cmdStringToArgs(rdscmd)
    res, err := rds.Do(ctx, args...).Result()
    if err != nil {
        panic(err)
    }
    fmt.Println("RESULT:", res)
}

Test case illustrating several search commands:

func TestExcuteCommand(t *testing.T) {
    cases := []string{
        `FT.SEARCH movies '爱情'`,
        `FT.SEARCH movies '爱情' RETURN 2 title directors`,
        // ... more cases
    }
    for _, v := range cases {
        ExcuteCommand(v)
    }
}

Conclusion

Compared with Elasticsearch , the flagship full‑text engine, Redis offers fewer features (e.g., limited custom tokenization and scoring) but still provides all essential full‑text capabilities. For small‑to‑medium data volumes and simple search scenarios, Redis can be more resource‑efficient and faster than running an Elasticsearch cluster.

References:

https://redis.io/docs/latest/develop/interact/search-and-query/

https://redis.io/docs/latest/commands/ft.create/

https://redis.io/docs/latest/commands/ft.search/

RedisGoJSONFull-Text SearchFT.CREATERediSearch
System Architect Go
Written by

System Architect Go

Programming, architecture, application development, message queues, middleware, databases, containerization, big data, image processing, machine learning, AI, personal growth.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.