Valkey: Search and JSON

Store and query JSON documents with the Valkey search module

👋 Welcome to the Stackhero documentation!

Stackhero provides a ready-to-use Valkey cloud solution offering numerous advantages, including:

  • Redis Commander web interface included.
  • Unlimited message size and transfers.
  • One-click simplified updates.
  • Optimal performance and enhanced security thanks to a private, dedicated infrastructure.

Save time and make your life easier: it only takes 5 minutes to try Stackhero's Valkey cloud hosting solution!

Your Valkey instance on Stackhero includes essential modules maintained by the Valkey project. These modules make Valkey much more than just a cache: you can store native JSON documents, index your data, and perform tag, numeric, text, and vector queries, all without having to manage a separate database.

Three modules are available, each included in your plan and licensed under the same permissive BSD 3-Clause licence as Valkey:

  • JSON (JSON.* commands): Store and update structured JSON documents with JSONPath support. You can read or modify individual fields directly, so you never need to rewrite the entire document for minor changes.
  • Search (FT.* commands): Perform secondary indexing, tag, numeric, and full-text queries, as well as vector similarity search.
  • Bloom (BF.* commands): Use Bloom filters to efficiently answer "have I already seen this?" across large datasets, while using minimal memory.

Modules are disabled by default, so your existing service retains its current behaviour until you enable them.

  1. Open your service on the Stackhero dashboard.
  2. Go to the service configuration.
  3. In the Modules section, select the modules you wish to enable.
  4. Save your changes.

Valkey restarts to load the selected modules. This usually takes just a few seconds.

If you have stored data using a module (such as a JSON document or a Bloom filter), keep that module enabled. Valkey cannot read that data without the module that created it. Disabling the module would prevent your service from restarting successfully.

You can check which modules are currently loaded at any time:

valkey-cli -u "rediss://default:<yourPassword>@<XXXXXX>.stackhero-network.com:<PORT_TLS>" MODULE LIST

You can store structured documents natively as JSON, without serialising them into strings:

JSON.SET product:1 $ '{"name":"Espresso machine","brand":"Bianca","price":459,"stock":12}'

Reading or updating a single field is fast, atomic, and handled server-side:

JSON.GET product:1 $.price
# "[459]"

JSON.NUMINCRBY product:1 $.stock -1
# "[11]"

This approach removes the need to fetch and parse the entire document in your application for every update. For example, decrementing a stock counter becomes a single atomic operation. No more race conditions or extra code.

The JSON.* command set matches the API of RedisJSON, so most Valkey and Redis clients work without modification.

Create an index once, and Valkey keeps it up to date for every key matching your prefix, even for keys written before the index was created:

FT.CREATE productsIndex
  ON HASH
  PREFIX 1 product:
  SCHEMA
    name TEXT
    brand TAG
    price NUMERIC

You can query your indexed data with powerful search commands:

# Exact tag match
FT.SEARCH productsIndex "@brand:{Bianca}"

# Numeric range
FT.SEARCH productsIndex "@price:[0 500]"

# Combined query
FT.SEARCH productsIndex "@brand:{Bianca} @price:[0 500]"

# Full-text search by name
FT.SEARCH productsIndex "espresso"

You can also run aggregations on the same index with FT.AGGREGATE. The FT.INFO command allows you to check the index status, including the progress of background indexing jobs.

JSON documents are also indexable. When the JSON module is enabled, you can use ON JSON and JSONPath expressions in your schema.

Because Valkey uses the same protocol as Redis, any Redis client is compatible. You can use this example with the official node-redis client:

import { createClient, SCHEMA_FIELD_TYPE } from 'redis';

const client = createClient({ url: process.env.STACKHERO_VALKEY_URL_TLS });
await client.connect();

// Create the index once at startup
try {
  await client.ft.create(
    'productsIndex',
    {
      name: SCHEMA_FIELD_TYPE.TEXT,
      brand: SCHEMA_FIELD_TYPE.TAG,
      price: SCHEMA_FIELD_TYPE.NUMERIC
    },
    { ON: 'HASH', PREFIX: 'product:' }
  );
} catch (error) {
  if (!error.message.includes('Index already exists')) {
    throw error;
  }
}

await client.hSet('product:1', { name: 'Espresso machine', brand: 'Bianca', price: '459' });

const results = await client.ft.search('productsIndex', '@brand:{Bianca} @price:[0 500]');
console.log(results.total, results.documents);

await client.quit();

You can use the redis-py client to make use of Valkey's search features:

import os
import redis
from redis.commands.search.field import TextField, TagField, NumericField
from redis.commands.search.index_definition import IndexDefinition, IndexType
from redis.commands.search.query import Query

r = redis.from_url(os.environ['STACKHERO_VALKEY_URL_TLS'], decode_responses=True)

# Create the index once at startup
try:
    r.ft('productsIndex').create_index(
        (TextField('name'), TagField('brand'), NumericField('price')),
        definition=IndexDefinition(prefix=['product:'], index_type=IndexType.HASH),
    )
except redis.ResponseError as error:
    if 'Index already exists' not in str(error):
        raise

r.hset('product:1', mapping={'name': 'Espresso machine', 'brand': 'Bianca', 'price': 459})

results = r.ft('productsIndex').search(Query('@brand:{Bianca} @price:[0 500]'))
print(results.total, results.docs)
  • The search module provides a focused set of commands: FT.CREATE, FT.SEARCH, FT.AGGREGATE, FT.INFO, FT.DROPINDEX, and FT._LIST. These efficiently cover tag, numeric, text, and vector queries. If your application requires advanced full-text features (such as spell checking, synonyms, or suggestion dictionaries), the Redis query engine offers more advanced capabilities.
  • Indexes are per-database and work within each database. For example, an index created in database 3 is only visible and queryable from database 3. This is an area where Valkey goes beyond Redis database selection, which only supports indexing on database 0.
  • Indexes use your plan's memory, just like your data. You can monitor used_memory in your Prometheus metrics after building an index.
  • Indexing existing keys runs in the background. Immediately after running FT.CREATE, search results may be incomplete for a few seconds while the index is being built.
  • All module commands are enabled. Every command from every module is available on your service, with no restrictions.

If you would like to explore vector similarity, semantic search, or retrieval-augmented generation, see the vector search guide.