A modern, high performance Redis client for Node.js with full TypeScript support and all Redis Stack modules
—
Full-text search, indexing, and aggregation capabilities using RediSearch for complex query operations on Redis data. Provides powerful search features including full-text search, numeric filtering, geo-spatial queries, and advanced aggregation.
Operations for creating, managing, and configuring search indexes.
/**
* Create a search index with schema definition
* @param index - Name of the index
* @param schema - Index schema definition
* @param options - Optional index configuration
* @returns 'OK'
*/
function create(index: string, schema: RediSearchSchema, options?: FtCreateOptions): Promise<SimpleStringReply<'OK'>>;
/**
* Get information about a search index
* @param index - Name of the index
* @returns Array containing index information
*/
function info(index: string): Promise<ArrayReply>;
/**
* Drop a search index
* @param index - Name of the index
* @param options - Optional drop configuration
* @returns 'OK'
*/
function dropIndex(index: string, options?: FtDropIndexOptions): Promise<SimpleStringReply<'OK'>>;
/**
* Alter an existing index by adding new fields
* @param index - Name of the index
* @param schema - New fields to add to the schema
* @returns 'OK'
*/
function alter(index: string, schema: RediSearchSchema): Promise<SimpleStringReply<'OK'>>;
/**
* List all search indexes
* @returns Array of index names
*/
function _list(): Promise<ArrayReply<BlobStringReply>>;
interface RediSearchSchema {
[fieldName: string]: SchemaFieldDefinition;
}
interface SchemaFieldDefinition {
/** Field type */
type: SchemaFieldType;
/** Field is sortable */
SORTABLE?: boolean;
/** Field cannot be searched */
NOINDEX?: boolean;
/** Store original value for highlighting */
NOSTEM?: boolean;
/** Text field specific options */
WEIGHT?: number;
PHONETIC?: SchemaTextFieldPhonetic;
/** Numeric/geo field options */
SEPARATOR?: string;
/** Vector field options */
ALGORITHM?: SchemaVectorFieldAlgorithm;
DIM?: number;
DISTANCE_METRIC?: 'L2' | 'IP' | 'COSINE';
}
type SchemaFieldType = 'TEXT' | 'NUMERIC' | 'GEO' | 'TAG' | 'VECTOR';
type SchemaTextFieldPhonetic = 'dm:en' | 'dm:fr' | 'fm';
type SchemaVectorFieldAlgorithm = 'FLAT' | 'HNSW';
interface FtCreateOptions {
/** Key prefix for indexed documents */
PREFIX?: string | string[];
/** Custom key filter expression */
FILTER?: string;
/** Default language for text fields */
LANGUAGE?: RediSearchLanguage;
/** Default field for searches */
LANGUAGE_FIELD?: string;
/** Default score for documents */
SCORE?: number;
/** Field containing document score */
SCORE_FIELD?: string;
/** Field containing document payload */
PAYLOAD_FIELD?: string;
/** Maximum number of text records */
MAXTEXTFIELDS?: boolean;
/** Disable stemming */
NOSTEM?: boolean;
/** Disable stop words */
NOFREQS?: boolean;
/** Disable term offset vectors */
NOOFFSETS?: boolean;
/** Number of background indexing threads */
NOHL?: boolean;
/** Custom stop words */
STOPWORDS?: string[];
}
interface FtDropIndexOptions {
/** Also delete the documents */
DD?: boolean;
}
type RediSearchLanguage = 'arabic' | 'danish' | 'dutch' | 'english' | 'finnish' | 'french' | 'german' | 'hungarian' | 'italian' | 'norwegian' | 'portuguese' | 'romanian' | 'russian' | 'spanish' | 'swedish' | 'turkish' | 'chinese';Usage Examples:
import { createClient } from "redis";
const client = createClient();
await client.connect();
// Create a search index for products
await client.ft.create("products", {
name: { type: 'TEXT', SORTABLE: true },
description: { type: 'TEXT' },
price: { type: 'NUMERIC', SORTABLE: true },
category: { type: 'TAG', SORTABLE: true },
location: { type: 'GEO' },
tags: { type: 'TAG', SEPARATOR: ',' }
}, {
PREFIX: 'product:',
LANGUAGE: 'english'
});
// Get index information
const indexInfo = await client.ft.info("products");
console.log("Index info:", indexInfo);
// List all indexes
const indexes = await client.ft._list();
console.log("Available indexes:", indexes);
// Add new field to existing index
await client.ft.alter("products", {
brand: { type: 'TAG', SORTABLE: true }
});
// Drop index (keeping documents)
await client.ft.dropIndex("products");
// Drop index and delete documents
await client.ft.dropIndex("products", { DD: true });Core search functionality with query parsing and result formatting.
/**
* Search the index with a query
* @param index - Name of the index
* @param query - Search query string
* @param options - Optional search parameters
* @returns Search results with documents and metadata
*/
function search(index: string, query: string, options?: FtSearchOptions): Promise<SearchReply>;
/**
* Search without returning document content
* @param index - Name of the index
* @param query - Search query string
* @param options - Optional search parameters
* @returns Search results with document IDs only
*/
function searchNoContent(index: string, query: string, options?: FtSearchOptions): Promise<SearchReply>;
/**
* Explain query execution plan
* @param index - Name of the index
* @param query - Search query string
* @param options - Optional query options
* @returns Query execution plan
*/
function explain(index: string, query: string, options?: FtExplainOptions): Promise<BlobStringReply>;
/**
* Profile search query performance
* @param index - Name of the index
* @param query - Search query string
* @param options - Optional search parameters
* @returns Profiling information with search results
*/
function profileSearch(index: string, query: string, options?: FtSearchOptions): Promise<ArrayReply>;
interface FtSearchOptions {
/** Return only document IDs */
NOCONTENT?: boolean;
/** Include term frequency information */
WITHSCORES?: boolean;
/** Include payload information */
WITHPAYLOADS?: boolean;
/** Sort by field */
SORTBY?: {
BY: string;
DIRECTION?: 'ASC' | 'DESC';
};
/** Limit results */
LIMIT?: {
from: number;
size: number;
};
/** Specific fields to return */
RETURN?: string[];
/** Highlight matching terms */
HIGHLIGHT?: {
FIELDS?: string[];
TAGS?: {
open: string;
close: string;
};
};
/** Summarize fields */
SUMMARIZE?: {
FIELDS?: string[];
FRAGS?: number;
LEN?: number;
SEPARATOR?: string;
};
/** Default language */
LANGUAGE?: RediSearchLanguage;
/** Expression filter */
FILTER?: string;
/** Geographic filter */
GEOFILTER?: {
field: string;
lon: number;
lat: number;
radius: number;
unit: 'm' | 'km' | 'mi' | 'ft';
};
/** Include term position information */
INKEYS?: string[];
/** Include tag values */
INFIELDS?: string[];
/** Return raw document values */
RETURN_RAW?: boolean;
/** Disable stemming */
NOSTEM?: boolean;
/** Custom scoring function */
SCORER?: string;
}
interface SearchReply {
/** Total number of results */
total: number;
/** Array of document results */
documents: SearchDocument[];
}
interface SearchDocument {
/** Document ID */
id: string;
/** Document score */
score?: number;
/** Document payload */
payload?: string;
/** Document fields */
value: Record<string, string>;
}Usage Examples:
// Add some sample data first
await client.hSet("product:1", {
name: "Smartphone",
description: "Latest Android smartphone with great camera",
price: "599",
category: "electronics",
tags: "mobile,android,camera"
});
await client.hSet("product:2", {
name: "Laptop",
description: "High-performance laptop for developers",
price: "1299",
category: "computers",
tags: "laptop,development,performance"
});
// Basic search
const results = await client.ft.search("products", "smartphone");
console.log(`Found ${results.total} results:`, results.documents);
// Search with options
const searchResults = await client.ft.search("products", "camera OR laptop", {
WITHSCORES: true,
SORTBY: { BY: "price", DIRECTION: "ASC" },
LIMIT: { from: 0, size: 10 },
HIGHLIGHT: {
FIELDS: ["name", "description"],
TAGS: { open: "<mark>", close: "</mark>" }
}
});
// Filtered search
const electronicsResults = await client.ft.search("products", "*", {
FILTER: "@category:{electronics}",
SORTBY: { BY: "price", DIRECTION: "DESC" }
});
// Price range search
const priceResults = await client.ft.search("products", "@price:[500 1000]");
// Geographic search (if location field exists)
const nearbyResults = await client.ft.search("products", "*", {
GEOFILTER: {
field: "location",
lon: -122.4194,
lat: 37.7749,
radius: 10,
unit: "km"
}
});
// Get search explanation
const explanation = await client.ft.explain("products", "smartphone camera");
console.log("Query plan:", explanation);Advanced aggregation operations for complex data analysis and reporting.
/**
* Perform aggregation query on search index
* @param index - Name of the index
* @param query - Search query string
* @param options - Aggregation pipeline steps
* @returns Aggregation results
*/
function aggregate(index: string, query: string, ...options: FtAggregateStep[]): Promise<ArrayReply>;
/**
* Perform aggregation with cursor for large result sets
* @param index - Name of the index
* @param query - Search query string
* @param options - Aggregation options with cursor configuration
* @returns Aggregation results with cursor
*/
function aggregateWithCursor(index: string, query: string, options?: FtAggregateWithCursorOptions): Promise<ArrayReply>;
/**
* Profile aggregation query performance
* @param index - Name of the index
* @param query - Search query string
* @param options - Aggregation pipeline steps
* @returns Profiling information with aggregation results
*/
function profileAggregate(index: string, query: string, ...options: FtAggregateStep[]): Promise<ArrayReply>;
type FtAggregateStep =
| { type: 'GROUPBY'; fields: string[]; reducers: FtAggregateGroupByReducer[] }
| { type: 'SORTBY'; fields: Array<{ field: string; direction?: 'ASC' | 'DESC' }> }
| { type: 'APPLY'; expression: string; as: string }
| { type: 'LIMIT'; offset: number; count: number }
| { type: 'FILTER'; expression: string };
type FtAggregateGroupByReducer =
| { type: 'COUNT'; as?: string }
| { type: 'COUNT_DISTINCT'; field: string; as?: string }
| { type: 'SUM'; field: string; as?: string }
| { type: 'MIN'; field: string; as?: string }
| { type: 'MAX'; field: string; as?: string }
| { type: 'AVG'; field: string; as?: string }
| { type: 'STDDEV'; field: string; as?: string }
| { type: 'TOLIST'; field: string; as?: string };
interface FtAggregateWithCursorOptions {
/** Cursor read size */
WITHCURSOR?: {
COUNT?: number;
MAXIDLE?: number;
};
/** Aggregation steps */
steps?: FtAggregateStep[];
}Usage Examples:
// Group by category and count
const categoryStats = await client.ft.aggregate("products", "*",
{
type: 'GROUPBY',
fields: ['@category'],
reducers: [
{ type: 'COUNT', as: 'count' },
{ type: 'AVG', field: '@price', as: 'avg_price' },
{ type: 'MIN', field: '@price', as: 'min_price' },
{ type: 'MAX', field: '@price', as: 'max_price' }
]
},
{
type: 'SORTBY',
fields: [{ field: '@count', direction: 'DESC' }]
}
);
// Price range analysis
const priceRanges = await client.ft.aggregate("products", "*",
{
type: 'APPLY',
expression: 'floor(@price/100)*100',
as: 'price_range'
},
{
type: 'GROUPBY',
fields: ['@price_range'],
reducers: [
{ type: 'COUNT', as: 'products_in_range' }
]
},
{
type: 'SORTBY',
fields: [{ field: '@price_range', direction: 'ASC' }]
}
);
// Complex aggregation with multiple steps
const complexAgg = await client.ft.aggregate("products", "electronics",
{
type: 'FILTER',
expression: '@price > 100'
},
{
type: 'APPLY',
expression: '@price * 0.1',
as: 'tax'
},
{
type: 'GROUPBY',
fields: ['@category'],
reducers: [
{ type: 'COUNT', as: 'count' },
{ type: 'SUM', field: '@price', as: 'total_price' },
{ type: 'SUM', field: '@tax', as: 'total_tax' }
]
},
{
type: 'LIMIT',
offset: 0,
count: 10
}
);Cursor-based operations for handling large result sets efficiently.
/**
* Read from aggregation cursor
* @param index - Name of the index
* @param cursor - Cursor ID
* @param options - Read options
* @returns Next batch of results
*/
function cursorRead(index: string, cursor: number, options?: FtCursorReadOptions): Promise<ArrayReply>;
/**
* Delete aggregation cursor
* @param index - Name of the index
* @param cursor - Cursor ID
* @returns 'OK'
*/
function cursorDel(index: string, cursor: number): Promise<SimpleStringReply<'OK'>>;
interface FtCursorReadOptions {
/** Number of results to read */
COUNT?: number;
}Usage Examples:
// Create aggregation with cursor
const cursorResult = await client.ft.aggregateWithCursor("products", "*", {
WITHCURSOR: { COUNT: 100, MAXIDLE: 300000 },
steps: [
{
type: 'GROUPBY',
fields: ['@category'],
reducers: [{ type: 'COUNT', as: 'count' }]
}
]
});
// Extract cursor ID from result
const cursorId = cursorResult[cursorResult.length - 1];
// Read more results
const moreResults = await client.ft.cursorRead("products", cursorId, { COUNT: 50 });
// Clean up cursor
await client.ft.cursorDel("products", cursorId);Auto-complete and suggestion functionality.
/**
* Add suggestion to auto-complete dictionary
* @param key - Dictionary key
* @param string - Suggestion string
* @param score - Suggestion score
* @param options - Optional payload and increment behavior
* @returns Number of suggestions added
*/
function sugAdd(key: string, string: string, score: number, options?: FtSugAddOptions): Promise<NumberReply>;
/**
* Get suggestions from auto-complete dictionary
* @param key - Dictionary key
* @param prefix - Prefix to match
* @param options - Optional parameters for fuzzy matching and limits
* @returns Array of matching suggestions
*/
function sugGet(key: string, prefix: string, options?: FtSugGetOptions): Promise<ArrayReply<BlobStringReply>>;
/**
* Get suggestions with scores
* @param key - Dictionary key
* @param prefix - Prefix to match
* @param options - Optional parameters
* @returns Array of suggestions with scores
*/
function sugGetWithScores(key: string, prefix: string, options?: FtSugGetOptions): Promise<ArrayReply>;
/**
* Delete suggestion from dictionary
* @param key - Dictionary key
* @param string - Suggestion string to delete
* @returns 1 if deleted, 0 if not found
*/
function sugDel(key: string, string: string): Promise<BooleanReply>;
/**
* Get number of suggestions in dictionary
* @param key - Dictionary key
* @returns Number of suggestions
*/
function sugLen(key: string): Promise<NumberReply>;
interface FtSugAddOptions {
/** Increment score if suggestion exists */
INCR?: boolean;
/** Optional payload data */
PAYLOAD?: string;
}
interface FtSugGetOptions {
/** Enable fuzzy matching */
FUZZY?: boolean;
/** Maximum number of results */
MAX?: number;
/** Include scores in results */
WITHSCORES?: boolean;
/** Include payloads in results */
WITHPAYLOADS?: boolean;
}Usage Examples:
// Add suggestions to auto-complete
await client.ft.sugAdd("product_suggestions", "smartphone", 1.0);
await client.ft.sugAdd("product_suggestions", "smart tv", 0.8);
await client.ft.sugAdd("product_suggestions", "smart watch", 0.9, { PAYLOAD: "category:wearable" });
// Get suggestions
const suggestions = await client.ft.sugGet("product_suggestions", "smart", { MAX: 5 });
// ["smartphone", "smart watch", "smart tv"]
// Get suggestions with scores
const withScores = await client.ft.sugGetWithScores("product_suggestions", "smart", { MAX: 3 });
// ["smartphone", "1", "smart watch", "0.9", "smart tv", "0.8"]
// Fuzzy matching
const fuzzy = await client.ft.sugGet("product_suggestions", "smar", { FUZZY: true, MAX: 3 });
// Get suggestion count
const count = await client.ft.sugLen("product_suggestions"); // 3
// Delete suggestion
await client.ft.sugDel("product_suggestions", "smart tv");Operations for managing dictionaries and synonyms.
/**
* Add terms to dictionary
* @param dictionary - Dictionary name
* @param terms - Terms to add
* @returns Number of new terms added
*/
function dictAdd(dictionary: string, ...terms: string[]): Promise<NumberReply>;
/**
* Delete terms from dictionary
* @param dictionary - Dictionary name
* @param terms - Terms to delete
* @returns Number of terms deleted
*/
function dictDel(dictionary: string, ...terms: string[]): Promise<NumberReply>;
/**
* Dump all terms in dictionary
* @param dictionary - Dictionary name
* @returns Array of all terms in dictionary
*/
function dictDump(dictionary: string): Promise<ArrayReply<BlobStringReply>>;
/**
* Update synonym group
* @param index - Index name
* @param groupId - Synonym group ID
* @param terms - Terms in the synonym group
* @param options - Optional skip initial scan
* @returns 'OK'
*/
function synUpdate(index: string, groupId: string, terms: string[], options?: { SKIPINITIALSCAN?: boolean }): Promise<SimpleStringReply<'OK'>>;
/**
* Dump synonym groups
* @param index - Index name
* @returns Array of synonym group information
*/
function synDump(index: string): Promise<ArrayReply>;Usage Examples:
// Dictionary management
await client.ft.dictAdd("stopwords", "the", "a", "an", "and", "or", "but");
const stopwordCount = await client.ft.dictAdd("stopwords", "in", "on", "at"); // Returns count of new words
// View dictionary contents
const allStopwords = await client.ft.dictDump("stopwords");
// Remove words from dictionary
await client.ft.dictDel("stopwords", "but", "or");
// Synonym management
await client.ft.synUpdate("products", "smartphones", [
"smartphone", "mobile phone", "cell phone", "mobile device"
]);
await client.ft.synUpdate("products", "laptops", [
"laptop", "notebook", "portable computer"
]);
// View synonyms
const synonyms = await client.ft.synDump("products");Index alias operations for flexible index management.
/**
* Add alias for an index
* @param alias - Alias name
* @param index - Target index name
* @returns 'OK'
*/
function aliasAdd(alias: string, index: string): Promise<SimpleStringReply<'OK'>>;
/**
* Delete an alias
* @param alias - Alias name to delete
* @returns 'OK'
*/
function aliasDel(alias: string): Promise<SimpleStringReply<'OK'>>;
/**
* Update alias to point to different index
* @param alias - Alias name
* @param index - New target index name
* @returns 'OK'
*/
function aliasUpdate(alias: string, index: string): Promise<SimpleStringReply<'OK'>>;Usage Examples:
// Create alias for current index
await client.ft.aliasAdd("current_products", "products_v1");
// Use alias in searches (same as using index name)
const results = await client.ft.search("current_products", "smartphone");
// Update alias to point to new index version
await client.ft.aliasUpdate("current_products", "products_v2");
// Delete alias when no longer needed
await client.ft.aliasDel("current_products");RediSearch supports powerful query syntax for complex searches:
// Query syntax examples:
"hello world" // Full-text search for both terms
"hello | world" // OR search
"hello -world" // Exclude term
"\"hello world\"" // Exact phrase
"hello*" // Prefix search
"%hello%" // Fuzzy search
"@title:hello" // Field-specific search
"@price:[100 200]" // Numeric range
"@location:[lat lon radius unit]" // Geographic filter
"@tags:{electronics|computers}" // Tag filter
"(@title:hello) (@price:[100 200])" // Complex queryconst REDISEARCH_LANGUAGE = {
ARABIC: 'arabic',
DANISH: 'danish',
DUTCH: 'dutch',
ENGLISH: 'english',
FINNISH: 'finnish',
FRENCH: 'french',
GERMAN: 'german',
HUNGARIAN: 'hungarian',
ITALIAN: 'italian',
NORWEGIAN: 'norwegian',
PORTUGUESE: 'portuguese',
ROMANIAN: 'romanian',
RUSSIAN: 'russian',
SPANISH: 'spanish',
SWEDISH: 'swedish',
TURKISH: 'turkish',
CHINESE: 'chinese'
} as const;
const SCHEMA_FIELD_TYPE = {
TEXT: 'TEXT',
NUMERIC: 'NUMERIC',
GEO: 'GEO',
TAG: 'TAG',
VECTOR: 'VECTOR'
} as const;
const FT_AGGREGATE_GROUP_BY_REDUCERS = {
COUNT: 'COUNT',
COUNT_DISTINCT: 'COUNT_DISTINCT',
SUM: 'SUM',
MIN: 'MIN',
MAX: 'MAX',
AVG: 'AVG',
STDDEV: 'STDDEV',
TOLIST: 'TOLIST'
} as const;Install with Tessl CLI
npx tessl i tessl/npm-redis