title: Redis Data Integration release notes 1.6.2 (March 2025)

title: Overview

text:
> This maintenance release replaces the 1.6.1 release.

RDI’s mission is to help Redis customers sync Redis Enterprise with live data from their slow disk-based databases to:

- Meet the required speed and scale of read queries and provide an excellent and predictable user experience.
- Save resources and time when building pipelines and coding data transformations.
- Reduce the total cost of ownership by saving money on expensive database read replicas.

RDI keeps the Redis cache up to date with changes in the primary database, using a [_Change Data Capture (CDC)_](https://en.wikipedia.org/wiki/Change_data_capture) mechanism.
It also lets you _transform_ the data from relational tables into convenient and fast data structures that match your app's requirements. You specify the transformations using a configuration system, so no coding is required.

title: Headlines

text:
- Fix: With an RDI namespace that contains dashes, `rdi-metrics-exporter` crashes immediately upon startup
- Fix reported security vulnerabilities
- Fix: Connection to PostgreSQL from Debezium fails with "could not read SSL key file" when using mTLS

title: Fixes & Improvements

text:
- **Resolved startup crash in `rdi-metrics-exporter`**
  Fixed an issue where the exporter would crash on startup if the RDI namespace included dashes (e.g., `my-namespace`) that are not allowed in prometheus labels.

- **Security Vulnerabilities Patched**
  Addressed the following reported CVEs:

  - CVE-2019-14250
  - CVE-2019-17543
  - CVE-2023-32665
  - CVE-2024-52533
  - CVE-2020-17049
  - CVE-2024-47874

- **Improved mTLS compatibility in `collector-initializer`**
  DER-formatted keys are now skipped during initialization, resolving a PostgreSQL connection error with Debezium (`could not read SSL key file`) when using mTLS.

title: Limitations


title: Redis Data Integration release notes 1.6.3 (March 2025)

summary: Installation on Kubernetes with a Helm chart. Improvements for installation on VMs.

page_type: content

title: Overview

role: overview

text:
> This maintenance release replaces the 1.6.2 release.

RDI’s mission is to help Redis customers sync Redis Enterprise with live data from their slow disk-based databases to:

- Meet the required speed and scale of read queries and provide an excellent and predictable user experience.
- Save resources and time when building pipelines and coding data transformations.
- Reduce the total cost of ownership by saving money on expensive database read replicas.

RDI keeps the Redis cache up to date with changes in the primary database, using a [_Change Data Capture (CDC)_](https://en.wikipedia.org/wiki/Change_data_capture) mechanism.
It also lets you _transform_ the data from relational tables into convenient and fast data structures that match your app's requirements. You specify the transformations using a configuration system, so no coding is required.

title: Headlines

role: content

text: - Fix: RDI 1.6.2 VM Installer not working on RHEL-8 OS.

title: Limitations

role: limits

text: RDI can write data to a Redis Active-Active database. However, it doesn't support writing data to two or more Active-Active replicas. Writing data from RDI to several Active-Active replicas could easily harm data integrity as RDI is not synchronous with the source database commits.


title: Redis Data Integration release notes 1.6.4 (April 2025)

title: Overview

text:
> This maintenance release replaces the 1.6.3 release.

RDI’s mission is to help Redis customers sync Redis Enterprise with live data from their slow disk-based databases to:

- Meet the required speed and scale of read queries and provide an excellent and predictable user experience.
- Save resources and time when building pipelines and coding data transformations.
- Reduce the total cost of ownership by saving money on expensive database read replicas.

RDI keeps the Redis cache up to date with changes in the primary database, using a [_Change Data Capture (CDC)_](https://en.wikipedia.org/wiki/Change_data_capture) mechanism.
It also lets you _transform_ the data from relational tables into convenient and fast data structures that match your app's requirements. You specify the transformations using a configuration system, so no coding is required.

title: Headlines

text: - Feature: Add support for Debezium 3.0.8

title: Limitations


title: Redis Data Integration release notes 1.6.5 (April 2025)

title: Overview

text:
> This maintenance release replaces the 1.6.4 release.

RDI’s mission is to help Redis customers sync Redis Enterprise with live data from their slow disk-based databases to:

- Meet the required speed and scale of read queries and provide an excellent and predictable user experience.
- Save resources and time when building pipelines and coding data transformations.
- Reduce the total cost of ownership by saving money on expensive database read replicas.

RDI keeps the Redis cache up to date with changes in the primary database, using a [_Change Data Capture (CDC)_](https://en.wikipedia.org/wiki/Change_data_capture) mechanism.
It also lets you _transform_ the data from relational tables into convenient and fast data structures that match your app's requirements. You specify the transformations using a configuration system, so no coding is required.

title: Headlines

text:
- Vulnerabilities: Resolve vulnerabilities in the `collector-api` component.
  - CVE-2025-27363
  - CVE-2021-23336
  - CVE-2025-27113
  - CVE-2024-52533
  - CVE-2025-1632
  - CVE-2024-47535

title: Limitations


title: Redis Data Integration release notes 1.6.6 (April 2025)

title: Overview

text:
> This maintenance release replaces the 1.6.5 release.

RDI’s mission is to help Redis customers sync Redis Enterprise with live data from their slow disk-based databases to:

- Meet the required speed and scale of read queries and provide an excellent and predictable user experience.
- Save resources and time when building pipelines and coding data transformations.
- Reduce the total cost of ownership by saving money on expensive database read replicas.

RDI keeps the Redis cache up to date with changes in the primary database, using a [_Change Data Capture (CDC)_](https://en.wikipedia.org/wiki/Change_data_capture) mechanism.
It also lets you _transform_ the data from relational tables into convenient and fast data structures that match your app's requirements. You specify the transformations using a configuration system, so no coding is required.

title: Headlines

text:
- Fixed a Redis Insight issue when accessing the RDI API `/strategies` endpoint.
- Added a new script to the RDI Helm chart for managing the source and target database secrets. See
  [Deploy a pipeline]()
  for more information.
- Fixed an issue when connecting from Redis Insight to the RDI API `/login` endpoint with a non-default user.
- Updated the RDI API `/schemas` endpoint to return a list of databases for MySQL and MariaDB, instead of an empty list of schemas.

title: Limitations


title: Redis Data Integration release notes 1.6.7 (May 2025)

title: Overview

text:
> This maintenance release replaces the 1.6.6 release.

RDI’s mission is to help Redis customers sync Redis Enterprise with live data from their slow disk-based databases to:

- Meet the required speed and scale of read queries and provide an excellent and predictable user experience.
- Save resources and time when building pipelines and coding data transformations.
- Reduce the total cost of ownership by saving money on expensive database read replicas.

RDI keeps the Redis cache up to date with changes in the primary database, using a [_Change Data Capture (CDC)_](https://en.wikipedia.org/wiki/Change_data_capture) mechanism.
It also lets you _transform_ the data from relational tables into convenient and fast data structures that match your app's requirements. You specify the transformations using a configuration system, so no coding is required.

title: Headlines

text:
- Update the RDI API `/tables` and `/metadata` endpoints to filter the results by schema for MySQL and
  MariaDB. Schema and Database are the same for these databases.

title: Limitations


title: Redis Data Integration release notes 1.8.0 (May 2025)

summary: Enhanced RDI operator for better pipeline orchestration, resiliency, observability and flexibility; External collector support; Labels and annotations to RDI data plane pods; Custom Debezium image; Calculated TTL for target database.

title: Overview

text:
This minor release replaces the 1.6.7 release.

RDI’s mission is to help Redis customers sync Redis Enterprise with live data from their slow disk-based databases to:

- Meet the required speed and scale of read queries and provide an excellent and predictable user experience.
- Save resources and time when building pipelines and coding data transformations.
- Reduce the total cost of ownership by saving money on expensive database read replicas.

RDI keeps the Redis cache up to date with changes in the primary database, using a [_Change Data Capture (CDC)_](https://en.wikipedia.org/wiki/Change_data_capture) mechanism.
It also lets you _transform_ the data from relational tables into convenient and fast data structures that match your app's requirements. You specify the transformations using a configuration system, so no coding is required.

title: Headlines

text:
- Enhanced RDI operator for better pipeline orchestration, resiliency, observability, and flexibility. It
  will also enable many new features in the near future.
- You can now use an external collector that is not managed by RDI but writes into RDI streams
  (Debezium compatible).
- You can now add labels and annotations to RDI data plane pods, for example to control service
  mesh features.
- RDI now uses a custom image of Debezium (based on `3.0.8.Final`) to address known vulnerabilities.
- Added support for calculated TTL for target database keys via `expire` expressions.

title: Detailed changes

title: Helm chart changes

text:
- All collector and processor values that were previously under `collector`, `collectorSourceMetricsExporter`, and `processor` have been moved to `operator.dataPlane.collector` and `operator.dataPlane.processor`.
- `global.collectorApiEnabled` has been moved to `operator.dataPlane.collectorApi.enabled`, and is now a boolean value (`true` or `false`), not `"0"` or `"1"`.
- `api.authEnabled` is also now a boolean value, not `"0"` or `"1"`.
- The following values have been deprecated: `rdiMetricsExporter.service.protocol`, `rdiMetricsExporter.service.port`, `rdiMetricsExporter.serviceMonitor.path`, `api.service.name`
- You can now add custom labels and annotations to all RDI components.
- You can now disable the creation of the RDI system secrets.

title: Operator Improvements

text:
The RDI operator has been significantly enhanced in the following areas:

- **Resilience**: The operator now always maintains the desired pipeline state. Manual changes or random disruptions are reverted automatically.
- **Automatic recovery**: When a configuration issue is resolved, the entire pipeline starts automatically, eliminating the need for manual redeployment.
- **Consistency**: A pipeline that has been stopped with `stop` will remain stopped after `deploy` or `reset`, until explicitly started again.
- **Enhanced configuration**: You can now configure data plane components in ways that were previously not supported, such as adding labels and annotations.
- **External collector support**: No collector resources are created for sources of type `external`.
- **Enhanced troubleshooting**: You can now gain extra insight into the pipeline state by examining the `Pipeline` and `PipelineRelease` custom K8s resources.

title: Other Features, Improvements and Enhancements

text:
- Added `expire` expression for target output in transformation jobs.
- Addressed security vulnerabilities: TLS certificate hostname verification is now ON by default.
- Improved Helm default values while preserving `values.yaml` formatting.
- Enhanced Helm values and templates for better configuration.
- Added a script to create or update secrets when using Helm (`rdi-secret.sh` in the Helm zip file).
- Improved validation schema and ensured backward compatibility.
- Fixed compatibility issues with newer versions of `requests` and `urllib3`.
- Improved error messages for JSON schema validation.
- Improved PostgreSQL documentation for mTLS.
- Added timestamps to the `status` command.
- Fixed issues with `primary_key` and `unique_constraint` attributes in Oracle metadata.
- Added `capture.mode` to MongoDB scaffolding.
- Improved Helm TLS setup for RDI database connections.
- Enhanced error handling and validation for transformation jobs.
- Improved documentation for supported platforms and configurations.

title: Fixes

text:
- Fixed HTTP 500 error when querying columns with tables parameter.
- Improved Helm TLS setup for RDI database connections.
- Fixed keystore overwrite when using mTLS on both source and RDI DBs in the collector.

title: Limitations

title: Known Issues

title: High Availability upgrade issue

text:
When upgrading from RDI < 1.8.0 to RDI >= 1.8.0 in an HA setup, both RDI instances may incorrectly consider themselves active after the upgrade. This occurs because the upgrade process doesn't update the `rdi:ha:lock` value from the legacy `cluster-1` identifier, causing both clusters to assume they are the active cluster.

**Symptoms:**

- The upgraded passive node will start collector and processor components
- Collector may enter a crash loop as it fails to connect to the source
- Both clusters will restart in a loop

**Workaround:**

After upgrading, manually set a unique cluster ID for one of the installations by editing the configmap:

[code example]

Then add the following line to distinguish between the clusters:

[code example]

code: kubectl edit cm -n rdi rdi-sys-config

section_id: high-availability-upgrade-issue

code: RDI_CLUSTER_ID: cluster-2


title: Troubleshooting

summary: Solve and report simple problems with RDI

title: Overview

text:
The following sections explain how you can get extra information from
Redis Data Integration (RDI) to help you solve problems that you may encounter. Redis support may
also ask you to provide this information to help you resolve issues.

title: Debug information during installation {#install-debug}

text:
If the installer fails with an error, then try installing again with the
log level set to `DEBUG`:

[code example]

This gives you more detail about the installation steps and can often
help you to pinpoint the source of the error.

title: RDI logs

text:
By default, RDI records the following logs in the host VM file system at
`/opt/rdi/logs` (or whichever path you specified during installation);

| Filename | Phase |
| :-- | :-- |
| `rdi_collector-collector-initializer.log` | Initializing the collector. |
| `rdi_collector-debezium-ssl-init.log` | Establishing the connector SSL connections to the source and RDI database (if you are using SSL). |
| `rdi_collector-collector-source.log` | Collector [change data capture (CDC)]() operations. |
| `rdi_rdi-rdi-operator.log` | Main [RDI control plane]() component. |
| `rdi_processor-processor.log` | RDI stream processing. |

Logs are recorded at the minimum `INFO` level in a simple format that
log analysis tools can use.

Often during the initial sync phase, the collector source log will contain a message
saying RDI is out of
memory. This is not an error but an informative message to say that RDI
is applying *backpressure* to the collector. See
[Backpressure mechanism]()
in the Architecture guide for more information.

title: Dump support package

text:
If you need to send a comprehensive set of forensics data to Redis support,
run the
[`redis-di dump-support-package`]()
command from the CLI.

This command gathers the following data:

- All the internal RDI components and their status
- All internal RDI configuration
- List of secret names used by RDI components (but not the secrets themselves)
- RDI logs
- RDI component versions
- Output from the [`redis-di status`]() command
- Text of the `config.yaml` file
- Text of the Job configuration files
- [optional] RDI DLQ streams content
- Rejected records along with the reason for their rejection (should not exist in production)

code:
./install.sh -l DEBUG   # Installer script
redis-di install -l DEBUG    # Install command


title: When to use RDI

summary: Understand when (and when not) to use RDI.

title: Overview

text:
RDI is designed to support apps that must use a disk-based database as the system of record
but must also be fast and scalable. This is a common requirement for mobile and web
apps with a rapidly-growing number of users; the performance of the main database is fine at first
but it will soon struggle to handle the increasing demand without a cache.

title: Guidelines for using RDI

text:
Use the information in the sections below to determine whether RDI is a good fit for your architecture.

[code example]

title: Decision tree for using RDI

text: Use the decision tree below to determine whether RDI is a good fit for your architecture:


title: Configure client apps

summary: Configure client apps to use the Redis MCP server.

title: Overview

text:
When you have [installed]()
the Redis MCP server, you must also configure your client app to use it.
The sections below describe the ways you can do this.

title: Smithery

text:
[Smithery](https://smithery.ai/) provides a searchable repository of scripts
that add configurations for many MCP services to client apps.
The easiest way to configure your client is to use the
[Smithery tool for Redis MCP](https://smithery.ai/server/@redis/mcp-redis).

When you select your client from the **Install** bar on the Redis MCP page,
you will see a command line that you can copy and paste into a terminal.
Running this command will configure your client app to use Redis MCP. (Note
that you must have [Node.js](https://nodejs.org/en) installed to run
the Smithery scripts.) For example, the command line for
[Claude Desktop](https://claude.ai/download) is

[code example]

The script will prompt you for the information required to connect to
your Redis database.

title: Manual configuration

text:
You can also add the configuration for Redis MCP to your client app
manually. The exact method varies from client to client but the
basic approach is similar in each case. The sections below
give the general configuration details for some common MCP client tools:

-   [Augment Code](#augment-code)
-   [Claude Desktop](#claude-desktop)
-   [OpenAI Agents](#openai-agents)
-   [VS Code with GitHub Copilot](#vs-code-with-github-copilot)

title: Augment Code

text:
Redis supports Augment Code's
[Easy MCP](https://docs.augmentcode.com/setup-augment/mcp#easy-mcp%3A-one-click-integrations)
feature in [VSCode](https://docs.augmentcode.com/setup-augment/mcp#getting-started-with-easy-mcp)
and the [JetBrains IDEs](https://docs.augmentcode.com/jetbrains/setup-augment/mcp#getting-started-with-easy-mcp)
to install the server in seconds:

1.  Open the Augment settings panel and
    navigate to the **MCP** pane.
1.  Click the "+" button next to Redis in the
    **Easy MCP installation** list and enter the connection details for your Redis database.
1.  Click **Install** to start using Redis MCP.

If you need to supply environment variables or command line parameters:

1.  Click the **Add MCP** button underneath the list of Easy MCP integrations.
1.  Enter `Redis` in the name field and paste the appropriate command line in the
    command field (see
    [Configuration]() for
    more information about the available command line options).
1.  Click the **+ Variable** button to add any environment variables that you need.
1.  Click **Add** to add the server.

title: Claude Desktop

text:
First, locate the configuration file by selecting **Settings** from the menu, then selecting the **Developer** tab, and then clicking the **Edit Config** button. Open this JSON file and add your settings as shown below to run Redis MCP with [`uvx`](https://docs.astral.sh/uv/guides/tools/#running-tools):

[code example]

You can also optionally set the environment for the command shell here in the
`env` section:

[code example]

If you are using
[Docker]()
to deploy the server, change the `command` and `args` sections of the
configuration as shown below:

[code example]

For more information about using MCP with Claude Desktop, see
[Connect to Local MCP Servers](https://modelcontextprotocol.io/quickstart/user).

title: OpenAI Agents

text:
The
[`redis_assistant.py`](https://github.com/redis/mcp-redis/blob/main/examples/redis_assistant.py)
file in the [`mcp-redis`](https://github.com/redis/mcp-redis) repository contains an
example of how to configure OpenAI Agents to use Redis MCP.

To use this example, install the `openai-agents` library, ensure
you have exported the OpenAI token in the `OPENAI_API_KEY` environment variable
and run the `redis_assistant.py` script:

[code example]

See the
[OpenAI Agents SDK documentation](https://openai.github.io/openai-agents-python/mcp/)
for more information about using MCP servers.

title: VS Code with GitHub Copilot

text:
To use Redis MCP with VS Code, first add the following to your `settings.json`
file to enable the
[agent mode tools](https://code.visualstudio.com/docs/copilot/chat/chat-agent-mode):

[code example]

Then, add the following lines to `settings.json` to run Redis MCP
with [`uvx`](https://docs.astral.sh/uv/guides/tools/#running-tools):

[code example]

You can also add
[environment variables]()
in the `env` section of the configuration:

[code example]

See
[Use MCP servers in VS Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers)
for more information.

title: Redis Cloud MCP

text:
If you are using
[Redis Cloud MCP](),
the configuration is similar to [basic MCP](#manual-configuration), but with a
few differences. Set the client to run the server using the `node` command, as shown
in the example for Claude Desktop below:

[code example]

Here, the environment includes the Redis Cloud API key and API secret key
(see [Redis Cloud REST API]() for more
information).

If you are deploying Redis Cloud MCP with Docker, use a configuration like
the following to launch the server with the `docker` command:

[code example]

code: npx -y @smithery/cli@latest install @redis/mcp-redis --client claude

section_id: smithery

code:
{
    "mcpServers": {
        .
        .
        "redis-mcp-server": {
            "type": "stdio",
            "command": "uvx",
            "args": [
                "--from", "redis-mcp-server@latest",
                "redis-mcp-server",
                "--url", "redis://localhost:6379/0"
            ]
        }
    }
        .
        .
  }

section_id: claude-desktop

code:
"redis": {
    "type": "stdio",
    "command": "uvx",
    "args": [
        "--from", "redis-mcp-server@latest",
        "redis-mcp-server",
        "--url", "redis://localhost:6379/0"
    ],
    "env": {
        "REDIS_HOST": "<your_redis_database_hostname>",
        "REDIS_PORT": "<your_redis_database_port>",
        "REDIS_PWD": "<your_redis_database_password>",
        "REDIS_SSL": True|False,
        "REDIS_CA_PATH": "<your_redis_ca_path>",
        "REDIS_CLUSTER_MODE": True|False
    }
}

code:
"redis": {
    "command": "docker",
    "args": ["run",
            "--rm",
            "--name",
            "redis-mcp-server",
            "-i",
            "-e", "REDIS_HOST=<redis_hostname>",
            "-e", "REDIS_PORT=<redis_port>",
            "-e", "REDIS_USERNAME=<redis_username>",
            "-e", "REDIS_PWD=<redis_password>",
            "mcp-redis"]
}

code:
pip install openai-agents
export OPENAI_API_KEY=<your_openai_api_key>
python3 redis_assistant.py

section_id: openai-agents

section_id: vs-code-with-github-copilot

code:
.
  .
"mcp": {
    "servers": {
        "Redis MCP Server": {
        "type": "stdio",
        "command": "uvx",
        "args": [
            "--from", "redis-mcp-server@latest",
            "redis-mcp-server",
            "--url", "redis://localhost:6379/0"
        ]
        },
    }
},
  .
  .

code:
"Redis MCP Server": {
    .
    .
  "env": {
    "REDIS_HOST": "<your_redis_database_hostname>",
    "REDIS_PORT": "<your_redis_database_port>",
    "REDIS_USERNAME": "<your_redis_database_username>",
    "REDIS_PWD": "<your_redis_database_password>",
      .
      .
  }
}

command: node

section_id: redis-cloud-mcp

command: docker


title: Redis MCP

summary: Access a Redis server using any MCP client.

summary: Install and configure the Redis MCP server.

title: Install

title: Configure client apps

title: Content

text:
The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction)
is a standard that lets AI agents access data and perform actions. Using MCP,
your server can publish a set of commands that are usable by any MCP-compatible
client app (such as [Claude Desktop](https://claude.ai/download) or
[VS Code](https://code.visualstudio.com/)). These commands can retrieve
whatever data you wish to provide and you can also let the agent make
changes to the data. For example, you could publish a feed of news items that
an agent can use in its responses, and also let the agent add the user's
comments to those items.

Redis MCP is a general-purpose implementation that lets agents read, write, and
query data in Redis and also has some basic commands to manage the Redis
server. With this enabled, you can use an LLM client as a very high-level
interface to Redis. Add, query, and analyze any Redis data set directly from
an LLM chat using instructions and questions like the following:

- "Store the entire conversation in the 'recent_chats' stream"
- "Cache this item"
- "How many keys does my database have?"
- "What is user:1's email?"

See the other pages in this section to learn how to set up and use Redis MCP.
See also the [GitHub repository](https://github.com/redis/mcp-redis) for
the latest changes.


title: Install

title: Overview

text:
The MCP server runs separately from Redis, so you will need a
Redis server for it to connect to. See [Redis Cloud]()
or [Redis Open Source]() to learn
how to get a test server active within minutes.

When you have a Redis server available, use the instructions below to install and
configure the Redis MCP server.

title: Quick Start with PyPI

text:
The easiest way to use the Redis MCP Server is with
[`uvx`](https://docs.astral.sh/uv/guides/tools/),
which lets you run it from its [PyPI package](https://pypi.org/project/redis-mcp-server/)
(see the `uv`
[installation instructions](https://github.com/astral-sh/uv?tab=readme-ov-file#installation)
for more information.)

[code example]

title: Run from GitHub

text:
Running from [PyPI](#quick-start-with-pypi)
is the recommended way to use the Redis MCP server, but
you can also run it from a GitHub branch or a tagged release with `uvx`:

[code example]

title: Install the server from source

role: setup

text:
You can also run Redis MCP from source, which may be useful if you want to
contribute to the project.

Clone Redis MCP from the
[Github repository](https://github.com/redis/mcp-redis) using the following
command:

[code example]

You will also need the [`uv`](https://github.com/astral-sh/uv) packaging
tool to set up the server. See the `uv`
[installation instructions](https://github.com/astral-sh/uv?tab=readme-ov-file#installation)
for more information.

When you have installed `uv`, go to the `mcp-redis` folder that you cloned and
enter the following commands to initialize the MCP server code:

[code example]

title: Install using Docker

text:
You can use the [`mcp/redis`](https://hub.docker.com/r/mcp/redis)
image to run Redis MCP with [Docker](https://www.docker.com/).
Alternatively, use the following
command to build the Docker image with the `Dockerfile` in the
`mcp/redis` folder:

[code example]

title: Configuration

role: configuration

text:
The default settings for MCP assume a Redis server is running on the
local machine, with the default port and no security arrangements.
You can use environment variables to change these settings
(see [Environment variables](#environment-variables) below for the full list).
For example, for a `bash` shell, use

[code example]

from the command line or the `.bashrc` file to set the username you want
to connect with.

Alternatively, you can use a `.env` file in your project folder to set the
environment variables as key-value pairs, one per line:

[code example]

See the [`.env.example` file](https://github.com/redis/mcp-redis/blob/main/.env.example)
in the repository for the full list of variables and their default values.

You can also set the configuration using command-line arguments, which
may be useful if you only want to change a few settings from the defaults
(see [Command line options](#command-line-options) below for the full list
of options).

[code example]

The command-line options take precedence over the environment variables.

title: Environment variables

text:
The full set of environment variables is shown in the table below. The
`REDIS_ENTRAID_XXX` variables (used for EntraID authentication to
Azure Managed Redis) are described in the
[EntraID authentication](#entraid-authentication) section.

| Name                 | Description                 | Default Value |
|----------------------|-----------------------------|---------------|
| `REDIS_HOST`         | Redis IP or hostname   | `"127.0.0.1"` |
| `REDIS_PORT`         | Redis port         | `6379`        |
| `REDIS_DB`           | Database | 0 |
| `REDIS_USERNAME`     | Database username   | `"default"`   |
| `REDIS_PWD`          | Database password       | ""            |
| `REDIS_SSL`          | Enables or disables SSL/TLS    | `False`       |
| `REDIS_CA_PATH`      | CA certificate for verifying server   | None  |
| `REDIS_SSL_KEYFILE`  | Client's private key file for client authentication       | None          |
| `REDIS_SSL_CERTFILE` | Client's certificate file for client authentication       | None          |
| `REDIS_CERT_REQS`    | Whether the client should verify the server's certificate | `"required"`  |
| `REDIS_CA_CERTS`     | Path to the trusted CA certificates file                  | None          |
| `REDIS_CLUSTER_MODE` | Enable Redis Cluster mode                                 | `False`       |
| `REDIS_ENTRAID_AUTH_FLOW`               | Authentication flow type                                  | None (EntraID disabled)              |
| `REDIS_ENTRAID_CLIENT_ID`               | Service Principal client ID                               | None                                 |
| `REDIS_ENTRAID_CLIENT_SECRET`           | Service Principal client secret                           | None                                 |
| `REDIS_ENTRAID_TENANT_ID`               | Azure tenant ID                                           | None                                 |
| `REDIS_ENTRAID_IDENTITY_TYPE`           | Managed identity type                                     | `"system_assigned"`                  |
| `REDIS_ENTRAID_USER_ASSIGNED_CLIENT_ID` | User-assigned managed identity client ID                  | None                                 |
| `REDIS_ENTRAID_SCOPES`                  | OAuth scopes for Default Azure Credential                | `"https://redis.azure.com/.default"` |
| `REDIS_ENTRAID_RESOURCE`                | Azure Redis resource identifier                          | `"https://redis.azure.com/"`         |

title: Command line options

role: syntax

text:
The full set of command line options is shown in the table below:

| Name                 | Description     | Default Value |
|----------------------|-----------------|---------------|
| `--url`              | Redis URL (`redis://user:pass@host:port/db`)   |  |
| `--host`             | Redis IP or hostname   | `"127.0.0.1"` |
| `--port`             | Redis port         | `6379`        |
| `--db`               | Database | 0 |
| `--username`         | Database username   | `"default"`   |
| `--password`         | Database password       | |
| `--ssl`              | Enables or disables SSL/TLS    | `False`       |
| `--ssl-ca-path`          | CA certificate for verifying server   | None  |
| `--ssl-keyfile`      | Client's private key file for client authentication   |    |
| `--ssl-certfile`     | Client's certificate file for client authentication   |   |
| `--ssl-cert-reqs`        | Whether the client should verify the server's certificate   | `"required"`  |
| `--ssl-ca-certs`         | Path to the trusted CA certificates file   |   |
| `--cluster-mode`     | Enable Redis Cluster mode    | `False`       |

title: EntraID authentication

text:
The Redis MCP Server supports EntraID (Azure Active Directory) authentication
for Azure Managed Redis, enabling OAuth-based authentication with automatic token management. This uses redis-py's [redis-entra-id]() library to offer:

- **Automatic token renewal** - Refreshes tokens in the background with no manual intervention
- **Graceful fallback** - Falls back to standard Redis authentication when EntraID is not configured
- **Multiple auth flows** - Supports Service Principal, Managed Identity, and Default Azure Credential
- **Enterprise capabilities** - Designed for Azure Managed Redis with centralized identity management

Use the `REDIS_ENTRAID_XXX` environment variables to configure EntraID authentication.
Some common configurations are:

- **Local development with Azure CLI**:
  [code example]
- **Production usage with Service Principal**:

  [code example]
- **Azure-hosted application with Managed Identity**:

  [code example]

title: Redis Cloud MCP

text:
A separate MCP server is available for
[Redis Cloud](). This has features
specific to Redis Cloud, including subscription management and
billing details. For example, you can use questions and instructions
like the following:

-   "Create a new Redis database in AWS"
-   "What are my current subscriptions?"
-   "Help me choose the right Redis database for my e-commerce application"

By itself, the Redis Cloud MCP server only provides admin
features and does not let you interact
with Redis databases. You can still use the main MCP server for interaction
with a Cloud database, but you must install it separately.

You will need [Node.js](https://nodejs.org/en) installed to run Redis Cloud MCP.
Clone the GitHub repository using the following command:

[code example]

Go into the `mcp-redis-cloud` folder and install the dependencies:

[code example]

The server is now ready for use.

You can also deploy Redis Cloud MCP using Docker. Build the image
using the `Dockerfile` in the repository folder with the following
command:

[code example]

title: Next steps

text:
When you have installed the server, you will need a MCP client to
connect to it and use its services. See
[Configure client apps]()
for more information.

code:
# Run with Redis URI
uvx --from redis-mcp-server@latest redis-mcp-server --url redis://localhost:6379/0

# Run with Redis URI and SSL
uvx --from redis-mcp-server@latest redis-mcp-server --url "rediss://<USERNAME>:<PASSWORD>@<HOST>:<PORT>?ssl_cert_reqs=required&ssl_ca_certs=<PATH_TO_CERT>"

# Run with individual parameters
uvx --from redis-mcp-server@latest redis-mcp-server --host localhost --port 6379 --password mypassword

# See all options
uvx --from redis-mcp-server@latest redis-mcp-server --help

section_id: quick-start-with-pypi

code:
# Run with Redis URI
uvx --from git+https://github.com/redis/mcp-redis.git redis-mcp-server --url redis://localhost:6379/0

# Run with Redis URI and SSL
uvx --from git+https://github.com/redis/mcp-redis.git redis-mcp-server --url "rediss://<USERNAME>:<PASSWORD>@<HOST>:<PORT>?ssl_cert_reqs=required&ssl_ca_certs=<PATH_TO_CERT>"

# Run with individual parameters
uvx --from git+https://github.com/redis/mcp-redis.git redis-mcp-server --host localhost --port 6379 --password mypassword

# See all options
uvx --from git+https://github.com/redis/mcp-redis.git redis-mcp-server --help

section_id: run-from-github

section_id: install-the-server-from-source

code:
cd mcp-redis

uv venv
source .venv/bin/activate
uv sync

# Run with CLI interface
uv run redis-mcp-server --help

# Or run the main file directly (uses environment variables)
uv run src/main.py

code: docker build -t mcp-redis .

section_id: install-using-docker

code: export REDIS_USERNAME="my_username"

section_id: configuration

code:
REDIS_HOST=your_redis_host
REDIS_PORT=6379
    .
    .

code:
# Basic Redis connection
uvx --from redis-mcp-server@latest redis-mcp-server \
  --host localhost \
  --port 6379 \
  --password mypassword

# Using Redis URI (simpler)
uvx --from redis-mcp-server@latest redis-mcp-server \
  --url redis://user:pass@localhost:6379/0

# SSL connection
uvx --from redis-mcp-server@latest redis-mcp-server \
  --url rediss://user:pass@redis.example.com:6379/0

# See all available options
uvx --from redis-mcp-server@latest redis-mcp-server --help

code:
# Login with Azure CLI
  az login

  # Configure MCP server
  export REDIS_ENTRAID_AUTH_FLOW=default_credential
  export REDIS_URL=redis://your-azure-redis.redis.cache.windows.net:6379

section_id: entraid-authentication

code:
export REDIS_ENTRAID_AUTH_FLOW=service_principal
  export REDIS_ENTRAID_CLIENT_ID=your-app-client-id
  export REDIS_ENTRAID_CLIENT_SECRET=your-app-secret
  export REDIS_ENTRAID_TENANT_ID=your-tenant-id
  export REDIS_URL=redis://your-azure-redis.redis.cache.windows.net:6379

code:
export REDIS_ENTRAID_AUTH_FLOW=managed_identity
  export REDIS_ENTRAID_IDENTITY_TYPE=system_assigned
  export REDIS_URL=redis://your-azure-redis.redis.cache.windows.net:6379

code: npm run build

code: docker build -t mcp/redis-cloud .


title: Python client for Redis

summary: Learn how to build with Redis and Python

title: Overview

text: Connect your Python application to a Redis database using the redis-py client library.

title: Overview

text: redis-py is the recommended Python client for Redis, providing a comprehensive interface for all Redis commands and features. It offers both synchronous and asynchronous APIs, making it suitable for a wide range of Python applications from simple scripts to high-performance web applications.

title: Key Features

text:
- **Complete Redis Support**: Full coverage of all Redis commands and data types
- **Async/Await Support**: Native asyncio support for high-performance applications
- **Connection Pooling**: Efficient connection management for multi-threaded applications
- **Redis Cluster**: Built-in support for Redis Cluster deployments
- **Sentinel Support**: High availability with Redis Sentinel integration
- **Pipeline Support**: Command batching for improved performance
- **Pub/Sub**: Real-time messaging with Redis publish/subscribe
- **Framework Integration**: Seamless integration with Django, Flask, FastAPI, and other Python frameworks

title: Getting Started

text: Refer to the complete [Python guide]() to install, connect, and use redis-py.


title: Redis Insight

summary: Visual client tool for creating, managing, and analyzing Redis databases

title: Overview

text: Redis Insight is a powerful tool for visualizing and optimizing data in Redis, making real-time application development easier and more fun than ever before. Redis Insight lets you do both GUI- and CLI-based interactions in a fully-featured desktop GUI client.

title: Key Features

text:
- **Visual data browser**: Browse, filter and visualize your key-value Redis data structures
- **Real-time profiler**: Analyze every command sent to Redis in real time
- **Advanced CLI**: Intelligent command auto-complete and complex data visualization support
- **Database analysis**: Optimize performance and memory usage of your Redis database
- **Redis Copilot**: AI-powered developer assistant for learning and data exploration

title: Installation

text:
Redis Insight is available for multiple platforms:

- [Install on Desktop]() - Windows, macOS, Linux
- [Install on Docker]() - Container deployment
- [Install on Kubernetes]() - K8s clusters
- [Install on AWS]() - Amazon Web Services

title: Getting Started

text: Refer to the complete [Redis Insight guide]() to install, connect, and use Redis Insight for data visualization and database management.


title: RedisOM for Java

summary: Learn how to build with Redis Stack and Spring

title: Overview

text:
Redis Stack provides a seamless and straightforward way to use different data models and functionality from Redis, including a document store, a time series data database, probabilistic data structures, and a full-text search engine.

Redis Stack is supported by several client libraries, including Node.js, Java, and Python, so that developers can use their preferred language. We'll be using one of the Redis Stack supporting libraries; [Redis OM Spring](https://github.com/redis/redis-om-spring).
Redis OM Spring provides a robust repository and custom object-mapping abstractions built on the powerful Spring Data Redis (SDR) framework.

title: What you’ll need:

text:
* Redis Stack: See []()
* [Redis Insight]()
* Your favorite browser
* Java 11 or greater

title: Spring Boot scaffold with Spring Initializer

text:
We’ll start by creating a skeleton app using the [Spring Initializer](https://start.spring.io), open your browser to https://start.spring.io and let's configure our skeleton application as follows:

* We’ll use a Maven-based build (check Maven checkbox)
* And version **`2.6.4`** of Spring Boot which is the current version supported by Redis OM Spring
* Group: **`com.redis.om`**
* Artifact: **`skeleton`**
* Name: **`skeleton`**
* Description: Skeleton App for Redis OM Spring
* Package Name: **`com.redis.om.skeleton`**
* Packaging: JAR
* Java: **`11`**
* Dependencies: **`web`**, **`devtools`** and **`lombok`**.

The `web` (Spring Web) gives us the ability to build RESTful applications using Spring MVC. With `devtools` we get fast application restarts and reloads. And `lombok` reduces boilerplate code like getters and setters.

![Spring Initializer](./images/001_stack_spring.png "Spring Initializer")

Click `Generate` and download the ZIP file, unzip it and load the Maven project into your IDE of choice.

title: Adding Redis OM Spring

text:
Open the Maven `pom.xml` and between the `<dependencies>` and `<build>` sections we’ll add the snapshots repositories so that we can get to latest SNAPSHOT release of redis-om-spring:

<repositories>
  <repository>
    <id>snapshots-repo</id>
    <url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
  </repository>
</repositories>

And then in the `<dependencies>` section add version `0.3.0` of Redis OM Spring:

<dependency>
  <groupId>com.redis.om</groupId>
  <artifactId>redis-om-spring</artifactId>
  <version>0.3.0-SNAPSHOT</version>
</dependency>

title: Adding Swagger

text:
We'll use the Swagger UI to test our web services endpoint. To add Swagger 2 to a Spring REST web service, using the Springfox implementation add the following dependencies to the POM:

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-boot-starter</artifactId>
  <version>3.0.0</version>
</dependency>
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>3.0.0</version>
</dependency>

Let's add Swagger Docker Bean to the Spring App class:

@Bean
public Docket api() {
  return new Docket(DocumentationType.SWAGGER_2)
      .select()
      .apis(RequestHandlerSelectors.any())
      .paths(PathSelectors.any())
      .build();
}

Which will pick up any HTTP endpoints exposed by our application. Add to your app's property file (src/main/resources/application.properties):

spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER

And finally, to enable Swagger on the application, we need to use the `EnableSwagger2` annotation, by
annotating the main application class:

@EnableSwagger2
@SpringBootApplication
public class SkeletonApplication {
  // ...
}

title: Creating the Domain

text:
Our domain will be fairly simple; `Person`s that have `Address`es. Let's start with the `Person` entity:

package com.redis.om.skeleton.models;

import java.util.Set;

import org.springframework.data.annotation.Id;
import org.springframework.data.geo.Point;

import com.redis.om.spring.annotations.Document;
import com.redis.om.spring.annotations.Indexed;
import com.redis.om.spring.annotations.Searchable;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Data
@Document
public class Person {
  // Id Field, also indexed
  @Id
  @Indexed
  private String id;

  // Indexed for exact text matching
  @Indexed @NonNull
  private String firstName;

  @Indexed @NonNull
  private String lastName;

  //Indexed for numeric matches
  @Indexed @NonNull
  private Integer age;

  //Indexed for Full Text matches
  @Searchable @NonNull
  private String personalStatement;

  //Indexed for Geo Filtering
  @Indexed @NonNull
  private Point homeLoc;

  // Nest indexed object
  @Indexed @NonNull
  private Address address;

  @Indexed @NonNull
  private Set<String> skills;
}

The `Person` class has the following properties:

* `id`: An autogenerated `String` using [ULIDs](https://github.com/ulid/spec)
* `firstName`: A `String` representing their first or given name.
* `lastName`: A `String` representing their last or surname.
* `age`: An `Integer` representing their age in years.
* `personalStatement`: A `String` representing a personal text statement containing facts or other biographical information.
* `homeLoc`: A `org.springframework.data.geo.Point` representing the geo coordinates.
* `address`: An entity of type `Address` representing the Person's postal address.
* `skills`: A `Set<String>` representing a collection of Strings representing skills the Person possesses.

title: @Document

text: The `Person` class (`com.redis.om.skeleton.models.Person`) is annotated with `@Document` (`com.redis.om.spring.annotations.Document`), which is marks the object as a Redis entity to be persisted as a JSON document by the appropriate type of repository.

title: @Indexed and @Searchable

text:
The fields `id`, `firstName`, `lastName`, `age`, `homeLoc`, `address`, and `skills` are all annotated
with `@Indexed` (`com.redis.om.spring.annotations.Indexed`). On entities annotated with `@Document` Redis OM Spring will scan the fields and add an appropriate search index field to the schema for the entity. For example, for the `Person` class
an index named `com.redis.om.skeleton.models.PersonIdx` will be created on application startup. In the index schema, a search field will be added for each `@Indexed` annotated property. RediSearch, the underlying search engine powering searches, supports Text (full-text searches), Tag (exact-match searches), Numeric (range queries), Geo (geographic range queries), and Vector (vector queries) fields. For `@Indexed` fields, the appropriate search field (Tag, Numeric, or Geo) is selected based on the property's data type.

Fields marked as `@Searchable` (`com.redis.om.spring.annotations.Searchable`) such as `personalStatement` in `Person` are reflected as Full-Text search fields in the search index schema.

title: Nested Field Search Features

text:
The embedded class `Address` (`com.redis.om.skeleton.models.Address`) has several properties annotated with `@Indexed`  and `@Searchable`, which will generate search index fields in Redis. The scanning of these fields is triggered by the `@Indexed` annotation on the `address` property in the `Person` class:

package com.redis.om.skeleton.models;

import com.redis.om.spring.annotations.Indexed;
import com.redis.om.spring.annotations.Searchable;

import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@Data
@RequiredArgsConstructor(staticName = "of")
public class Address {

  @NonNull
  @Indexed
  private String houseNumber;

  @NonNull
  @Searchable(nostem = true)
  private String street;

  @NonNull
  @Indexed
  private String city;

  @NonNull
  @Indexed
  private String state;

  @NonNull
  @Indexed
  private String postalCode;

  @NonNull
  @Indexed
  private String country;
}

title: Spring Data Repositories

text:
With the model in place now, we need to create the bridge between the models and the Redis, a Spring Data Repository. Like other Spring Data Repositories, Redis OM Spring data repository's goal is to reduce the boilerplate code required to implement data access significantly. Create a Java interface like:

package com.redis.om.skeleton.models.repositories;

import com.redis.om.skeleton.models.Person;
import com.redis.om.spring.repository.RedisDocumentRepository;

public interface PeopleRepository extends RedisDocumentRepository<Person,String> {

}

That's really all we need to get all the CRUD and Paging/Sorting functionality. The
`RedisDocumentRepository` (`com.redis.om.spring.repository.RedisDocumentRepository`) extends `PagingAndSortingRepository` (`org.springframework.data.repository.PagingAndSortingRepository`) which extends CrudRepository to provide additional methods to retrieve entities using the pagination and sorting.

title: @EnableRedisDocumentRepositories

text:
Before we can fire up the application, we need to enable our Redis Document repositories. Like most
Spring Data projects, Redis OM Spring provides an annotation to do so; the `@EnableRedisDocumentRepositories`. We annotate the main application class:

@EnableRedisDocumentRepositories(basePackages = "com.redis.om.skeleton.*")
@EnableSwagger2
@SpringBootApplication
public class SkeletonApplication {

title: CRUD with Repositories

text:
With the repositories enabled, we can use our repo; let's put in some data to see the object mapping in action. Let’s create `CommandLineRunner` that will execute on application startup:

public class SkeletonApplication {

 @Bean
 CommandLineRunner loadTestData(PeopleRepository repo) {
   return args -> {
     repo.deleteAll();

     String thorSays = "The Rabbit Is Correct, And Clearly The Smartest One Among You.";

     // Serendipity, 248 Seven Mile Beach Rd, Broken Head NSW 2481, Australia
     Address thorsAddress = Address.of("248", "Seven Mile Beach Rd", "Broken Head", "NSW", "2481", "Australia");

     Person thor = Person.of("Chris", "Hemsworth", 38, thorSays, new Point(153.616667, -28.716667), thorsAddress, Set.of("hammer", "biceps", "hair", "heart"));

     repo.save(thor);
   };
 }

In the `loadTestData` method, we will take an instance of the `PeopleRepository` (thank you, Spring, for Dependency Injection!). Inside the returned lambda, we will first call the repo’s `deleteAll` method, which will ensure that we have clean data on each application reload.

We create a `Person` object using the Lombok generated builder method and then save it using the repo’s `save` method.

title: Keeping tabs with Redis Insight

text:
Let’s launch Redis Insight and connect to the localhost at port 6379. With a clean Redis Stack install, we can use the built-in CLI to check the keys in the system:

![Redis Insight](./images/002_stack_spring.png "Redis Insight")

For a small amount of data, you can use the `keys` command (for any significant amount of data, use `scan`):

keys *

If you want to keep an eye on the commands issued against the server, Redis Insight provides a
profiler. If you click the "profile" button at the bottom of the screen, it should reveal the profiler window, and there you can start the profiler by clicking on the "Start Profiler" arrow.

Let's start our Spring Boot application by using the Maven command:

./mvnw spring-boot:run

On Redis Insight, if the application starts correctly, you should see a barrage of commands fly by on the profiler:

![Redis Insight](./images/003_stack_spring.png "Redis Insight")

Now we can inspect the newly loaded data by simply refreshing the "Keys" view:

![Redis Insight](./images/004_stack_spring.png "Redis Insight")

You should now see two keys; one for the JSON document for "Thor" and one for the Redis Set that Spring Data Redis (and Redis OM Spring) use to maintain the list of primary keys for an entity.

You can select any of the keys on the key list to reveal their contents on the details panel. For JSON documents, we get a nice tree-view:

![Redis Insight](./images/005_stack_spring.png "Redis Insight")

Several Redis commands were executed on application startup. Let’s break them down so that we can understand what's transpired.

title: Index Creation

text:
The first one is a call to [`FT.CREATE`](), which happens after Redis OM Spring scanned the `@Document` annotations. As you can see, since it encountered the annotation on `Person`, it creates the  `PersonIdx` index.

"FT.CREATE"
  "com.redis.om.skeleton.models.PersonIdx" "ON" "JSON"
  "PREFIX" "1" "com.redis.om.skeleton.models.Person:"
"SCHEMA"
  "$.id" "AS" "id" "TAG"
  "$.firstName" "AS" "firstName" "TAG"
  "$.lastName" "AS" "lastName" "TAG"
  "$.age" "AS" "age" "NUMERIC"
  "$.personalStatement" "AS" "personalStatement" "TEXT"
  "$.homeLoc" "AS" "homeLoc" "GEO"
  "$.address.houseNumber" "AS" "address_houseNumber" "TAG"
  "$.address.street" "AS" "address_street" "TEXT" "NOSTEM"
  "$.address.city" "AS" "address_city" "TAG"
  "$.address.state" "AS" "address_state" "TAG"
  "$.address.postalCode" "AS" "address_postalCode" "TAG"
  "$.address.country" "AS" "address_country" "TAG"
  "$.skills[*]" "AS" "skills"

title: Cleaning the Person Repository

text:
The next set of commands are generated by the call to `repo.deleteAll()`:

"DEL" "com.redis.om.skeleton.models.Person"
"KEYS" "com.redis.om.skeleton.models.Person:*"

The first call clears the set of Primary Keys that Spring Data Redis maintains (and therefore Redis OM Spring), the second call collects all the keys to delete them, but there are none to delete on this first load of the data.

title: Saving Person Entities

text:
The next repo call is `repo.save(thor)` that triggers the following sequence:

"SISMEMBER" "com.redis.om.skeleton.models.Person" "01FYANFH68J6WKX2PBPX21RD9H"
"EXISTS" "com.redis.om.skeleton.models.Person:01FYANFH68J6WKX2PBPX21RD9H"
"JSON.SET" "com.redis.om.skeleton.models.Person:01FYANFH68J6WKX2PBPX21RD9H" "." "{"id":"01FYANFH68J6WKX2PBPX21RD9H","firstName":"Chris","lastName":"Hemsworth","age":38,"personalStatement":"The Rabbit Is Correct, And Clearly The Smartest One Among You.","homeLoc":"153.616667,-28.716667","address":{"houseNumber":"248","street":"Seven Mile Beach Rd","city":"Broken Head","state":"NSW","postalCode":"2481","country":"Australia"},"skills":["biceps","hair","heart","hammer"]}
"SADD" "com.redis.om.skeleton.models.Person" "01FYANFH68J6WKX2PBPX21RD9H"

Let's break it down:

* The first call uses the generated ULID to check if the id is in the set of primary keys (if it is, it’ll be removed)
* The second call checks if JSON document exists (if it is, it’ll be removed)
* The third call uses the [`JSON.SET`]() command to save the JSON payload
* The last call adds the primary key of the saved document to the set of primary keys

Now that we’ve seen the repository in action via the `.save` method, we know that the trip from Java to Redis work. Now let’s add some more data to make the interactions more interesting:

@Bean
CommandLineRunner loadTestData(PeopleRepository repo) {
  return args -> {
    repo.deleteAll();

    String thorSays = "The Rabbit Is Correct, And Clearly The Smartest One Among You.";
    String ironmanSays = "Doth mother know you weareth her drapes?";
    String blackWidowSays = "Hey, fellas. Either one of you know where the Smithsonian is? I’m here to pick up a fossil.";
    String wandaMaximoffSays = "You Guys Know I Can Move Things With My Mind, Right?";
    String gamoraSays = "I Am Going To Die Surrounded By The Biggest Idiots In The Galaxy.";
    String nickFurySays = "Sir, I’m Gonna Have To Ask You To Exit The Donut";

    // Serendipity, 248 Seven Mile Beach Rd, Broken Head NSW 2481, Australia
    Address thorsAddress = Address.of("248", "Seven Mile Beach Rd", "Broken Head", "NSW", "2481", "Australia");

    // 11 Commerce Dr, Riverhead, NY 11901
    Address ironmansAddress = Address.of("11", "Commerce Dr", "Riverhead", "NY",  "11901", "US");

    // 605 W 48th St, New York, NY 10019
    Address blackWidowAddress = Address.of("605", "48th St", "New York", "NY", "10019", "US");

    // 20 W 34th St, New York, NY 10001
    Address wandaMaximoffsAddress = Address.of("20", "W 34th St", "New York", "NY", "10001", "US");

    // 107 S Beverly Glen Blvd, Los Angeles, CA 90024
    Address gamorasAddress = Address.of("107", "S Beverly Glen Blvd", "Los Angeles", "CA", "90024", "US");

    // 11461 Sunset Blvd, Los Angeles, CA 90049
    Address nickFuryAddress = Address.of("11461", "Sunset Blvd", "Los Angeles", "CA", "90049", "US");

    Person thor = Person.of("Chris", "Hemsworth", 38, thorSays, new Point(153.616667, -28.716667), thorsAddress, Set.of("hammer", "biceps", "hair", "heart"));
    Person ironman = Person.of("Robert", "Downey", 56, ironmanSays, new Point(40.9190747, -72.5371874), ironmansAddress, Set.of("tech", "money", "one-liners", "intelligence", "resources"));
    Person blackWidow = Person.of("Scarlett", "Johansson", 37, blackWidowSays, new Point(40.7215259, -74.0129994), blackWidowAddress, Set.of("deception", "martial_arts"));
    Person wandaMaximoff = Person.of("Elizabeth", "Olsen", 32, wandaMaximoffSays, new Point(40.6976701, -74.2598641), wandaMaximoffsAddress, Set.of("magic", "loyalty"));
    Person gamora = Person.of("Zoe", "Saldana", 43, gamoraSays, new Point(-118.399968, 34.073087), gamorasAddress, Set.of("skills", "martial_arts"));
    Person nickFury = Person.of("Samuel L.", "Jackson", 73, nickFurySays, new Point(-118.4345534, 34.082615), nickFuryAddress, Set.of("planning", "deception", "resources"));

    repo.saveAll(List.of(thor, ironman, blackWidow, wandaMaximoff, gamora, nickFury));
  };
}

We have 6 People in the database now; since we’re using the devtools in Spring, the app should have reloaded, and the database reseeded with new data. Press enter the key pattern input box in Redis Insight to refresh the view. Notice that we used the repository’s `saveAll` to save several objects in bulk.

![Redis Insight](./images/006_stack_spring.png "Redis Insight")

title: Web Service Endpoints

text:
Before we beef up the repository with more interesting queries, let’s create a controller so that we can test our queries using the Swagger UI:

package com.redis.om.skeleton.controllers;

import com.redis.om.skeleton.models.Person;
import com.redis.om.skeleton.models.repositories.PeopleRepository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/people")
public class PeopleControllerV1 {
 @Autowired
 PeopleRepository repo;

 @GetMapping("all")
 Iterable<Person> all() {
   return repo.findAll();
 }
}

In this controller, we inject a repository and use one of the CRUD methods, `findAll()`, to return all the `Person` documents in the database.

If we navigate to http://localhost:8080/swagger-ui/ you should see the Swagger UI:

![SwaggerUI](./images/007_stack_spring.png "SwaggerUI")

We can see the `/all` method from our people-controller-v-1, expanding that you should see:

![SwaggerUI](./images/008_stack_spring.png "SwaggerUI")

And if you select "Try it out" and then "Execute," you should see the resulting JSON array containing all People documents in the database:

![SwaggerUI](./images/009_stack_spring.png "SwaggerUI")

Let’s also add the ability to retrieve a Person by its id by using the repo’s findById method:

@GetMapping("{id}")
Optional<Person> byId(@PathVariable String id) {
  return repo.findById(id);
}

Refreshing the Swagger UI, we should see the newly added endpoint. We can grab an id using the [`SRANDMEMBER`]() command on the Redis Insight CLI like this:

SRANDMEMBER com.redis.om.skeleton.models.Person

Plugging the resulting ID in the Swagger UI, we can get the corresponding  JSON document:

![SwaggerUI](./images/010_stack_spring.png "SwaggerUI")

title: Custom Repository Finders

text:
Now that we tested quite a bit of the CRUD functionality, let's add some custom finders to our repository. We’ll start with a finder over a numeric range, on the `age` property of `Person`:

public interface PeopleRepository extends RedisDocumentRepository<Person,String> {
 // Find people by age range
 Iterable<Person> findByAgeBetween(int minAge, int maxAge);
}

At runtime, the repository method `findByAgeBetween` is fulfilled by the framework, so all you need to do is declare it, and Redis OM Spring will handle the querying and mapping of the results. The property or properties to be used are picked after the key phrase "findBy". The "Between" keyword is the predicate that tells the query builder what operation to use.

To test it on the Swagger UI, let’s add a corresponding method to the controller:

@GetMapping("age_between")
Iterable<Person> byAgeBetween( //
    @RequestParam("min") int min, //
    @RequestParam("max") int max) {
  return repo.findByAgeBetween(min, max);
}

Refreshing the UI, we can see the new endpoint. Let’s try it with some data:

![SwaggerUI](./images/011_stack_spring.png "SwaggerUI")

Invoke the endpoint with the value `30` for `min` and `37` for `max` we get two hits;
"Scarlett Johansson" and "Elizabeth Olsen" are the only two people with ages between 30 and 37.

![SwaggerUI](./images/012_stack_spring.png "SwaggerUI")

If we look at the Redis Insight Profiler, we can see the resulting query, which is a range query on the index numeric field `age`:

![Redis Insight](./images/013_stack_spring.png "Redis Insight Profiler")

We can also create query methods with more than one property. For example, if we wanted to do a query by first and last names, we would declare a repository method like:

// Find people by their first and last name
Iterable<Person> findByFirstNameAndLastName(String firstName, String lastName);

Let’s add a corresponding controller method:

@GetMapping("name")
Iterable<Person> byFirstNameAndLastName(@RequestParam("first") String firstName, //
    @RequestParam("last") String lastName) {
  return repo.findByFirstNameAndLastName(firstName, lastName);
}

Once again, we can refresh the swagger UI and test the newly created endpoint:

![SwaggerUI](./images/014_stack_spring.png "SwaggerUI")

Executing the request with the first name `Robert` and last name `Downey`, we get:

![SwaggerUI](./images/015_stack_spring.png "SwaggerUI")

And the resulting query on Redis Insight:

![Redis Insight](./images/016_stack_spring.png "Redis Insight Profiler")

Now let’s try a Geospatial query. The `homeLoc` property is a Geo Point, and by using the "Near" predicate in our method declaration, we can get a finder that takes a point and a radius around that point to search:

// Draws a circular geofilter around a spot and returns all people in that
// radius
Iterable<Person> findByHomeLocNear(Point point, Distance distance);
And the corresponding controller method:

@GetMapping("homeloc")
Iterable<Person> byHomeLoc(//
    @RequestParam("lat") double lat, //
    @RequestParam("lon") double lon, //
    @RequestParam("d") double distance) {
  return repo.findByHomeLocNear(new Point(lon, lat), new Distance(distance, Metrics.MILES));
}

Refreshing the Swagger US, we should now see the `byHomeLoc` endpoint. Let’s see which of the Avengers live within 10 miles of Suffolk Park Pub in South Wales, Australia... hmmm.

![SwaggerUI](./images/017_stack_spring.png "SwaggerUI")

Executing the request, we get the record for Chris Hemsworth:

![SwaggerUI](./images/018_stack_spring.png "SwaggerUI")

and in Redis Insight we can see the backing query:

![Redis Insight](./images/019_stack_spring.png "Redis Insight Profiler")

Let’s try a full-text search query against the `personalStatement` property. To do so, we prefix our query method with the word `search` as shown below:

// Performs full-text search on a person’s personal Statement
Iterable<Person> searchByPersonalStatement(String text);

And the corresponding controller method:

@GetMapping("statement")
Iterable<Person> byPersonalStatement(@RequestParam("q") String q) {
  return repo.searchByPersonalStatement(q);
}

Once again, we can try it on the Swagger UI with the text "mother":

![SwaggerUI](./images/020_stack_spring.png "SwaggerUI")

Which results in a single hit, the record for Robert Downey Jr.:

![SwaggerUI](./images/021_stack_spring.png "SwaggerUI")

Notice that you can pass a query string like "moth*" with wildcards if needed

![SwaggerUI](./images/022_stack_spring.png "SwaggerUI")

title: Nested object searches

text:
You’ve noticed that the `address` object in `Person` is mapped as a JSON object. If we want to search by address fields, we use an underscore to access the nested fields. For example, if we wanted to find a Person by their city, the method signature would be:

// Performing a tag search on city
Iterable<Person> findByAddress_City(String city);

Let’s add the matching controller method so that we can test it:

@GetMapping("city")
Iterable<Person> byCity(@RequestParam("city") String city) {
  return repo.findByAddress_City(city);
}

Let’s test the byCity endpoint:

![SwaggerUI](./images/023_stack_spring.png "SwaggerUI")

As expected, we should get two hits; Scarlett Johansson and Elizabeth Olsen, both with addresses in Nee York:

![SwaggerUI](./images/024_stack_spring.png "SwaggerUI")

The skills set is indexed as tag search. To find a Person with any of the skills in a provided list, we can add a repository method like:

// Search Persons that have one of multiple skills (OR condition)
Iterable<Person> findBySkills(Set<String> skills);

And the corresponding controller method:

@GetMapping("skills")
Iterable<Person> byAnySkills(@RequestParam("skills") Set<String> skills) {
  return repo.findBySkills(skills);
}

Let's test the endpoint with the value "deception":

![SwaggerUI](./images/025_stack_spring.png "SwaggerUI")

The search returns the records for Scarlett Johansson and Samuel L. Jackson:

![SwaggerUI](./images/026_stack_spring.png "SwaggerUI")

We can see the backing query using a tag search:

![Redis Insight](./images/027_stack_spring.png "Redis Insight Profiler")

title: Fluid Searching with Entity Streams

text:
Redis OM Spring Entity Streams provides a Java 8 Streams interface to Query Redis JSON documents using Redis Stack. Entity Streams allow you to process data in a typesafe declarative way similar to SQL statements. Streams can be used to express a query as a chain of operations.

Entity Streams in Redis OM Spring provide the same semantics as Java 8 streams. Streams can be made of Redis Mapped entities (`@Document`) or one or more properties of an Entity. Entity Streams progressively build the query until a terminal operation is invoked (such as `collect`). Whenever a Terminal operation is applied to a Stream, the Stream cannot accept additional operations to its pipeline, which means that the Stream is started.

Let’s start with a simple example, a Spring `@Service` which includes `EntityStream` to query for instances of the mapped class `Person`:

package com.redis.om.skeleton.services;

import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.redis.om.skeleton.models.Person;
import com.redis.om.skeleton.models.Person$;
import com.redis.om.spring.search.stream.EntityStream;

@Service
public class PeopleService {
  @Autowired
  EntityStream entityStream;

  // Find all people
  public Iterable<Person> findAllPeople(int minAge, int maxAge) {
    return entityStream //
        .of(Person.class) //
        .collect(Collectors.toList());
  }

}

The `EntityStream` is injected into the `PeopleService` using `@Autowired`. We can then get a stream for `Person` objects by using `entityStream.of(Person.class)`. The stream represents the equivalent of a `SELECT * FROM Person` on a relational database. The call to `collect` will then execute the underlying query and return a collection of all `Person` objects in Redis.

title: Entity Meta-model

text:
You’re provided with a generated meta-model to produce more elaborate queries, a class with the same name as your model but ending with a dollar sign. In the
example below, our entity model is `Person`; therefore, we get a meta-model named `Person$`. With the meta-model, you have access to the
underlying search engine field operations. For example, we have an `age` property which is an integer. Therefore our meta-model has an `AGE` property with
numeric operations we can use with the stream’s `filter` method such as `between`.

// Find people by age range
public Iterable<Person> findByAgeBetween(int minAge, int maxAge) {
  return entityStream //
      .of(Person.class) //
      .filter(Person$.AGE.between(minAge, maxAge)) //
      .sorted(Person$.AGE, SortOrder.ASC) //
      .collect(Collectors.toList());
}

In this example, we also use the Streams `sorted` method to declare that our stream will be sorted by the `Person$.AGE` in `ASC`ending order.

To "AND" property expressions we can chain multiple `.filter` statements. For example, to recreate
the finder by first and last name we can use an Entity Stream in the following way:

// Find people by their first and last name
public Iterable<Person> findByFirstNameAndLastName(String firstName, String lastName) {
  return entityStream //
      .of(Person.class) //
      .filter(Person$.FIRST_NAME.eq(firstName)) //
      .filter(Person$.LAST_NAME.eq(lastName)) //
      .collect(Collectors.toList());
}

In this article, we explored how Redis OM Spring provides a couple of APIs to tap into the power of Redis Stack’s document database and search features from Spring Boot application. We’ll explore other Redis Stack features via Redis OM Spring in future articles


title: RedisOM for .NET

summary: Learn how to build with Redis Stack and .NET

title: Overview

text: [Redis OM .NET](https://github.com/redis/redis-om-dotnet) is a purpose-built library for handling documents in Redis Stack. In this tutorial, we'll build a simple ASP.NET Core Web-API app for performing CRUD operations on a simple Person & Address model, and we'll accomplish all of this with Redis OM .NET.

title: Prerequisites

text:
* [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
* Any IDE for writing .NET (Visual Studio, Rider, Visual Studio Code).
* RediSearch must be installed as part of your Redis Stack configuration.
* Optional: Docker Desktop for running redis-stack in docker for local testing.

title: Skip to the code

text: If you want to skip this tutorial and just jump straight into code, all the source code is available in [GitHub](https://github.com/redis-developer/redis-om-dotnet-skeleton-app)

title: Run Redis Stack

text:
There are a variety of ways to run Redis Stack. One way is to use the docker image:

[code example]

title: Create the project

text:
To create the project, just run:

[code example]

Then open the `Redis.OM.Skeleton.csproj` file in your IDE of choice.

title: Configure the app

text: Add a `REDIS_CONNECTION_STRING` field to your `appsettings.json` file to configure the application. Set that connection string to be the URI of your Redis instance. If using the docker command mentioned earlier, your connection string will be `redis://localhost:6379`.

title: Connection string specification

text: The specification for Redis URIs is located [here](https://www.iana.org/assignments/uri-schemes/prov/redis). You can use `:password@host:port` or `default:password@host:port` for connection strings that do not include `username`.

title: Create the model

text:
Make sure to add the `Redis.OM` package to your project. This package makes it easy to create models and query your Redis domain objects.

[code example]

Now it's time to create the `Person`/`Address` model that the app will use for storing/retrieving people. Create a new directory called `Model` and add the files `Address.cs` and `Person.cs` to it. In `Address.cs`, add the following:

[code example]

Here, you'll notice that except `StreetName`, marked as `Searchable`, all the fields are decorated with the `Indexed` attribute. These attributes (`Searchable` and `Indexed`) tell Redis OM that you want to be able to use those fields in queries when querying your documents in Redis Stack. `Address` will not be a Document itself, so the top-level class is not decorated with anything; instead, the `Address` model will be embedded in our `Person` model.

To that end, add the following to `Person.cs`

[code example]

There are a few things to take note of here:

1. `[Document(StorageType = StorageType.Json, Prefixes = new []{"Person"})]` Indicates that the data type that Redis OM will use to store the document in Redis is JSON and that the prefix for the keys for the Person class will be `Person`.

2. `[Indexed(CascadeDepth = 1)] Address? Address { get; set; }` is one of two ways you can index an embedded object with Redis OM. This way instructs the index to cascade to the objects in the object graph, `CascadeDepth` of 1 means that it will traverse just one level, indexing the object as if it were building the index from scratch. The other method uses the `JsonPath` property of the individual indexed fields you want to search for. This more surgical approach limits the size of the index.

3. the `Id` property is marked as a `RedisIdField`. This denotes the field as one that will be used to generate the document's key name when it's stored in Redis.

title: Create the Index

text:
With the model built, the next step is to create the index in Redis. The most correct way to manage this is to spin the index creation out into a Hosted Service, which will run when the app spins up.
Create a `HostedServices` directory and add `IndexCreationService.cs` to that. In that file, add the following, which will create the index on startup.

[code example]

Next, add the following to `Program.cs` to register the service on startup:

[code example]

title: Inject the RedisConnectionProvider

text:
Redis OM uses the `RedisConnectionProvider` class to handle connections to Redis and provides the classes you can use to interact with Redis. To use it, simply inject an instance of the RedisConnectionProvider into your app. In your `Program.cs` file, add:

[code example]

This will pull your connection string out of the config and initialize the provider. The provider will now be available in your controllers/services to use.

title: Create the PeopleController

text:
The final puzzle piece is to write the actual API controller for our People API. In the `controllers` directory, add the file `PeopleController.cs`, the skeleton of the `PeopleController`class will be:

[code example]

title: Inject the RedisConnectionProvider

text:
To interact with Redis, inject the RedisConnectionProvider. During this dependency injection, pull out a `RedisCollection<Person>` instance, which will allow a fluent interface for querying documents in Redis.

[code example]

title: Add route for creating a Person

text:
The first route to add to the API is a POST request for creating a person, using the `RedisCollection`, it's as simple as calling `InsertAsync`, passing in the person object:

[code example]

title: Add route to filter by age

text:
The first filter route to add to the API will let the user filter by a minimum and maximum age. Using the LINQ interface available to the `RedisCollection`, this is a simple operation:

[code example]

title: Filter by GeoLocation

text:
Redis OM has a `GeoLoc` data structure, an instance of which is indexed by the `Address` model, with the `RedisCollection`, it's possible to find all objects with a radius of particular position using the `GeoFilter` method along with the field you want to filter:

[code example]

title: Filter by exact string

text:
When a string property in your model is marked as `Indexed`, e.g. `FirstName` and `LastName`, Redis OM can perform exact text matches against them. For example, the following two routes filter by `PostalCode` and name demonstrate exact string matches.

[code example]

title: Filter with a full-text search

text:
When a property in the model is marked as `Searchable`, like `StreetAddress` and `PersonalStatement`, you can perform a full-text search, see the filters for the `PersonalStatement` and `StreetAddress`:

[code example]

title: Filter by array membership

text:
When a string array or list is marked as `Indexed`, Redis OM can filter all the records containing a given string using the `Contains` method of the array or list. For example, our `Person` model has a list of skills you can query by adding the following route.

[code example]

title: Updating a person

text:
Updating a document in Redis Stack with Redis OM can be done by first materializing the person object, making your desired changes, and then calling `Save` on the collection. The collection is responsible for keeping track of updates made to entities materialized in it; therefore, it will track and apply any updates you make in it. For example, add the following route to update the age of a Person given their Id:

[code example]

title: Delete a person

text:
Deleting a document from Redis can be done with `Unlink`. All that's needed is to call Unlink, passing in the key name. Given an id, we can reconstruct the key name using the prefix and the id:

[code example]

title: Run the app

text: All that's left to do now is to run the app and test it. You can do so by running `dotnet run`, the app is now exposed on port 5000, and there should be a swagger UI that you can use to play with the API at http://localhost:5000/swagger. There's a couple of scripts, along with some data files, to insert some people into Redis using the API in the [GitHub repo](https://github.com/redis-developer/redis-om-dotnet-skeleton-app/tree/main/data)

title: Viewing data in with Redis Insight

text:
You can either install the Redis Insight GUI or use the Redis Insight GUI running on http://localhost:8001/.

You can view the data by following these steps:

1. Accept the EULA

![Accept EULA](./images/Accept_EULA.png)

2. Click the Add Redis Database button

![Add Redis Database Button](./images/Add_Redis_Database_button.png)

3. Enter your hostname and port name for your redis server. If you are using the docker image, this is `localhost` and `6379` and give your database an alias

![Configure Redis Insight Database](./images/Configure_Redis_Insight_Database.png)

4. Click `Add Redis Database.`

title: Resources

text: The source code for this tutorial can be found in [GitHub](https://github.com/redis-developer/redis-om-dotnet-skeleton-app).

code: docker run -d -p 6379:6379 -p 8001:8001 redis/redis-stack

section_id: run-redis-stack

code: dotnet new webapi -n Redis.OM.Skeleton --no-https --kestrelHttpPort 5000

section_id: create-the-project

code: dotnet add package Redis.OM

section_id: create-the-model

code:
using Redis.OM.Modeling;

namespace Redis.OM.Skeleton.Model;

public class Address
{
    [Indexed]
    public int? StreetNumber { get; set; }

    [Indexed]
    public string? Unit { get; set; }

    [Searchable]
    public string? StreetName { get; set; }

    [Indexed]
    public string? City { get; set; }

    [Indexed]
    public string? State { get; set; }

    [Indexed]
    public string? PostalCode { get; set; }

    [Indexed]
    public string? Country { get; set; }

    [Indexed]
    public GeoLoc Location { get; set; }
}

code:
using Redis.OM.Modeling;

namespace Redis.OM.Skeleton.Model;

[Document(StorageType = StorageType.Json, Prefixes = new []{"Person"})]
public class Person
{
    [RedisIdField] [Indexed]public string? Id { get; set; }

    [Indexed] public string? FirstName { get; set; }

    [Indexed] public string? LastName { get; set; }

    [Indexed] public int Age { get; set; }

    [Searchable] public string? PersonalStatement { get; set; }

    [Indexed] public string[] Skills { get; set; } = Array.Empty<string>();

    [Indexed(CascadeDepth = 1)] public Address? Address { get; set; }

}

code:
using Redis.OM.Skeleton.Model;

namespace Redis.OM.Skeleton.HostedServices;

public class IndexCreationService : IHostedService
{
    private readonly RedisConnectionProvider _provider;
    public IndexCreationService(RedisConnectionProvider provider)
    {
        _provider = provider;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        await _provider.Connection.CreateIndexAsync(typeof(Person));
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}

section_id: create-the-index

section_id: inject-the-redisconnectionprovider

code:
using Microsoft.AspNetCore.Mvc;
using Redis.OM.Searching;
using Redis.OM.Skeleton.Model;

namespace Redis.OM.Skeleton.Controllers;

[ApiController]
[Route("[controller]")]
public class PeopleController : ControllerBase
{

}

section_id: create-the-peoplecontroller

code:
private readonly RedisCollection<Person> _people;
private readonly RedisConnectionProvider _provider;
public PeopleController(RedisConnectionProvider provider)
{
    _provider = provider;
    _people = (RedisCollection<Person>)provider.RedisCollection<Person>();
}

code:
[HttpPost]
public async Task<Person> AddPerson([FromBody] Person person)
{
    await _people.InsertAsync(person);
    return person;
}

section_id: add-route-for-creating-a-person

code:
[HttpGet("filterAge")]
public IList<Person> FilterByAge([FromQuery] int minAge, [FromQuery] int maxAge)
{
    return _people.Where(x => x.Age >= minAge && x.Age <= maxAge).ToList();
}

section_id: add-route-to-filter-by-age

code:
[HttpGet("filterGeo")]
public IList<Person> FilterByGeo([FromQuery] double lon, [FromQuery] double lat, [FromQuery] double radius, [FromQuery] string unit)
{
    return _people.GeoFilter(x => x.Address!.Location, lon, lat, radius, Enum.Parse<GeoLocDistanceUnit>(unit)).ToList();
}

section_id: filter-by-geolocation

code:
[HttpGet("filterName")]
public IList<Person> FilterByName([FromQuery] string firstName, [FromQuery] string lastName)
{
    return _people.Where(x => x.FirstName == firstName && x.LastName == lastName).ToList();
}

[HttpGet("postalCode")]
public IList<Person> FilterByPostalCode([FromQuery] string postalCode)
{
    return _people.Where(x => x.Address!.PostalCode == postalCode).ToList();
}

section_id: filter-by-exact-string

code:
[HttpGet("fullText")]
public IList<Person> FilterByPersonalStatement([FromQuery] string text){
    return _people.Where(x => x.PersonalStatement == text).ToList();
}

[HttpGet("streetName")]
public IList<Person> FilterByStreetName([FromQuery] string streetName)
{
    return _people.Where(x => x.Address!.StreetName == streetName).ToList();
}

section_id: filter-with-a-full-text-search

code:
[HttpGet("skill")]
public IList<Person> FilterBySkill([FromQuery] string skill)
{
    return _people.Where(x => x.Skills.Contains(skill)).ToList();
}

section_id: filter-by-array-membership

code:
[HttpPatch("updateAge/{id}")]
public IActionResult UpdateAge([FromRoute] string id, [FromBody] int newAge)
{
    foreach (var person in _people.Where(x => x.Id == id))
    {
        person.Age = newAge;
    }
    _people.Save();
    return Accepted();
}

section_id: updating-a-person

code:
[HttpDelete("{id}")]
public IActionResult DeletePerson([FromRoute] string id)
{
    _provider.Connection.Unlink($"Person:{id}");
    return NoContent();
}

section_id: delete-a-person