# 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:

<Tabs>

<Tab title="TypeScript">
```ts
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(),
  }),
});
```
</Tab>

<Tab title="Python">
```python
from upstash_redis import Redis

redis = 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},
    },
)
```
</Tab>

<Tab title="Redis CLI">
```bash
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 FAST
```
</Tab>

</Tabs>

### Sample Data

<Tabs>

<Tab title="TypeScript">
```ts
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",
});
```
</Tab>

<Tab title="Python">
```python
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",
})
```
</Tab>

<Tab title="Redis CLI">
```bash
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"}'
```
</Tab>

</Tabs>

### 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:

<Tabs>

<Tab title="TypeScript">
```ts
await users.waitIndexing();
```
</Tab>

<Tab title="Python">
```python
users.wait_indexing()
```
</Tab>

<Tab title="Redis CLI">
```bash
SEARCH.WAITINDEXING users
```
</Tab>

</Tabs>

### Autocomplete 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:

<Tabs>

<Tab title="TypeScript">
```ts
// As user types "ja" in the search box
const 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.
```
</Tab>

<Tab title="Python">
```python
# As user types "ja" in the search box
suggestions = 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.
```
</Tab>

<Tab title="Redis CLI">
```bash
# 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 5
```
</Tab>

</Tabs>

### Fuzzy Name Search

Handle typos and misspellings in name searches:

<Tabs>

<Tab title="TypeScript">
```ts
// 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 names
const fuzzyResults = await users.query({
  filter: {
    "profile.lastName": {
      $fuzzy: {
        value: "Willaims",  // Typo in "Williams"
        distance: 2,
      },
    },
  },
});

// Search across first and last name with fuzzy matching
const combinedFuzzy = await users.query({
  filter: {
    $should: [
      { "profile.firstName": { $fuzzy: "Srah" } },  // Typo
      { "profile.lastName": { $fuzzy: "Srah" } },
    ],
  },
});
```
</Tab>

<Tab title="Python">
```python
# User types "Micheal" (common misspelling of "Michael")
results = users.query(
    filter={"profile.firstName": {"$fuzzy": "Micheal"}},
)
# Matches "Michael Johnson"

# Search with more tolerance for longer names
fuzzy_results = users.query(
    filter={"profile.lastName": {"$fuzzy": {"value": "Willaims", "distance": 2}}},  # Typo in "Williams"
)

# Search across first and last name with fuzzy matching
combined_fuzzy = users.query(
    filter={
        "$should": [
            {"profile.firstName": {"$fuzzy": "Srah"}},  # Typo
            {"profile.lastName": {"$fuzzy": "Srah"}},
        ]
    },
)
```
</Tab>

<Tab title="Redis CLI">
```bash
# Fuzzy first name search
SEARCH.QUERY users '{"profile.firstName": {"$fuzzy": "Micheal"}}'

# Fuzzy with higher distance tolerance
SEARCH.QUERY users '{"profile.lastName": {"$fuzzy": {"value": "willaims", "distance": 2}}}'

# Search both first and last name
SEARCH.QUERY users '{"$should": [{"profile.firstName": {"$fuzzy": "srah"}}, {"profile.lastName": {"$fuzzy": "srah"}}]}'
```
</Tab>

</Tabs>

### Exact Username/Email Lookup

Find users by exact username or email:

<Tabs>

<Tab title="TypeScript">
```ts
// Exact username lookup
const user = await users.query({
  filter: {
    username: "jsmith",
  },
});

// Exact email lookup
const userByEmail = await users.query({
  filter: {
    "profile.email": "jane.smith@company.com",
  },
});

// Find users with email at specific domain
const companyUsers = await users.query({
  filter: {
    "profile.email": {
      $regex: "jane.*@company\\.com",
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
# Exact username lookup
user = users.query(
    filter={"username": "jsmith"},
)

# Exact email lookup
user_by_email = users.query(
    filter={"profile.email": "jane.smith@company.com"},
)

# Find users with email at specific domain
company_users = users.query(
    filter={"profile.email": {"$regex": "jane.*@company\\.com"}},
)
```
</Tab>

<Tab title="Redis CLI">
```bash
# Exact username lookup
SEARCH.QUERY users '{"username": "jsmith"}'

# Exact email lookup
SEARCH.QUERY users '{"profile.email": "jane.smith@company.com"}'

# Users with specific email domain
SEARCH.QUERY users '{"profile.email": {"$regex": "jane.*@company\\.com"}}'
```
</Tab>

</Tabs>

### Department and Role Filtering

Filter users by department, role, or both:

<Tabs>

<Tab title="TypeScript">
```ts
// All users in Engineering
const engineers = await users.query({
  filter: {
    department: "Engineering",
    isActive: true,
  },
});

// All managers across departments
const managers = await users.query({
  filter: {
    role: "Manager",
    isActive: true,
  },
});

// Engineers who are managers
const engineeringManagers = await users.query({
  filter: {
    $must: {
      department: "Engineering",
      role: "Manager",
      isActive: true,
    },
  },
});

// Users in Engineering or Design
const productTeam = await users.query({
  filter: {
    department: {
      $in: ["Engineering", "Design", "Product"],
    },
    isActive: true,
  },
});
```
</Tab>

<Tab title="Python">
```python
# All users in Engineering
engineers = users.query(
    filter={"department": "Engineering", "isActive": True},
)

# All managers across departments
managers = users.query(
    filter={"role": "Manager", "isActive": True},
)

# Engineers who are managers
engineering_managers = users.query(
    filter={
        "$must": {
            "department": "Engineering",
            "role": "Manager",
            "isActive": True,
        }
    },
)

# Users in Engineering or Design
product_team = users.query(
    filter={
        "department": {"$in": ["Engineering", "Design", "Product"]},
        "isActive": True,
    },
)
```
</Tab>

<Tab title="Redis CLI">
```bash
# All users in Engineering
SEARCH.QUERY users '{"department": "Engineering", "isActive": true}'

# All managers
SEARCH.QUERY users '{"role": "Manager", "isActive": true}'

# Engineering managers
SEARCH.QUERY users '{"$must": {"department": "Engineering", "role": "Manager", "isActive": true}}'

# Users in multiple departments
SEARCH.QUERY users '{"department": {"$in": ["Engineering", "Design", "Product"]}, "isActive": true}'
```
</Tab>

</Tabs>

### Search by Skills in Bio

Find users with specific skills or expertise:

<Tabs>

<Tab title="TypeScript">
```ts
// Find users who mention "distributed" in their bio
const distributedExperts = await users.query({
  filter: {
    "profile.bio": "distributed",
    isActive: true,
  },
});

// Find users with multiple skills
const backendEngineers = await users.query({
  filter: {
    $must: {
      "profile.bio": "backend",
      department: "Engineering",
    },
  },
});

// Search for phrase in bio
const uxResearchers = await users.query({
  filter: {
    "profile.bio": {
      $phrase: "user research",
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
# Find users who mention "distributed" in their bio
distributed_experts = users.query(
    filter={"profile.bio": "distributed", "isActive": True},
)

# Find users with multiple skills
backend_engineers = users.query(
    filter={"$must": {"profile.bio": "backend", "department": "Engineering"}},
)

# Search for phrase in bio
ux_researchers = users.query(
    filter={"profile.bio": {"$phrase": "user research"}},
)
```
</Tab>

<Tab title="Redis CLI">
```bash
# Find users mentioning "distributed" in bio
SEARCH.QUERY users '{"profile.bio": "distributed", "isActive": true}'

# Backend engineers
SEARCH.QUERY users '{"$must": {"profile.bio": "backend", "department": "Engineering"}}'

# Phrase search in bio
SEARCH.QUERY users '{"profile.bio": {"$phrase": "user research"}}'
```
</Tab>

</Tabs>

### Admin User Search

Find administrators or users with specific permissions:

<Tabs>

<Tab title="TypeScript">
```ts
// All admin users
const admins = await users.query({
  filter: {
    isAdmin: true,
    isActive: true,
  },
});

// Active non-admin users in Engineering
const regularEngineers = await users.query({
  filter: {
    $must: {
      department: "Engineering",
      isActive: true,
    },
    $mustNot: {
      isAdmin: true,
    },
  },
});
```
</Tab>

<Tab title="Python">
```python
# All admin users
admins = users.query(
    filter={"isAdmin": True, "isActive": True},
)

# Active non-admin users in Engineering
regular_engineers = users.query(
    filter={
        "$must": {"department": "Engineering", "isActive": True},
        "$mustNot": {"isAdmin": True},
    },
)
```
</Tab>

<Tab title="Redis CLI">
```bash
# All admin users
SEARCH.QUERY users '{"isAdmin": true, "isActive": true}'

# Active non-admin engineers
SEARCH.QUERY users '{"$must": {"department": "Engineering", "isActive": true}, "$mustNot": {"isAdmin": true}}'
```
</Tab>

</Tabs>

### Key Takeaways

- Use `NOTOKENIZE` for usernames, emails, and exact-match identifiers
- Use `NOSTEM` for proper nouns like names to prevent incorrect stemming
- Use `$fuzzy` with `prefix: true` for search-as-you-type autocomplete with typo tolerance
- Use `$fuzzy` to handle typos in name searches
- Combine nested field paths (e.g., `profile.firstName`) for structured data
