User Directory
This recipe demonstrates building a searchable employee directory with autocomplete, fuzzy name matching, and department filtering.
Schema Design
The schema uses nested fields for profile data and exact matching for identifiers:
import { Redis, s } from "@upstash/redis";const redis = Redis.fromEnv();const users = await redis.search.createIndex({ name: "users", dataType: "json", prefix: "user:", schema: s.object({ // Exact username matching (no tokenization) username: s.string().noTokenize(), // Nested profile fields profile: s.object({ // Name search without stemming (proper nouns) firstName: s.string().noStem(), lastName: s.string().noStem(), displayName: s.string().noStem(), // Email as exact match (contains special characters) email: s.string().noTokenize(), // Searchable bio/about text bio: s.string(), }), // Department and role for filtering department: s.string().noTokenize(), role: s.string().noTokenize(), title: s.string(), // Boolean flags isActive: s.boolean(), isAdmin: s.boolean(), // Dates for filtering hiredAt: s.date().fast(), lastActiveAt: s.date().fast(), }),});from upstash_redis import Redisredis = Redis.from_env()users = redis.search.create_index( name="users", data_type="json", prefix="user:", schema={ # Exact username matching (no tokenization) "username": {"type": "TEXT", "notokenize": True}, # Nested profile fields # Name search without stemming (proper nouns) "profile.firstName": {"type": "TEXT", "nostem": True}, "profile.lastName": {"type": "TEXT", "nostem": True}, "profile.displayName": {"type": "TEXT", "nostem": True}, # Email as exact match (contains special characters) "profile.email": {"type": "TEXT", "notokenize": True}, # Searchable bio/about text "profile.bio": "TEXT", # Department and role for filtering "department": {"type": "TEXT", "notokenize": True}, "role": {"type": "TEXT", "notokenize": True}, "title": "TEXT", # Boolean flags "isActive": "BOOL", "isAdmin": "BOOL", # Dates for filtering "hiredAt": {"type": "DATE", "fast": True}, "lastActiveAt": {"type": "DATE", "fast": True}, },)SEARCH.CREATE users ON JSON PREFIX 1 user: SCHEMA username TEXT NOTOKENIZE profile.firstName TEXT NOSTEM profile.lastName TEXT NOSTEM profile.displayName TEXT NOSTEM profile.email TEXT NOTOKENIZE profile.bio TEXT department TEXT NOTOKENIZE role TEXT NOTOKENIZE title TEXT isActive BOOL isAdmin BOOL hiredAt DATE FAST lastActiveAt DATE FASTSample Data
await redis.json.set("user:1", "$", { username: "jsmith", profile: { firstName: "Jane", lastName: "Smith", displayName: "Jane Smith", email: "jane.smith@company.com", bio: "Senior software engineer focused on backend systems and distributed computing.", }, department: "Engineering", role: "Individual Contributor", title: "Senior Software Engineer", isActive: true, isAdmin: false, hiredAt: "2021-03-15T00:00:00Z", lastActiveAt: "2024-03-25T14:30:00Z",});await redis.json.set("user:2", "$", { username: "mjohnson", profile: { firstName: "Michael", lastName: "Johnson", displayName: "Mike Johnson", email: "michael.johnson@company.com", bio: "Engineering manager leading the platform team. Passionate about developer experience.", }, department: "Engineering", role: "Manager", title: "Engineering Manager", isActive: true, isAdmin: true, hiredAt: "2019-06-01T00:00:00Z", lastActiveAt: "2024-03-25T16:45:00Z",});await redis.json.set("user:3", "$", { username: "swilliams", profile: { firstName: "Sarah", lastName: "Williams", displayName: "Sarah Williams", email: "sarah.williams@company.com", bio: "Product designer specializing in user research and interaction design.", }, department: "Design", role: "Individual Contributor", title: "Senior Product Designer", isActive: true, isAdmin: false, hiredAt: "2022-01-10T00:00:00Z", lastActiveAt: "2024-03-24T11:20:00Z",});await redis.json.set("user:4", "$", { username: "rbrown", profile: { firstName: "Robert", lastName: "Brown", displayName: "Rob Brown", email: "robert.brown@company.com", bio: "Former engineering lead, now focused on technical writing and documentation.", }, department: "Engineering", role: "Individual Contributor", title: "Staff Engineer", isActive: false, isAdmin: false, hiredAt: "2018-09-20T00:00:00Z", lastActiveAt: "2023-12-15T09:00:00Z",});redis.json().set("user:1", "$", { "username": "jsmith", "profile": { "firstName": "Jane", "lastName": "Smith", "displayName": "Jane Smith", "email": "jane.smith@company.com", "bio": "Senior software engineer focused on backend systems and distributed computing.", }, "department": "Engineering", "role": "Individual Contributor", "title": "Senior Software Engineer", "isActive": True, "isAdmin": False, "hiredAt": "2021-03-15T00:00:00Z", "lastActiveAt": "2024-03-25T14:30:00Z",})redis.json().set("user:2", "$", { "username": "mjohnson", "profile": { "firstName": "Michael", "lastName": "Johnson", "displayName": "Mike Johnson", "email": "michael.johnson@company.com", "bio": "Engineering manager leading the platform team. Passionate about developer experience.", }, "department": "Engineering", "role": "Manager", "title": "Engineering Manager", "isActive": True, "isAdmin": True, "hiredAt": "2019-06-01T00:00:00Z", "lastActiveAt": "2024-03-25T16:45:00Z",})redis.json().set("user:3", "$", { "username": "swilliams", "profile": { "firstName": "Sarah", "lastName": "Williams", "displayName": "Sarah Williams", "email": "sarah.williams@company.com", "bio": "Product designer specializing in user research and interaction design.", }, "department": "Design", "role": "Individual Contributor", "title": "Senior Product Designer", "isActive": True, "isAdmin": False, "hiredAt": "2022-01-10T00:00:00Z", "lastActiveAt": "2024-03-24T11:20:00Z",})redis.json().set("user:4", "$", { "username": "rbrown", "profile": { "firstName": "Robert", "lastName": "Brown", "displayName": "Rob Brown", "email": "robert.brown@company.com", "bio": "Former engineering lead, now focused on technical writing and documentation.", }, "department": "Engineering", "role": "Individual Contributor", "title": "Staff Engineer", "isActive": False, "isAdmin": False, "hiredAt": "2018-09-20T00:00:00Z", "lastActiveAt": "2023-12-15T09:00:00Z",})JSON.SET user:1 $ '{"username": "jsmith", "profile": {"firstName": "Jane", "lastName": "Smith", "displayName": "Jane Smith", "email": "jane.smith@company.com", "bio": "Senior software engineer focused on backend systems and distributed computing."}, "department": "Engineering", "role": "Individual Contributor", "title": "Senior Software Engineer", "isActive": true, "isAdmin": false, "hiredAt": "2021-03-15T00:00:00Z", "lastActiveAt": "2024-03-25T14:30:00Z"}'JSON.SET user:2 $ '{"username": "mjohnson", "profile": {"firstName": "Michael", "lastName": "Johnson", "displayName": "Mike Johnson", "email": "michael.johnson@company.com", "bio": "Engineering manager leading the platform team. Passionate about developer experience."}, "department": "Engineering", "role": "Manager", "title": "Engineering Manager", "isActive": true, "isAdmin": true, "hiredAt": "2019-06-01T00:00:00Z", "lastActiveAt": "2024-03-25T16:45:00Z"}'JSON.SET user:3 $ '{"username": "swilliams", "profile": {"firstName": "Sarah", "lastName": "Williams", "displayName": "Sarah Williams", "email": "sarah.williams@company.com", "bio": "Product designer specializing in user research and interaction design."}, "department": "Design", "role": "Individual Contributor", "title": "Senior Product Designer", "isActive": true, "isAdmin": false, "hiredAt": "2022-01-10T00:00:00Z", "lastActiveAt": "2024-03-24T11:20:00Z"}'JSON.SET user:4 $ '{"username": "rbrown", "profile": {"firstName": "Robert", "lastName": "Brown", "displayName": "Rob Brown", "email": "robert.brown@company.com", "bio": "Former engineering lead, now focused on technical writing and documentation."}, "department": "Engineering", "role": "Individual Contributor", "title": "Staff Engineer", "isActive": false, "isAdmin": false, "hiredAt": "2018-09-20T00:00:00Z", "lastActiveAt": "2023-12-15T09:00:00Z"}'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 users.waitIndexing();users.wait_indexing()SEARCH.WAITINDEXING usersAutocomplete Search
Use $fuzzy with prefix: true for search-as-you-type functionality. This approach handles
both incomplete words and typos, providing a more forgiving autocomplete experience:
// As user types "ja" in the search boxconst suggestions = await users.query({ filter: { "profile.displayName": { $fuzzy: { value: "ja", prefix: true, }, }, }, limit: 5,});// Matches "Jane Smith", "James Wilson", etc.// As user types "jn" (typo for "ja")const typoSuggestions = await users.query({ filter: { "profile.displayName": { $fuzzy: { value: "jn", prefix: true, transpositionCostOne: true, }, }, }, limit: 5,});// Still matches "Jane Smith", "James Wilson", etc.// As user types "jane smi"const refinedSuggestions = await users.query({ filter: { "profile.displayName": "jane smi", }, limit: 5,});// Smart matching applies fuzzy prefix to last word automatically// Matches "Jane Smith", "Jane Smithson", etc.# As user types "ja" in the search boxsuggestions = users.query( filter={"profile.displayName": {"$fuzzy": {"value": "ja", "prefix": True}}}, limit=5,)# Matches "Jane Smith", "James Wilson", etc.# As user types "jn" (typo for "ja")typo_suggestions = users.query( filter={ "profile.displayName": { "$fuzzy": {"value": "jn", "prefix": True, "transpositionCostOne": True} } }, limit=5,)# Still matches "Jane Smith", "James Wilson", etc.# As user types "jane smi"refined_suggestions = users.query( filter={"profile.displayName": "jane smi"}, limit=5,)# Smart matching applies fuzzy prefix to last word automatically# Matches "Jane Smith", "Jane Smithson", etc.# As user types "ja"SEARCH.QUERY users '{"profile.displayName": {"$fuzzy": {"value": "ja", "prefix": true}}}' LIMIT 5# As user types "jn" (typo)SEARCH.QUERY users '{"profile.displayName": {"$fuzzy": {"value": "jn", "prefix": true, "transpositionCostOne": true}}}' LIMIT 5# As user types "jane smi" (smart matching handles fuzzy prefix on last word)SEARCH.QUERY users '{"profile.displayName": "jane smi"}' LIMIT 5Fuzzy Name Search
Handle typos and misspellings in name searches:
// User types "Micheal" (common misspelling of "Michael")const results = await users.query({ filter: { "profile.firstName": { $fuzzy: "Micheal", }, },});// Matches "Michael Johnson"// Search with more tolerance for longer namesconst fuzzyResults = await users.query({ filter: { "profile.lastName": { $fuzzy: { value: "Willaims", // Typo in "Williams" distance: 2, }, }, },});// Search across first and last name with fuzzy matchingconst combinedFuzzy = await users.query({ filter: { $should: [ { "profile.firstName": { $fuzzy: "Srah" } }, // Typo { "profile.lastName": { $fuzzy: "Srah" } }, ], },});# User types "Micheal" (common misspelling of "Michael")results = users.query( filter={"profile.firstName": {"$fuzzy": "Micheal"}},)# Matches "Michael Johnson"# Search with more tolerance for longer namesfuzzy_results = users.query( filter={"profile.lastName": {"$fuzzy": {"value": "Willaims", "distance": 2}}}, # Typo in "Williams")# Search across first and last name with fuzzy matchingcombined_fuzzy = users.query( filter={ "$should": [ {"profile.firstName": {"$fuzzy": "Srah"}}, # Typo {"profile.lastName": {"$fuzzy": "Srah"}}, ] },)# Fuzzy first name searchSEARCH.QUERY users '{"profile.firstName": {"$fuzzy": "Micheal"}}'# Fuzzy with higher distance toleranceSEARCH.QUERY users '{"profile.lastName": {"$fuzzy": {"value": "willaims", "distance": 2}}}'# Search both first and last nameSEARCH.QUERY users '{"$should": [{"profile.firstName": {"$fuzzy": "srah"}}, {"profile.lastName": {"$fuzzy": "srah"}}]}'Exact Username/Email Lookup
Find users by exact username or email:
// Exact username lookupconst user = await users.query({ filter: { username: "jsmith", },});// Exact email lookupconst userByEmail = await users.query({ filter: { "profile.email": "jane.smith@company.com", },});// Find users with email at specific domainconst companyUsers = await users.query({ filter: { "profile.email": { $regex: "jane.*@company\\.com", }, },});# Exact username lookupuser = users.query( filter={"username": "jsmith"},)# Exact email lookupuser_by_email = users.query( filter={"profile.email": "jane.smith@company.com"},)# Find users with email at specific domaincompany_users = users.query( filter={"profile.email": {"$regex": "jane.*@company\\.com"}},)# Exact username lookupSEARCH.QUERY users '{"username": "jsmith"}'# Exact email lookupSEARCH.QUERY users '{"profile.email": "jane.smith@company.com"}'# Users with specific email domainSEARCH.QUERY users '{"profile.email": {"$regex": "jane.*@company\\.com"}}'Department and Role Filtering
Filter users by department, role, or both:
// All users in Engineeringconst engineers = await users.query({ filter: { department: "Engineering", isActive: true, },});// All managers across departmentsconst managers = await users.query({ filter: { role: "Manager", isActive: true, },});// Engineers who are managersconst engineeringManagers = await users.query({ filter: { $must: { department: "Engineering", role: "Manager", isActive: true, }, },});// Users in Engineering or Designconst productTeam = await users.query({ filter: { department: { $in: ["Engineering", "Design", "Product"], }, isActive: true, },});# All users in Engineeringengineers = users.query( filter={"department": "Engineering", "isActive": True},)# All managers across departmentsmanagers = users.query( filter={"role": "Manager", "isActive": True},)# Engineers who are managersengineering_managers = users.query( filter={ "$must": { "department": "Engineering", "role": "Manager", "isActive": True, } },)# Users in Engineering or Designproduct_team = users.query( filter={ "department": {"$in": ["Engineering", "Design", "Product"]}, "isActive": True, },)# All users in EngineeringSEARCH.QUERY users '{"department": "Engineering", "isActive": true}'# All managersSEARCH.QUERY users '{"role": "Manager", "isActive": true}'# Engineering managersSEARCH.QUERY users '{"$must": {"department": "Engineering", "role": "Manager", "isActive": true}}'# Users in multiple departmentsSEARCH.QUERY users '{"department": {"$in": ["Engineering", "Design", "Product"]}, "isActive": true}'Search by Skills in Bio
Find users with specific skills or expertise:
// Find users who mention "distributed" in their bioconst distributedExperts = await users.query({ filter: { "profile.bio": "distributed", isActive: true, },});// Find users with multiple skillsconst backendEngineers = await users.query({ filter: { $must: { "profile.bio": "backend", department: "Engineering", }, },});// Search for phrase in bioconst uxResearchers = await users.query({ filter: { "profile.bio": { $phrase: "user research", }, },});# Find users who mention "distributed" in their biodistributed_experts = users.query( filter={"profile.bio": "distributed", "isActive": True},)# Find users with multiple skillsbackend_engineers = users.query( filter={"$must": {"profile.bio": "backend", "department": "Engineering"}},)# Search for phrase in bioux_researchers = users.query( filter={"profile.bio": {"$phrase": "user research"}},)# Find users mentioning "distributed" in bioSEARCH.QUERY users '{"profile.bio": "distributed", "isActive": true}'# Backend engineersSEARCH.QUERY users '{"$must": {"profile.bio": "backend", "department": "Engineering"}}'# Phrase search in bioSEARCH.QUERY users '{"profile.bio": {"$phrase": "user research"}}'Admin User Search
Find administrators or users with specific permissions:
// All admin usersconst admins = await users.query({ filter: { isAdmin: true, isActive: true, },});// Active non-admin users in Engineeringconst regularEngineers = await users.query({ filter: { $must: { department: "Engineering", isActive: true, }, $mustNot: { isAdmin: true, }, },});# All admin usersadmins = users.query( filter={"isAdmin": True, "isActive": True},)# Active non-admin users in Engineeringregular_engineers = users.query( filter={ "$must": {"department": "Engineering", "isActive": True}, "$mustNot": {"isAdmin": True}, },)# All admin usersSEARCH.QUERY users '{"isAdmin": true, "isActive": true}'# Active non-admin engineersSEARCH.QUERY users '{"$must": {"department": "Engineering", "isActive": true}, "$mustNot": {"isAdmin": true}}'Key Takeaways
- Use
NOTOKENIZEfor usernames, emails, and exact-match identifiers - Use
NOSTEMfor proper nouns like names to prevent incorrect stemming - Use
$fuzzywithprefix: truefor search-as-you-type autocomplete with typo tolerance - Use
$fuzzyto handle typos in name searches - Combine nested field paths (e.g.,
profile.firstName) for structured data