Recipes

Blog Search

This recipe demonstrates building a full-text blog search with highlighted snippets, phrase matching, and date filtering.

Schema Design

The schema prioritizes full-text search capabilities with date-based filtering:

import { Redis, s } from "@upstash/redis";const redis = Redis.fromEnv();const articles = await redis.search.createIndex({  name: "articles",  dataType: "hash",  prefix: "article:",  schema: s.object({    // Full-text searchable content    title: s.string(),    body: s.string(),    summary: s.string(),    // Author name without stemming    author: s.string().noStem(),    // Date fields for filtering and sorting    publishedAt: s.date().fast(),    updatedAt: s.date().fast(),    // Status for draft/published filtering    published: s.boolean(),    // View count for popularity sorting    viewCount: s.number("U64"),  }),});
from upstash_redis import Redisredis = Redis.from_env()articles = redis.search.create_index(    name="articles",    data_type="hash",    prefix="article:",    schema={        # Full-text searchable content        "title": "TEXT",        "body": "TEXT",        "summary": "TEXT",        # Author name without stemming        "author": {"type": "TEXT", "nostem": True},        # Date fields for filtering and sorting        "publishedAt": {"type": "DATE", "fast": True},        "updatedAt": {"type": "DATE", "fast": True},        # Status for draft/published filtering        "published": "BOOL",        # View count for popularity sorting        "viewCount": "U64",    },)
SEARCH.CREATE articles ON HASH PREFIX 1 article: SCHEMA title TEXT body TEXT summary TEXT author TEXT NOSTEM publishedAt DATE FAST updatedAt DATE FAST published BOOL viewCount U64 FAST

Sample Data

await redis.hset("article:1", {  title: "Getting Started with Redis Search",  body: "Redis Search provides powerful full-text search capabilities directly in Redis. In this tutorial, we'll explore how to create indexes, define schemas, and write queries. Full-text search allows you to find documents based on their content rather than just their keys. This is essential for building search features in modern applications.",  summary: "Learn how to add full-text search to your Redis application with practical examples.",  author: "Jane Smith",  tags: "redis,search,tutorial",  publishedAt: "2024-03-15T10:00:00Z",  updatedAt: "2024-03-15T10:00:00Z",  published: "true",  viewCount: "1542",});await redis.hset("article:2", {  title: "Advanced Query Techniques for Search",  body: "Once you've mastered the basics, it's time to explore advanced query techniques. Boolean operators let you combine conditions with AND, OR, and NOT logic. Phrase matching ensures words appear in sequence. Fuzzy matching handles typos gracefully. Together, these features enable sophisticated search experiences.",  summary: "Master boolean operators, phrase matching, and fuzzy search for better results.",  author: "John Doe",  tags: "redis,search,advanced",  publishedAt: "2024-03-20T14:30:00Z",  updatedAt: "2024-03-22T09:15:00Z",  published: "true",  viewCount: "892",});await redis.hset("article:3", {  title: "Building Real-Time Search with Redis",  body: "Real-time search requires instant indexing and low-latency queries. Redis excels at both. When you write data to Redis, the search index updates automatically. Queries execute in milliseconds, even with millions of documents. This makes Redis ideal for applications where search results must reflect the latest data.",  summary: "Build search features that update instantly as data changes.",  author: "Jane Smith",  tags: "redis,real-time,performance",  publishedAt: "2024-03-25T08:00:00Z",  updatedAt: "2024-03-25T08:00:00Z",  published: "true",  viewCount: "2103",});
redis.hset("article:1", {    "title": "Getting Started with Redis Search",    "body": "Redis Search provides powerful full-text search capabilities directly in Redis. In this tutorial, we'll explore how to create indexes, define schemas, and write queries. Full-text search allows you to find documents based on their content rather than just their keys. This is essential for building search features in modern applications.",    "summary": "Learn how to add full-text search to your Redis application with practical examples.",    "author": "Jane Smith",    "tags": "redis,search,tutorial",    "publishedAt": "2024-03-15T10:00:00Z",    "updatedAt": "2024-03-15T10:00:00Z",    "published": "true",    "viewCount": "1542",})redis.hset("article:2", {    "title": "Advanced Query Techniques for Search",    "body": "Once you've mastered the basics, it's time to explore advanced query techniques. Boolean operators let you combine conditions with AND, OR, and NOT logic. Phrase matching ensures words appear in sequence. Fuzzy matching handles typos gracefully. Together, these features enable sophisticated search experiences.",    "summary": "Master boolean operators, phrase matching, and fuzzy search for better results.",    "author": "John Doe",    "tags": "redis,search,advanced",    "publishedAt": "2024-03-20T14:30:00Z",    "updatedAt": "2024-03-22T09:15:00Z",    "published": "true",    "viewCount": "892",})redis.hset("article:3", {    "title": "Building Real-Time Search with Redis",    "body": "Real-time search requires instant indexing and low-latency queries. Redis excels at both. When you write data to Redis, the search index updates automatically. Queries execute in milliseconds, even with millions of documents. This makes Redis ideal for applications where search results must reflect the latest data.",    "summary": "Build search features that update instantly as data changes.",    "author": "Jane Smith",    "tags": "redis,real-time,performance",    "publishedAt": "2024-03-25T08:00:00Z",    "updatedAt": "2024-03-25T08:00:00Z",    "published": "true",    "viewCount": "2103",})
HSET article:1 title "Getting Started with Redis Search" body "Redis Search provides powerful full-text search capabilities directly in Redis. In this tutorial, we will explore how to create indexes, define schemas, and write queries. Full-text search allows you to find documents based on their content rather than just their keys. This is essential for building search features in modern applications." summary "Learn how to add full-text search to your Redis application with practical examples." author "Jane Smith" tags "redis,search,tutorial" publishedAt "2024-03-15T10:00:00Z" updatedAt "2024-03-15T10:00:00Z" published "true" viewCount "1542"HSET article:2 title "Advanced Query Techniques for Search" body "Once you have mastered the basics, it is time to explore advanced query techniques. Boolean operators let you combine conditions with AND, OR, and NOT logic. Phrase matching ensures words appear in sequence. Fuzzy matching handles typos gracefully. Together, these features enable sophisticated search experiences." summary "Master boolean operators, phrase matching, and fuzzy search for better results." author "John Doe" tags "redis,search,advanced" publishedAt "2024-03-20T14:30:00Z" updatedAt "2024-03-22T09:15:00Z" published "true" viewCount "892"HSET article:3 title "Building Real-Time Search with Redis" body "Real-time search requires instant indexing and low-latency queries. Redis excels at both. When you write data to Redis, the search index updates automatically. Queries execute in milliseconds, even with millions of documents. This makes Redis ideal for applications where search results must reflect the latest data." summary "Build search features that update instantly as data changes." author "Jane Smith" tags "redis,real-time,performance" publishedAt "2024-03-25T08:00:00Z" updatedAt "2024-03-25T08:00:00Z" published "true" viewCount "2103"

Waiting for Indexing

Index updates are batched for performance, so newly added data may not appear in search results immediately. Use SEARCH.WAITINDEXING to ensure all pending updates are processed before querying:

await articles.waitIndexing();
articles.wait_indexing()
SEARCH.WAITINDEXING articles

Smart matching handles natural language queries across title and body:

// Search across title and bodyconst results = await articles.query({  filter: {    $should: [      { title: "redis search" },      { body: "redis search" },    ],  },});
# Search across title and bodyresults = articles.query(    filter={        "$should": [            {"title": "redis search"},            {"body": "redis search"},        ],    },)
SEARCH.QUERY articles '{"$should": [{"title": "redis search"}, {"body": "redis search"}]}'

Search with Highlighted Results

Highlighting shows users why each result matched their query:

// Search with highlighted matches in title and bodyconst results = await articles.query({  filter: {    $should: [      { title: "full-text search" },      { body: "full-text search" },    ],  },  highlight: {    fields: ["title", "body"],  },});// Results include highlighted text like:// "Redis Search provides powerful <em>full-text</em> <em>search</em> capabilities..."
# Search with highlighted matches in title and bodyresults = articles.query(    filter={        "$should": [            {"title": "full-text search"},            {"body": "full-text search"},        ],    },    highlight={"fields": ["title", "body"]},)# Results include highlighted text like:# "Redis Search provides powerful <em>full-text</em> <em>search</em> capabilities..."
SEARCH.QUERY articles '{"$should": [{"title": "full-text search"}, {"body": "full-text search"}]}' HIGHLIGHT FIELDS 2 title body

Custom Highlight Tags

Use custom tags for different rendering contexts:

// Markdown-style highlightingconst results = await articles.query({  filter: {    body: "redis",  },  highlight: {    fields: ["body"],    preTag: "**",    postTag: "**",  },});// HTML with custom classconst htmlResults = await articles.query({  filter: {    body: "redis",  },  highlight: {    fields: ["body"],    preTag: "<mark class='search-match'>",    postTag: "</mark>",  },});
# Markdown-style highlightingresults = articles.query(    filter={"body": "redis"},    highlight={"fields": ["body"], "preTag": "**", "postTag": "**"},)# HTML with custom classhtml_results = articles.query(    filter={"body": "redis"},    highlight={"fields": ["body"], "preTag": "<mark class='search-match'>", "postTag": "</mark>"},)
# Markdown-style highlightingSEARCH.QUERY articles '{"body": "redis"}' HIGHLIGHT FIELDS 1 body TAGS ** **# HTML with custom classSEARCH.QUERY articles '{"body": "redis"}' HIGHLIGHT FIELDS 1 body TAGS "<mark class='search-match'>" "</mark>"

Find articles containing exact phrases using double quotes or the $phrase operator:

// Using double quotes for exact phraseconst results = await articles.query({  filter: {    body: "\"full-text search\"",  },});// Using $phrase operatorconst phraseResults = await articles.query({  filter: {    body: {      $phrase: "boolean operators",    },  },});// Phrase with slop (allow words between)// Matches "search results" or "search the results" or "search for better results"const slopResults = await articles.query({  filter: {    body: {      $phrase: {        value: "search results",        slop: 3,      },    },  },});
# Using double quotes for exact phraseresults = articles.query(    filter={"body": '"full-text search"'},)# Using $phrase operatorphrase_results = articles.query(    filter={"body": {"$phrase": "boolean operators"}},)# Phrase with slop (allow words between)# Matches "search results" or "search the results" or "search for better results"slop_results = articles.query(    filter={        "body": {            "$phrase": {                "value": "search results",                "slop": 3,            },        },    },)
# Using double quotes for exact phraseSEARCH.QUERY articles '{"body": "\"full-text search\""}'# Using $phrase operatorSEARCH.QUERY articles '{"body": {"$phrase": "boolean operators"}}'# Phrase with slopSEARCH.QUERY articles '{"body": {"$phrase": {"value": "search results", "slop": 3}}}'

Filter by Author

Find all articles by a specific author:

// All articles by Jane Smithconst results = await articles.query({  filter: {    author: "Jane Smith",    published: true,  },  orderBy: {    publishedAt: "DESC",  },});// Search within a specific author's articlesconst authorSearch = await articles.query({  filter: {    $must: {      author: "Jane Smith",      body: "redis",    },  },});
# All articles by Jane Smithresults = articles.query(    filter={"author": "Jane Smith", "published": True},    order_by={"publishedAt": "DESC"},)# Search within a specific author's articlesauthor_search = articles.query(    filter={        "$must": {            "author": "Jane Smith",            "body": "redis",        },    },)
# All articles by Jane SmithSEARCH.QUERY articles '{"author": "Jane Smith", "published": true}' ORDERBY publishedAt DESC# Search within a specific author's articlesSEARCH.QUERY articles '{"$must": {"author": "Jane Smith", "body": "redis"}}'

Date Range Queries

Find articles published within a specific time period:

// Articles from a specific monthconst marchArticles = await articles.query({  filter: {    publishedAt: {      $gte: "2026-01-01T00:00:00Z",      $lt: "2026-02-01T00:00:00Z",    },  },  orderBy: {    publishedAt: "DESC",  },});
# Articles from a specific monthmarch_articles = articles.query(    filter={        "publishedAt": {            "$gte": "2026-01-01T00:00:00Z",            "$lt": "2026-02-01T00:00:00Z",        },    },    order_by={"publishedAt": "DESC"},)
# Articles from a specific monthSEARCH.QUERY articles '{"publishedAt": {"$gte": "2026-01-01T00:00:00Z", "$lt": "2026-02-01T00:00:00Z"}}' ORDERBY publishedAt DESC

Sort by view count to find popular content:

// Most popular articlesconst popular = await articles.query({  filter: {    published: true,  },  orderBy: {    viewCount: "DESC",  },  limit: 10,});// Popular articles about a topicconst popularRedis = await articles.query({  filter: {    $must: {      body: "redis",      published: true,    },  },  orderBy: {    viewCount: "DESC",  },  limit: 5,});
# Most popular articlespopular = articles.query(    filter={"published": True},    order_by={"viewCount": "DESC"},    limit=10,)# Popular articles about a topicpopular_redis = articles.query(    filter={        "$must": {            "body": "redis",            "published": True,        },    },    order_by={"viewCount": "DESC"},    limit=5,)
# Most popular articlesSEARCH.QUERY articles '{"published": true}' ORDERBY viewCount DESC LIMIT 10# Popular articles about a topicSEARCH.QUERY articles '{"$must": {"body": "redis", "published": true}}' ORDERBY viewCount DESC LIMIT 5

Boosting Title Matches

Prioritize matches in the title over body text:

// Boost title matches for better relevanceconst results = await articles.query({  filter: {    $should: [      { title: "redis search", $boost: 5.0 }, // Title matches score 5x higher      { body: "redis search" },      { summary: "redis search", $boost: 2.0 },    ],  },});
# Boost title matches for better relevanceresults = articles.query(    filter={        "$should": [            {"title": "redis search", "$boost": 5.0},  # Title matches score 5x higher            {"body": "redis search"},            {"summary": "redis search", "$boost": 2.0},        ],    },)
SEARCH.QUERY articles '{"$should": [{"title": "redis search", "$boost": 5.0}, {"body": "redis search"}, {"summary": "redis search", "$boost": 2.0}]}'

Key Takeaways

  • Hash storage works well for flat document structures like blog articles
  • Use highlighting to show users why results matched their query
  • Boost title matches over body text for better relevance
  • Use $phrase with slop for flexible phrase matching
  • Combine date ranges with text search for temporal filtering
  • Mark viewCount as FAST to enable popularity sorting
  • Filter drafts using published: true in $must conditions
Loading search…