title: Connect to Azure Managed Redis

summary: Learn how to authenticate to an Azure Managed Redis (AMR) database

title: Overview

role: overview

text:
The [`@redis/entraid`](https://github.com/redis/node-redis/tree/master/packages/entraid)
package lets you authenticate your app to
[Azure Managed Redis (AMR)](https://azure.microsoft.com/en-us/products/managed-redis)
using [Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/identity/).
You can authenticate using a system-assigned or user-assigned
[managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview),
a [service principal](https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals),
an [auth code flow](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow),
or a [`DefaultAzureCredential`](https://learn.microsoft.com/en-gb/dotnet/azure/sdk/authentication/credential-chains?tabs=dac#defaultazurecredential-overview) instance.
The `@redis/entraid` code fetches and renews the authentication tokens for you automatically.

title: Install

role: setup

text:
Install [`node-redis`]() and
`@redis/entraid` with the following commands:

[code example]

title: Create a credentials provider instance

text:
A credentials provider object obtains the authentication credentials you
need when you connect to Redis (see the [Connect](#connect) section below
for a connection example). The `EntraIdCredentialsProviderFactory`
class provides a factory method for each type of credentials provider.
Import `EntraIdCredentialsProviderFactory` with the following code:

[code example]

Then, use the appropriate factory method to create your credentials provider:

-   Use [`createForClientCredentials()`](#authenticate-with-a-service-principal)
    to authenticate with a service principal using a client secret.
-   Use [`createForClientCredentialsWithCertificate()`](#authenticate-with-a-service-principal)
    to authenticate with a service principal using a certificate.
-   Use [`createForSystemAssignedManagedIdentity()`](#authenticate-with-a-managed-identity)
    to authenticate with a system-assigned managed identity.
-   Use [`createForUserAssignedManagedIdentity()`](#authenticate-with-a-managed-identity)
    to authenticate with a user-assigned managed identity.
-   Use [`createForAuthorizationCodeWithPKCE()`](#auth-code-flow-with-pkce)
    for interactive authentication flows in user applications.
-   Use [`createForDefaultAzureCredential()`](#def-az-cred)
    to authenticate with Azure Identity's `DefaultAzureCredential` class.

The sections below describe these factory functions in more detail.

title: Provider parameters

text:
All these factory functions receive an object containing the parameters
to create the credentials provider. The object generally contains the following
fields, but specific factory methods add or omit particular parameters:

-   `clientId`: A string containing the client ID.
-   `scopes`: (Optional) A string or an array of strings defining the
    [scopes](https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc)
    you want to apply. Configure your client application to acquire a Microsoft Entra token for scope, https://redis.azure.com/.default or acca5fbb-b7e4-4009-81f1-37e38fd66d78/.default with the
    [Microsoft Authentication Library (MSAL)](https://learn.microsoft.com/en-us/entra/identity-platform/msal-overview). Note that the `entra-id-credentials-provider-factory`
    module exports a constant `REDIS_SCOPE_DEFAULT` for the default scope. See the
    [`DefaultAzureCredential`](#def-az-cred) example below to learn how to use this.
-   `authorityConfig`: (Optional) [Authority](https://learn.microsoft.com/en-us/entra/identity-platform/msal-client-application-configuration#authority)
    settings. See the [`authorityConfig`](#authorityconfig) section below for a full
    description.
-   `tokenManagerConfig`: An object with fields that specify how to refresh the token.
    See the [`tokenManagerConfig`](#tokenmanagerconfig) section below for a full
    description.

#### **tokenManagerConfig**

You can configure an authentication token to expire after a certain amount of
time, known as its *lifetime*. You must refresh a token before its lifetime
is over to continue using it (see
[Configurable token lifetimes in the Microsoft identity platform](https://learn.microsoft.com/en-us/entra/identity-platform/configurable-token-lifetimes)
for more information). The `tokenManagerConfig` object lets you pass parameters to
specify how you want to manage token refreshes. The fields of the object are listed
below:

-   `expirationRefreshRatio`: This is the fraction of the token's lifetime that should
    elapse before a refresh is triggered. For example, a value of 0.75 means the token
    should be refreshed when 75% of its lifetime has elapsed.
-   `retry`: This object specifies the policy to retry refreshing the token if
    an error occurs. It has the following fields:
    -   `maxAttempts`: The maximum number of times to attempt a refresh before
        aborting.
    -   `initialDelayMs`: The delay (in milliseconds) before retrying after the
        first failed attempt.
    -   `maxDelayMs`: The maximum time (in milliseconds) to wait between attempts
        to refresh.
    -   `backoffMultiplier`: The
        [exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff)
        for the time between attempts to refresh. For example, a value of 2
        will double the delay for each attempt. Use a value of 1 to keep
        the delay roughly constant.
    -   `jitterPercentage`: (Optional) The maximum fraction of the calculated delay time to
        randomly add or subtract. For example, a value of 0.1 will add or subtract
        up to 10% of the delay time. This random component helps to prevent
        repeated collisions between clients that have the same retry settings.
    -   `isRetryable`: (Optional) This specifies a function with the signature

        `(error, attempt) => boolean`

        where `error` is the error object from
        a failed attempt and `attempt` is the number of attempts previously made.
        This function classifies errors from the identity provider as
        retryable (returning `true`) or non-retryable (returning `false`).
        Implement your own custom logic here to determine if a token refresh failure
        should be retried, based on the type of error. For example, a refresh failure
        caused by a transient network error would probably succeed eventually if you retry
        it, but an invalid certificate is generally a fatal error. If you don't supply a custom
        function in this parameter, the default behavior is to retry all types of errors.

#### **authorityConfig**

The `authorityConfig` object has a `type` field that can take any of
the string values `default`, `multi-tenant`, or `custom`. If you use
`multi-tenant` then you must also supply a `tenantId` field containing
the tenant ID string:

[code example]

If you use `custom` then you must also supply an `authorityUrl`
containing the authority URL string:

[code example]
See Microsoft's [Authority]([Authority](https://learn.microsoft.com/en-us/entra/identity-platform/msal-client-application-configuration#authority))
docs for more information.

title: Authenticate with a service principal

role: security

text:
Use the `createForClientCredentials()` factory function to create a
credentials provider that authenticates to AMR using a
service principal (see the
[Microsoft documentation](https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals) to learn more about service principals).

You will need the following details of your service principal to make the connection:

- Client ID
- Client secret
- Tenant ID

The example below shows how to call `createForClientCredentials()`. Note that the
[parameter object](#provider-parameters) includes an extra field here for the
client secret.

[code example]

If you want to authenticate using a service principal with a certificate,
use `createForClientCredentialsWithCertificate()` to create the credentials
provider. This is similar to `createForClientCredentials()` but it takes
a `clientCertificate` parameter object instead of the `clientSecret` parameter.
This object has the following string fields:

-   `x5c`: X.509 certificate.
-   `privateKey`: The certificate's private key.
-   `thumbprintSha256`: (Optional) SHA-256 hash of the certificate.

The example below shows how to call `createForClientCredentialsWithCertificate()` and
demonstrates the `retry` parameter in `tokenManagerConfig`:

[code example]

title: Authenticate with a managed identity

text:
You can authenticate to AMR using a managed identity (see the
[Microsoft documentation](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview) to learn more about managed identities).

For a system-assigned managed identity, use the `createForSystemAssignedManagedIdentity()` factory function as shown in the example below:

[code example]

For a user-assigned managed identity, use `createForUserAssignedManagedIdentity()`.
Here, the [parameter object](#provider-parameters) includes an extra field for
the user-assigned client ID.

[code example]

title: Auth code flow with PKCE

text:
*Auth code flow with Proof Key for Code Exchange (PKCE)* lets a client app
authenticate for access to web APIs and other restricted resources. This
requires a redirect URI to return control to your app after authentication.
See
[Microsoft identity platform and OAuth 2.0 authorization code flow](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow)
for more information.

Use the `createForAuthorizationCodeWithPKCE()` factory method to use auth code flow
with PKCE. The example below shows the extra field `redirectUri` in the parameter
object:

[code example]

title: Authenticate with `DefaultAzureCredential` {#def-az-cred}

text:
The
[`DefaultAzureCredential`](https://learn.microsoft.com/en-gb/dotnet/azure/sdk/authentication/credential-chains?tabs=dac#defaultazurecredential-overview)
class in `@azure/identity` is designed for use during development. It simplifies
authentication by automatically trying different credentials until one succeeds.
See [`DefaultAzureCredential` overview](https://learn.microsoft.com/en-gb/dotnet/azure/sdk/authentication/credential-chains?tabs=dac#defaultazurecredential-overview)
in the Microsoft docs for more information.

The example below shows how to use `createForDefaultAzureCredential()`:

[code example]

Note that when you use `createForDefaultAzureCredential()`, you must:

-   Create your own instance of `DefaultAzureCredential`.
-   Pass the same parameters to the factory method that you would use with the `getToken()`
    method:
    -   `scopes`: The Redis scope (use the exported `REDIS_SCOPE_DEFAULT` constant).
    -   `options`: Any other options for the `getToken()` method.

title: Connect

text:
When you have created your credential provider instance, you are ready to
connect to AMR.
The example below shows how to pass the instance as a parameter to the standard
`createCluster()` connection method.

[code example]

title: RESP2 PUB/SUB limitations

text:
If you are using the
[RESP2]()
protocol, you should
be aware that [pub/sub]() can
cause complications with reauthentication.

After a connection enters PUB/SUB mode, the socket is blocked and can't process
out-of-band commands like [`AUTH`](). This means that
connections in PUB/SUB mode can't be reauthenticated when the tokens are refreshed.
As a result, PUB/SUB connections will be evicted by the Redis proxy when their tokens expire.
You must reconnect with fresh tokens when this happens.

title: Note about transactions

text:
If you use
[transactions](https://redis.io/docs/latest/develop/clients/nodejs/transpipe)
in code that also uses token-based authentication, ensure that you use
the `node-redis` client multi/exec API rather than explicit commands to create
each transaction (see
[Execute a transaction](https://redis.io/docs/latest/develop/clients/nodejs/transpipe/#execute-a-transaction)
for an example of correct usage).
This is because the token manager might attempt to reauthenticate while your code
issues the separate transaction commands, which will interfere with the transaction.
The transaction API handles this case for you safely.

code:
npm install "@redis/client"
npm install "@redis/entraid"

section_id: install

section_id: create-a-credentials-provider-instance

code:
// Other fields...
authorityConfig: {
    type: 'multi-tenant',
    tenantId: 'your-tenant-id'
  },
// ...

section_id: provider-parameters

code:
// Other fields...
authorityConfig: {
    type: 'custom',
    authorityUrl: 'your-authority-url'
  },
// ...

code:
const provider = EntraIdCredentialsProviderFactory.createForClientCredentials({
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  authorityConfig: {
    type: 'multi-tenant',
    tenantId: 'your-tenant-id'
  },
  tokenManagerConfig: {
    expirationRefreshRatio: 0.8 // Refresh token after 80% of its lifetime
  }
});

section_id: authenticate-with-a-service-principal

code:
const provider = EntraIdCredentialsProviderFactory.createForClientCredentialsWithCertificate({
  clientId: 'your-client-id',
  clientCertificate: {
    x5c: '<certificate>',
    privateKey: '<private-key>',
    thumbprintSha256: '<certificate-SHA-256-hash>'
  },
  tokenManagerConfig: {
    expirationRefreshRatio: 0.75,
    retry: {
        maxAttempts: 3,
        initialDelayMs: 100,
        maxDelayMs: 1000,
        backoffMultiplier: 2
    }
  }
});

code:
const provider = EntraIdCredentialsProviderFactory.createForSystemAssignedManagedIdentity({
    clientId: 'your-client-id'
});

section_id: authenticate-with-a-managed-identity

code:
const provider = EntraIdCredentialsProviderFactory.createForUserAssignedManagedIdentity({
  clientId: 'your-client-id',
  userAssignedClientId: 'your-user-assigned-client-id'
});

code:
const provider = EntraIdCredentialsProviderFactory.createForAuthorizationCodeWithPKCE({
  clientId: 'your-client-id',
  redirectUri: '<uri-for-your-app>'
});

section_id: auth-code-flow-with-pkce

code:
import { DefaultAzureCredential } from '@azure/identity';
import { EntraIdCredentialsProviderFactory, REDIS_SCOPE_DEFAULT }
    from '@redis/entraid';

// ...

// Create a DefaultAzureCredential instance
const credential = new DefaultAzureCredential();

// Create a provider using DefaultAzureCredential
const provider = EntraIdCredentialsProviderFactory.createForDefaultAzureCredential({
  // Use the same parameters you would pass to credential.getToken()
  credential,
  scopes: REDIS_SCOPE_DEFAULT, // The Redis scope
  // Optional additional parameters for getToken
  options: {
    // Any options you would normally pass to credential.getToken()
  },
  tokenManagerConfig: {
    expirationRefreshRatio: 0.8
  }
});

code:
import { createCluster } from '@redis/client';
import { EntraIdCredentialsProviderFactory }
    from '@redis/entraid';

// Create credentials provider instance...
// Note: AMR databases have clustering enabled by default.
const client = createCluster({
  url: 'redis://localhost',
  credentialsProvider: provider
});

await client.connect();

const size = await client.dbSize();
console.log(`Database size is ${size}`);

section_id: connect


title: Connect to the server

summary: Connect your Node.js application to a Redis database

page_type: content

title: Basic connection

text:
Connect to localhost on port 6379.

[code example]

Store and retrieve a simple string.

[code example]

Store and retrieve a map.

[code example]

To connect to a different host or port, use a connection string in the format `redis[s]://[[username][:password]@][host][:port][/db-number]`:

[code example]
To check if the client is connected and ready to send commands, use `client.isReady`, which returns a Boolean. `client.isOpen` is also available. This returns `true` when the client's underlying socket is open, and `false` when it isn't (for example, when the client is still connecting or reconnecting after a network error).

title: Connect to a Redis cluster

role: content

text:
To connect to a Redis cluster, use `createCluster`.

[code example]

title: Connect to your production Redis with TLS

text:
When you deploy your application, use TLS and follow the [Redis security]() guidelines.

[code example]

You can also use discrete parameters and UNIX sockets. Details can be found in the [client configuration guide](https://github.com/redis/node-redis/blob/master/docs/client-configuration.md).

title: Connect using client-side caching

text:
Client-side caching is a technique to reduce network traffic between
the client and server, resulting in better performance. See
[Client-side caching introduction]()
for more information about how client-side caching works and how to use it effectively.

Client-side caching requires `node-redis` v5.1.0 or later.
To maximize compatibility with all Redis products, client-side caching
is supported by Redis v7.4 or later.

The [Redis server products]() support
[opt-in/opt-out]() mode
and [broadcasting mode]()
for CSC, but these modes are not currently implemented by `node-redis`.

To enable client-side caching, specify the
[RESP3]()
protocol and configure the cache with the `clientSideCache` parameter
when you connect. If you want `node-redis` to create the cache for you,
then you can pass a simple configuration object in `clientSideCache`, as
shown below:

[code example]

However, you can get more control over the cache very easily by creating
your own cache object and passing that as `clientSideCache` instead:

[code example]

The main advantage of using your own cache instance is that you can
use its methods to clear all entries, invalidate individual keys,
and gather useful performance statistics:

[code example]

When you have connected, the usual Redis commands will work transparently
with the cache:

[code example]

You can see the cache working if you connect to the same Redis database
with [`redis-cli`]() and run the
[`MONITOR`]() command. If you run the
code above but without passing `clientSideCache` during the connection,
you should see the following in the CLI among the output from `MONITOR`:

[code example]

The server responds to both `get("city")` calls.
If you run the code with `clientSideCache` added in again, you will see

[code example]

The first `get("city")` call contacted the server, but the second
call was satisfied by the cache.

title: Pooled caching

text:
You can also use client-side caching with client pools. Note that the same
cache is shared among all the clients in the same pool. As with the
non-pooled connection, you can let `node-redis` create the cache for you:

[code example]

If you want to access the cache, provide an instance of
`BasicPooledClientSideCache` instead of `BasicClientSideCache`:

[code example]

title: Reconnect after disconnection

text:
`node-redis` can attempt to reconnect automatically when
the connection to the server is lost. By default, it will retry
the connection using an
[exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff)
strategy with some random "jitter" added to avoid multiple
clients retrying in sync with each other.

You can also set the
`socket.reconnectionStrategy` field in the configuration to decide
whether to try to reconnect and how to approach it. Choose one of the following values for
`socket.reconnectionStrategy`:

-   `false`: Don't attempt to reconnect.
-   `number`: Wait for this number of milliseconds and then attempt to reconnect.
-   `<function>`: Use a custom
    function to decide how to handle reconnection.

The custom function has the following signature:

[code example]

It is called before each attempt to reconnect, with the `retries`
indicating how many attempts have been made so far. The `cause` parameter is an
[`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)
object with information about how the connection was lost. The return value
from the function can be any of the following:

-   `false`: Don't attempt to reconnect.
-   `number`: Wait this number of milliseconds and then try again.
-   `Error`: Same as `false`, but lets you supply extra information about why
    no attempt was made to reconnect.

The example below shows a `reconnectionStrategy` function that implements a
custom exponential backoff strategy:

[code example]

title: Connect using Smart client handoffs (SCH)

text:
*Smart client handoffs (SCH)* is a feature of Redis Cloud and
Redis Software servers that lets them actively notify clients
about planned server maintenance shortly before it happens. This
lets a client take action to avoid disruptions in service.
See [Smart client handoffs]()
for more information about SCH.

Use the configuration options shown in the example below to enable SCH
during the connection:

[code example]

SCH requires the [RESP3]()
protocol, so you must set the `RESP:3` option explicitly when you connect.

The available options are:

-   `maintNotifications`: (`string`) Whether or not to enable SCH. The options are
    -   `'disabled'`: don't use SCH
    -   `'enabled'`: attempt to activate SCH on the server and abort the connection if it isn't supported
    -   `'auto'`: attempt to activate SCH on the server and fall back to a non-SCH
        connection if it isn't supported. This is the default.
-   `maintEndpointType`: (`MovingEndpointType`) The type of endpoint to use for the connection. The options are:
    -   `'external-ip'`: use the external IP address of the server
    -   `'internal-ip'`: use the internal IP address of the server
    -   `'external-fqdn'`: use the external FQDN of the server
    -   `'internal-fqdn'`: use the internal FQDN of the server
    -   `'auto'`: auto-detect based on connection. This is the default.
    -   `'none'`: attempt a graceful reconnect to the current endpoint after a delay.
-   `maintRelaxedCommandTimeout`: (`number`) The command timeout to use while the server is
    performing maintenance. The default is 10000 (10 seconds). If a timeout happens during the maintenance period, the client receives a `CommandTimeoutDuringMaintenance` error.
-   `maintRelaxedSocketTimeout`: (`number`) The socket timeout to use while the server is
    performing maintenance. The default is 10000 (10 seconds). If a timeout happens during the maintenance period, the client receives a `SocketTimeoutDuringMaintenance` error.

 Redis Cloud supports relaxed timeouts *only* (and not pre-handoffs) for SCH if you are using
either [AWS PrivateLink]() or
[Google Cloud Private Service Connect]()
(see [Smart client handoffs]() for more information).
To use relaxed timeouts with these services, you should set `maintEndpointType: 'none'`
when you connect. All other configurations have full support for both relaxed timeouts and pre-handoffs.

title: Connection events

text:
The client object emits the following
[events](https://developer.mozilla.org/en-US/docs/Web/API/Event) that are
related to connection:

-   `connect`: (No parameters) The client is about to start connecting to the server.
-   `ready`: (No parameters) The client has connected and is ready to use.
-   `end`: (No parameters) The client has been intentionally closed using `client.quit()`.
-   `error`: An error has occurred, which is described by the
    [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)
    parameter. This is usually a network issue such as "Socket closed unexpectedly".
-   `reconnecting`: (No parameters) The client is about to try reconnecting after the
    connection was lost due to an error.
-   `sharded-channel-moved`: The cluster slot of a subscribed
    [sharded pub/sub channel]()
    has been moved to another shard. Note that when you use a
    [`RedisCluster`](#connect-to-a-redis-cluster) connection, this event is automatically
    handled for you. See
    [`sharded-channel-moved` event](https://github.com/redis/node-redis/blob/master/docs/pub-sub.md#sharded-channel-moved-event) for more information.

Use code like the following to respond to these events:

[code example]

code:
import { createClient } from 'redis';

const client = createClient();

client.on('error', err => console.log('Redis Client Error', err));

await client.connect();

section_id: basic-connection

code:
await client.set('key', 'value');
const value = await client.get('key');

code:
await client.hSet('user-session:123', {
    name: 'John',
    surname: 'Smith',
    company: 'Redis',
    age: 29
})

let userSession = await client.hGetAll('user-session:123');
console.log(JSON.stringify(userSession, null, 2));
/*
{
  "surname": "Smith",
  "name": "John",
  "company": "Redis",
  "age": "29"
}
 */

code:
createClient({
  url: 'redis://alice:foobared@awesome.redis.server:6380'
});

code:
import { createCluster } from 'redis';

const cluster = createCluster({
    rootNodes: [
        {
            url: 'redis://127.0.0.1:16379'
        },
        {
            url: 'redis://127.0.0.1:16380'
        },
        // ...
    ]
});

cluster.on('error', (err) => console.log('Redis Cluster Error', err));

await cluster.connect();

await cluster.set('foo', 'bar');
const value = await cluster.get('foo');
console.log(value); // returns 'bar'

await cluster.close();

section_id: connect-to-a-redis-cluster

code:
const client = createClient({
    username: 'default', // use your Redis user. More info https://redis.io/docs/latest/operate/oss_and_stack/management/security/acl/
    password: 'secret', // use your password here
    socket: {
        host: 'my-redis.cloud.redislabs.com',
        port: 6379,
        tls: true,
        key: readFileSync('./redis_user_private.key'),
        cert: readFileSync('./redis_user.crt'),
        ca: [readFileSync('./redis_ca.pem')]
    }
});

client.on('error', (err) => console.log('Redis Client Error', err));

await client.connect();

await client.set('foo', 'bar');
const value = await client.get('foo');
console.log(value) // returns 'bar'

await client.destroy();

code:
const client = createClient({
  RESP: 3,
  clientSideCache: {
    ttl: 0,             // Time-to-live in milliseconds (0 = no expiration)
    maxEntries: 0,      // Maximum entries to store (0 = unlimited)
    evictPolicy: "LRU"  // Eviction policy: "LRU" or "FIFO"
  }
});

section_id: connect-using-client-side-caching

code:
import { BasicClientSideCache } from 'redis';

const cache = new BasicClientSideCache({
  ttl: 0,
  maxEntries: 0,
  evictPolicy: "LRU"
});

const client = createClient({
  RESP: 3,
  clientSideCache: cache
});

code:
// Manually invalidate keys
cache.invalidate(key);

// Clear the entire cache
cache.clear();

// Get cache metrics
// `cache.stats()` returns a `CacheStats` object with comprehensive statistics.
const statistics = cache.stats();

// Key metrics:
const hits = statistics.hitCount;        // Number of cache hits
const misses = statistics.missCount;      // Number of cache misses
const hitRate = statistics.hitRate();     // Cache hit rate (0.0 to 1.0)

// Many other metrics are available on the `statistics` object, e.g.:
// statistics.missRate(), statistics.loadSuccessCount,
// statistics.averageLoadPenalty(), statistics.requestCount()

code:
client.set("city", "New York");
client.get("city");     // Retrieved from Redis server and cached
client.get("city");     // Retrieved from cache

code:
const client = createClientPool({RESP: 3}, {
  clientSideCache: {
    ttl: 0,
    maxEntries: 0,
    evictPolicy: "LRU"
  },
  minimum: 5
});

section_id: pooled-caching

code:
import { BasicPooledClientSideCache } from 'redis';

const cache = new BasicPooledClientSideCache({
  ttl: 0,
  maxEntries: 0,
  evictPolicy: "LRU"
});

const client = createClientPool({RESP: 3}, {
  clientSideCache: cache,
  minimum: 5
});

code: (retries: number, cause: Error) => false | number | Error

section_id: reconnect-after-disconnection

code:
createClient({
  socket: {
    reconnectStrategy: retries => {
        // Generate a random jitter between 0 – 100 ms:
        const jitter = Math.floor(Math.random() * 100);

        // Delay is an exponential backoff, (2^retries) * 50 ms, with a
        // maximum value of 3000 ms:
        const delay = Math.min(Math.pow(2, retries) * 50, 3000);

        return delay + jitter;
    }
  }
});

code:
const client = createClient({
  RESP: 3,
  maintNotifications: 'auto',
  maintEndpointType: 'auto',
  maintRelaxedCommandTimeout: 10000,
  maintRelaxedSocketTimeout: 10000,
  ...
});

section_id: connect-using-smart-client-handoffs-sch

code:
client.on('error', error => {
    console.error(`Redis client error:`, error);
});

section_id: connection-events


title: Error handling

summary: Learn how to handle errors when using node-redis.

title: Overview

text:
node-redis uses
[**promises**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
for error handling. Most Redis JavaScript examples
throughout the documentation mainly show the "happy path" and omit error
handling for brevity. This page shows how to apply error handling
techniques in node-redis for real world code.
For an overview of some common general error types and strategies for
handling them, see
[Error handling]().
See also [Production usage]()
for more information on connection management, timeouts, and other aspects of
app reliability.

title: Common error types

text:
node-redis throws errors as rejected promises. Common error types include:

| Error | When it occurs | Recoverable | Recommended action |
|---|---|---|---|
| `ECONNREFUSED` | Connection refused | ✅ | Retry with backoff or fall back |
| `ETIMEDOUT` | Command timeout | ✅ | Retry with backoff |
| `ECONNRESET` | Connection reset by peer | ✅ | Retry with backoff |
| `EAI_AGAIN` | DNS resolution failure | ✅ | Retry with backoff |
| `ReplyError` (`WRONGTYPE`) | Type mismatch | ❌ | Fix schema or code |
| `ReplyError` (`BUSY`, `TRYAGAIN`, `LOADING`) | Redis busy/loading | ⚠ | Retry with backoff (bounded) |

See [Categories of errors]()
for a more detailed discussion of these errors and their causes.

title: Async/await in examples

text:
The examples on this page and throughout the node-redis docs use `async/await`
style for clarity.

[code example]

Alternatively, you can use promise chains with `.then()` and `.catch()`:

[code example]

title: Error events

role: errors

text:
Node-Redis provides [multiple events to handle various scenarios](https://github.com/redis/node-redis?tab=readme-ov-file#events), among which the most critical is the `error` event.

This event is triggered whenever an error occurs within the client.

It is crucial to listen for error events.

If a client does not register at least one error listener and an error occurs, the system will throw that error, potentially causing the Node.js process to exit unexpectedly.
See [the EventEmitter docs](https://nodejs.org/api/events.html#events_error_events) for more details.

[code example]

title: Applying error handling patterns

text:
The [Error handling]()
overview describes four common error handling patterns. The sections
below show how to implement these patterns in node-redis:

title: Pattern 1: Fail fast

text:
Catch specific errors that represent unrecoverable errors and re-throw them (see
[Pattern 1: Fail fast]()
for a full description).

[code example]

title: Pattern 2: Graceful degradation

text:
Catch connection errors and fall back to an alternative (see
[Pattern 2: Graceful degradation]()
for a full description).

[code example]

title: Pattern 3: Retry with backoff

text:
Retry on temporary errors like timeouts (see
[Pattern 3: Retry with backoff]()
for a full description).

[code example]

Note that you can also configure node-redis to reconnect to the
server automatically when the connection is lost. See
[Reconnect after disconnection]()
for more information.

title: Pattern 4: Log and continue

text:
Log non-critical errors and continue (see
[Pattern 4: Log and continue]()
for a full description).

[code example]

title: See also

role: related

text:
- [Error handling]()
- [Production usage]()

code:
// Using async/await (shown in examples below)
try {
    const result = await client.get(key);
    // Handle success
} catch (error) {
    // Handle error
}

section_id: async-await-in-examples

code:
// Using promise chains (equivalent approach)
client.get(key)
    .then(result => {
        // Handle success
    })
    .catch(error => {
        // Handle error
    });

code:
const client = createClient({
  // ... client options
});
// Always ensure there's a listener for errors in the client to prevent process crashes due to unhandled errors
client.on('error', error => {
    console.error(`Redis client error:`, error);
});

section_id: error-events

code:
try {
    await client.get(key);
} catch (err) {
    if (err.name === 'ReplyError' && /WRONGTYPE|ERR /.test(err.message)) {
        throw err; // Fix code or data type
    }
    throw err;
}

code:
try {
    const val = await client.get(key);
    if (val != null) return val;
} catch (err) {
    if (['ECONNREFUSED','ECONNRESET','ETIMEDOUT','EAI_AGAIN'].includes(err.code)) {
        logger.warn('Cache unavailable; falling back to DB');
        return database.get(key);
    }
    throw err;
}
return database.get(key);

code:
async function getWithRetry(key, { attempts = 3, baseDelayMs = 100 } = {}) {
  let delay = baseDelayMs;
  for (let i = 0; i < attempts; i++) {
    try {
      return await client.get(key);
    } catch (err) {
      if (
        i < attempts - 1 &&
        (['ETIMEDOUT','ECONNRESET','EAI_AGAIN'].includes(err.code) ||
         (err.name === 'ReplyError' && /(BUSY|TRYAGAIN|LOADING)/.test(err.message)))
      ) {
        await new Promise(r => setTimeout(r, delay));
        delay *= 2;
        continue;
      }
      throw err;
    }
  }
}

code:
try {
    await client.setEx(key, 3600, value);
} catch (err) {
    if (['ECONNREFUSED','ECONNRESET','ETIMEDOUT','EAI_AGAIN'].includes(err.code)) {
        logger.warn(`Failed to cache ${key}, continuing without cache`);
    } else {
        throw err;
    }
}


title: node-redis guide (JavaScript)

summary: Connect your Node.js/JavaScript application to a Redis database

title: Connect to the server

title: Connect to Azure Managed Redis

summary: Learn how to use Redis Search with JSON and hash documents.

title: Index and query documents

summary: Learn how to index and query vector embeddings with Redis

title: Index and query vectors

summary: Index and query embeddings with Redis vector sets

title: Vector set embeddings

summary: Learn how to use Redis pipelines and transactions

title: Pipelines and transactions

summary: Learn how to use approximate calculations with Redis.

title: Probabilistic data types

title: Error handling

summary: Get your Node.js app ready for production

title: Production usage

summary: Discover the differences between `ioredis` and `node-redis`.

title: Migrate from ioredis

title: Install

text:
To install node-redis, run:

[code example]

title: Connect and test

text:
Connect to localhost on port 6379.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C, C# (Asynchronous), C# (Synchronous), Go, Java, Java (Synchronous - Jedis), JavaScript (Node.js), JavaScript (Node.js), PHP, Python, Ruby, Rust (Asynchronous), Rust (Synchronous)

**C:**

[code example]

**C# (Asynchronous):**

[code example]

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Ruby:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

Store and retrieve a simple string.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C, C# (Asynchronous), C# (Synchronous), Go, Java, Java (Synchronous - Jedis), JavaScript (Node.js), JavaScript (Node.js), PHP, Python, Ruby, Rust (Asynchronous), Rust (Synchronous)

**C:**

[code example]

**C# (Asynchronous):**

[code example]

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Ruby:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

Store and retrieve a map.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C, C# (Asynchronous), C# (Synchronous), Go, Java, Java (Synchronous - Jedis), JavaScript (Node.js), JavaScript (Node.js), PHP, Python, Ruby, Rust (Asynchronous), Rust (Synchronous)

**C:**

[code example]

**C# (Asynchronous):**

[code example]

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Ruby:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

To connect to a different host or port, use a connection string in the format `redis[s]://[[username][:password]@][host][:port][/db-number]`:

[code example]
To check if the client is connected and ready to send commands, use `client.isReady`, which returns a Boolean. `client.isOpen` is also available. This returns `true` when the client's underlying socket is open, and `false` when it isn't (for example, when the client is still connecting or reconnecting after a network error).

When you have finished using a connection, close it with `client.quit()`.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C, C# (Asynchronous), C# (Synchronous), Go, Java, Java (Synchronous - Jedis), JavaScript (Node.js), JavaScript (Node.js), PHP, Python, Ruby, Rust (Asynchronous), Rust (Synchronous)

**C:**

[code example]

**C# (Asynchronous):**

[code example]

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Ruby:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

title: More information

text:
The [`node-redis` website](https://redis.js.org/) has more examples.
The [Github repository](https://github.com/redis/node-redis) also has useful
information, including a guide to the
[connection configuration options](https://github.com/redis/node-redis/blob/master/docs/client-configuration.md) you can use.

See also the other pages in this section for more information and examples:

code: npm install redis

code:
#include <stdio.h>
#include <stdlib.h>

#include <hiredis/hiredis.h>

int main() {
    // The `redisContext` type represents the connection
    // to the Redis server. Here, we connect to the
    // default host and port.
    redisContext *c = redisConnect("127.0.0.1", 6379);

    // Check if the context is null or if a specific
    // error occurred.
    if (c == NULL || c->err) {
        if (c != NULL) {
            printf("Error: %s\n", c->errstr);
            // handle error
        } else {
            printf("Can't allocate redis context\n");
        }

        exit(1);
    }

    // Set a string key.
    redisReply *reply = redisCommand(c, "SET foo bar");
    printf("Reply: %s\n", reply->str); // >>> Reply: OK
    freeReplyObject(reply);

    // Get the key we have just stored.
    reply = redisCommand(c, "GET foo");
    printf("Reply: %s\n", reply->str); // >>> Reply: bar
    freeReplyObject(reply);

    // Close the connection.
    redisFree(c);
}

section_id: connect-and-test

code:
rdb := redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "", // no password
		DB:       0,  // use default DB
		Protocol: 2,
	})

	ctx := context.Background()

code: RedisClient jedis = new RedisClient("redis://localhost:6379");

code:
import { createClient } from 'redis';

const client = createClient();

client.on('error', err => console.log('Redis Client Error', err));

await client.connect();

code:
import { Redis } from 'ioredis';

const redis = new Redis();

code: r = Redis.new

code:
err := rdb.Set(ctx, "foo", "bar", 0).Err()
	if err != nil {
		panic(err)
	}

	val, err := rdb.Get(ctx, "foo").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("foo", val) // >>> foo bar

code:
String res1 = jedis.set("bike:1", "Deimos");
        System.out.println(res1); // >>> OK

        String res2 = jedis.get("bike:1");
        System.out.println(res2); // >>> Deimos

code:
await client.set('key', 'value');
const value = await client.get('key');
console.log(value); // >>> value

code:
await redis.set('key', 'value');
const value = await redis.get('key');
console.log(value); // >>> value

code:
hashFields := []string{
		"model", "Deimos",
		"brand", "Ergonom",
		"type", "Enduro bikes",
		"price", "4972",
	}

	res1, err := rdb.HSet(ctx, "bike:1", hashFields).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res1) // >>> 4

	res2, err := rdb.HGet(ctx, "bike:1", "model").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res2) // >>> Deimos

	res3, err := rdb.HGet(ctx, "bike:1", "price").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res3) // >>> 4972

	res4, err := rdb.HGetAll(ctx, "bike:1").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res4)
	// >>> map[brand:Ergonom model:Deimos price:4972 type:Enduro bikes]

code:
import io.lettuce.core.*;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;

public class ConnectBasicTest {

    public void connectBasic() {
        RedisURI uri = RedisURI.Builder
                .redis("localhost", 6379)
                .build();

        RedisClient client = RedisClient.create(uri);
        StatefulRedisConnection<String, String> connection = client.connect();
        RedisCommands<String, String> commands = connection.sync();

        commands.set("foo", "bar");
        String result = commands.get("foo");
        System.out.println(result); // >>> bar

        connection.close();
        client.shutdown();
    }
}

code:
Map<String, String> hash = new HashMap<>();
        hash.put("name", "John");
        hash.put("surname", "Smith");
        hash.put("company", "Redis");
        hash.put("age", "29");

        Long res3 = jedis.hset("user-session:123", hash);
        System.out.println(res3); // >>> 4

        Map<String, String> res4 = jedis.hgetAll("user-session:123");
        System.out.println(res4);
        // >>> {name=John, surname=Smith, company=Redis, age=29}

code:
await client.hSet('user-session:123', {
    name: 'John',
    surname: 'Smith',
    company: 'Redis',
    age: 29
})

let userSession = await client.hGetAll('user-session:123');
console.log(JSON.stringify(userSession, null, 2));
/* >>>
{
  "surname": "Smith",
  "name": "John",
  "company": "Redis",
  "age": "29"
}
 */

code:
await redis.hset('user-session:123', {
    name: 'John',
    surname: 'Smith',
    company: 'Redis',
    age: 29
});

const userSession = await redis.hgetall('user-session:123');
console.log(JSON.stringify(userSession, null, 2));
/* >>>
{
  "surname": "Smith",
  "name": "John",
  "company": "Redis",
  "age": "29"
}
 */

code:
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)

r.set('foo', 'bar')
# True
r.get('foo')
# bar

r.hset('user-session:123', mapping={
    'name': 'John',
    "surname": 'Smith',
    "company": 'Redis',
    "age": 29
})
# True

r.hgetall('user-session:123')
# {'surname': 'Smith', 'name': 'John', 'company': 'Redis', 'age': '29'}

r.close()

code:
require 'redis'

r = Redis.new

r.set 'foo', 'bar'
value = r.get('foo')
puts value # >>> bar

r.hset 'user-session:123', 'name', 'John'
r.hset 'user-session:123', 'surname', 'Smith'
r.hset 'user-session:123', 'company', 'Redis'
r.hset 'user-session:123', 'age', 29

hash_value = r.hgetall('user-session:123')
puts hash_value
# >>> {"name"=>"John", "surname"=>"Smith", "company"=>"Redis", "age"=>"29"}

r.close()

code:
createClient({
  url: 'redis://alice:foobared@awesome.redis.server:6380'
});

code:
using NRedisStack;
using NRedisStack.RedisStackCommands;
using StackExchange.Redis;

public class AsyncLandingExample
{
    public async Task Run()
    {
        var muxer = await ConnectionMultiplexer.ConnectAsync("localhost:6379");
        var db = muxer.GetDatabase();

        await db.StringSetAsync("foo", "bar");
        string? fooResult = await db.StringGetAsync("foo");
        Console.WriteLine(fooResult); // >>> bar

        var hash = new HashEntry[] {
            new HashEntry("name", "John"),
            new HashEntry("surname", "Smith"),
            new HashEntry("company", "Redis"),
            new HashEntry("age", "29"),
            };
        await db.HashSetAsync("user-session:123", hash);

        var hashFields = await db.HashGetAllAsync("user-session:123");
        Console.WriteLine(String.Join("; ", hashFields));
        // >>> name: John; surname: Smith; company: Redis; age: 29
    }
}

code:
using NRedisStack;
using NRedisStack.RedisStackCommands;
using StackExchange.Redis;

public class SyncLandingExample
{
    public void Run()
    {
        var muxer = ConnectionMultiplexer.Connect("localhost:6379");
        var db = muxer.GetDatabase();

        db.StringSet("foo", "bar");
        Console.WriteLine(db.StringGet("foo")); // >>> bar

        var hash = new HashEntry[] {
            new HashEntry("name", "John"),
            new HashEntry("surname", "Smith"),
            new HashEntry("company", "Redis"),
            new HashEntry("age", "29"),
        };
        db.HashSet("user-session:123", hash);

        var hashFields = db.HashGetAll("user-session:123");
        Console.WriteLine(String.Join("; ", hashFields));
        // >>> name: John; surname: Smith; company: Redis; age: 29
    }
}

code:
connection.close();
        client.shutdown();

code: await client.quit();

code:
<?php

require 'vendor/autoload.php';

use Predis\Client as PredisClient;

$r = new PredisClient([
                'scheme'   => 'tcp',
                'host'     => '127.0.0.1',
                'port'     => 6379,
                'password' => '',
                'database' => 0,
            ]);

echo $r->set('foo', 'bar'), PHP_EOL;
// >>> OK

echo $r->get('foo'), PHP_EOL;
// >>> bar

$r->hset('user-session:123', 'name', 'John');
$r->hset('user-session:123', 'surname', 'Smith');
$r->hset('user-session:123', 'company', 'Redis');
$r->hset('user-session:123', 'age', 29);

echo var_export($r->hgetall('user-session:123')), PHP_EOL;
/* >>>
array (
  'name' => 'John',
  'surname' => 'Smith',
  'company' => 'Redis',
  'age' => '29',
)
*/

code:
mod tests {
    use redis::AsyncCommands;

    async fn run() {
        let mut r = match redis::Client::open("redis://127.0.0.1") {
            Ok(client) => {
                match client.get_multiplexed_async_connection().await {
                    Ok(conn) => conn,
                    Err(e) => {
                        println!("Failed to connect to Redis: {e}");
                        return;
                    }
                }
            },
            Err(e) => {
                println!("Failed to create Redis client: {e}");
                return;
            }
        };

        if let Ok(res) = r.set("foo", "bar").await {
            let res: String = res;
            println!("{res}"); // >>> OK
        } else {
            println!("Error setting foo");
        }

        match r.get("foo").await {
            Ok(res) => {
                let res: String = res;
                println!("{res}"); // >>> bar
            },
            Err(e) => {
                println!("Error getting foo: {e}");
                return;
            }
        };

        let hash_fields = [
            ("model", "Deimos"),
            ("brand", "Ergonom"),
            ("type", "Enduro bikes"),
            ("price", "4972"),
        ];

        if let Ok(res) = r.hset_multiple("bike:1", &hash_fields).await {
            let res: String = res;
            println!("{res}"); // >>> OK
        } else {
            println!("Error setting bike:1");
        }

        match r.hget("bike:1", "model").await {
            Ok(res) => {
                let res: String = res;
                println!("{res}"); // >>> Deimos
            },
            Err(e) => {
                println!("Error getting bike:1 model: {e}");
                return;
            }
        }

        match r.hget("bike:1", "price").await {
            Ok(res) => {
                let res: String = res;
                println!("{res}"); // >>> 4972
            },
            Err(e) => {
                println!("Error getting bike:1 price: {e}");
                return;
            }
        }

        match r.hgetall("bike:1").await {
            Ok(res) => {
                let res: Vec<(String, String)> = res;

                for (key, value) in res {
                    println!("{key}: {value}");
                }
                // >>> model: Deimos
                // >>> brand: Ergonom
                // >>> type: Enduro bikes
                // >>> price: 4972
            },
            Err(e) => {
                println!("Error getting bike:1: {e}");
                return;
            }
        }
    }
}

code:
mod landing_tests {
    use redis::Commands;

    fn run() {
        let mut r = match redis::Client::open("redis://127.0.0.1") {
            Ok(client) => {
                match client.get_connection() {
                    Ok(conn) => conn,
                    Err(e) => {
                        println!("Failed to connect to Redis: {e}");
                        return;
                    }
                }
            },
            Err(e) => {
                println!("Failed to create Redis client: {e}");
                return;
            }
        };

        if let Ok(res) = r.set("foo", "bar") {
            let res: String = res;
            println!("{res}");    // >>> OK
        } else {
            println!("Error setting foo");
        }

        match r.get("foo") {
            Ok(res) => {
                let res: String = res;
                println!("{res}");   // >>> bar
            },
            Err(e) => {
                println!("Error getting foo: {e}");
                return;
            }
        };

        let hash_fields = [
            ("model", "Deimos"),
            ("brand", "Ergonom"),
            ("type", "Enduro bikes"),
            ("price", "4972"),
        ];

        if let Ok(res) = r.hset_multiple("bike:1", &hash_fields) {
            let res: String = res;
            println!("{res}"); // >>> OK
        } else {
            println!("Error setting bike:1");
        }

        match r.hget("bike:1", "model") {
            Ok(res) => {
                let res: String = res;
                println!("{res}"); // >>> Deimos
            },
            Err(e) => {
                println!("Error getting bike:1 model: {e}");
                return;
            }
        }

        match r.hget("bike:1", "price") {
            Ok(res) => {
                let res: String = res;
                println!("{res}"); // >>> 4972
            },
            Err(e) => {
                println!("Error getting bike:1 price: {e}");
                return;
            }
        }

        match r.hgetall("bike:1") {
            Ok(res) => {
                let res: Vec<(String, String)> = res;

                for (key, value) in res {
                    println!("{key}: {value}");
                }
                // >>> model: Deimos
                // >>> brand: Ergonom
                // >>> type: Enduro bikes
                // >>> price: 4972
            },
            Err(e) => {
                println!("Error getting bike:1: {e}");
                return;
            }
        }
    }
}


title: Migrate from ioredis

title: Overview

text:
Redis previously recommended the [`ioredis`]()
client library for development with [Node.js](https://nodejs.org/en),
but this library is now deprecated in favor of
[`node-redis`](). This guide
outlines the main similarities and differences between the two libraries.
You may find this information useful if you are an `ioredis` user and you want to
start a new Node.js project or migrate an existing `ioredis` project to `node-redis`.

title: Comparison of `ioredis` and `node-redis`

text:
The tables below summarize how `ioredis` and `node-redis` implement some
key features of Redis. See the following sections for more information about
each feature.

title: Connection

text:
| Feature | `ioredis` | `node-redis` |
| :-- | :-- | :-- |
| [Initial connection](#initial-connection) | Happens when you create a client instance | Requires you to call a method on the client instance |
| [Reconnection after a connection is lost](#reconnection) | Automatic by default | Automatic by default and configurable |
| [Connection events](#connection-events) | Emits `connect`, `ready`, `error`, and `close` events | Emits `connect`, `ready`, `error`, `end`, and `reconnecting` events |

title: Command handling

role: syntax

text:
| Feature | `ioredis` | `node-redis` |
| :-- | :-- | :-- |
| [Command case](#command-case) | Lowercase only (eg, `hset`) | Uppercase or camel case (eg, `HSET` or `hSet`) |
| [Command argument handling](#command-argument-handling) | Argument objects flattened and items passed directly | Argument objects parsed to generate correct argument list |
| [Asynchronous command result handling](#async-result) | Callbacks and Promises | Promises (but supports callbacks via Legacy Mode) |
| [Arbitrary command execution](#arbitrary-command-execution) | Uses the `call()` method | Uses the `sendCommand()` method |

title: Techniques

text:
| Feature | `ioredis` | `node-redis` |
| :-- | :-- | :-- |
| [Pipelining](#pipelining) | Automatic, or with `pipeline()` command | Automatic, or with `multi()` command |
| [Scan iteration](#scan-iteration) | Uses `scanStream()`, etc | Uses `scanIterator()`, etc |
| [Subscribing to channels](#subscribing-to-channels) | Uses `client.on('message', ...)` event | Uses `subscribe(...)` command |

title: Specific commands

text:
| Command | `ioredis` | `node-redis` |
| :-- | :-- | :-- |
| [`SETNX`](#setnx-command) | Supported explicitly | Supported as an option for `SET` |
| [`HMSET`](#hmset-command) | Supported explicitly | Supported with standard `HSET` functionality |
| [`CONFIG`](#config-command) | Supported explicitly | Supported with separate `configGet()`, `configSet()`, etc |co

title: Details

text:
The sections below explain the points of comparison between `ioredis` and
`node-redis` in more detail.

title: Initial connection

text:
`ioredis` makes the connection to the Redis server when you create an instance
of the client object:

[code example]

`node-redis` requires you to call the `connect()` method on the client object
to make the connection:

[code example]

title: Reconnection after a connection is lost {#reconnection}

text:
Both `ioredis` and `node-redis` automatically attempt to reconnect if the connection
was lost due to an error. `node-redis` also lets you add a custom reconnection strategy
when you create the client object. See
[Reconnect after disconnection]()
for more information.

title: Connection events

text:
The `connect`, `ready`, `error`, and `close` events that `ioredis` emits
are equivalent to the `connect`, `ready`, `error`, and `end` events
in `node-redis`, but `node-redis` also emits a `reconnecting` event.
See [Connection events]()
for more information.

title: Command case

text:
Command methods in `ioredis` are always lowercase. With `node-redis`, you can
use uppercase or camel case versions of the method names.

[code example]

title: Command argument handling

text:
`ioredis` parses command arguments to strings and then passes them to
the server, in a similar way to [`redis-cli`]().

[code example]

Arrays passed as arguments are flattened into individual elements and
objects are flattened into sequential key-value pairs:

[code example]

`node-redis` uses predefined formats for command arguments. These include specific
classes for commmand options that generally don't correspond to the syntax
of the CLI command. Internally, `node-redis` constructs the correct command using
the method arguments you pass:

[code example]

title: Asynchronous command result handling {#async-result}

text:
All commands for both `ioredis` and `node-redis` are executed
asynchronously. `ioredis` supports both callbacks and
[`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
return values to respond to command results:

[code example]

`node-redis` supports only `Promise` objects for results, so
you must always use a `then()` handler or the
[`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await)
operator to receive them. However, you can still use callbacks with the legacy mode if you need them:

[code example]

title: Arbitrary command execution

text:
`ioredis` lets you issue arbitrary commands in a similar format to
[`redis-cli`]() using the `call()`
command:

[code example]

In `node-redis`, you can get the same effect outside a transaction using `sendCommand()`:

[code example]

Within a transaction, use `addCommand()` to include arbitrary commands. Note that
you can freely mix `addCommand()` calls with standard commands in the same
transaction:

[code example]

title: Pipelining

text:
Both `ioredis` and `node-redis` will pipeline commands automatically if
they are executed in the same "tick" of the
[event loop](https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick#what-is-the-event-loop)
(see
[Execute a pipeline]()
for more information).

You can also create a pipeline with explicit commands in both clients.
With `ioredis`, you use the `pipeline()` command with a chain of
commands, ending with `exec()` to run the pipeline:

[code example]

For `node-redis`, the approach is similar, except that you call the `multi()`
command to start the pipeline and `execAsPipeline()` to run it:

[code example]

title: Scan iteration

text:
`ioredis` supports the `scanStream()` method to create a readable stream
from the set of keys returned by the [`SCAN`]()
command:

[code example]

You can also use the similar `hscanStream()`, `sscanStream()`, and
`zscanStream()` to iterate over the items of a hash, set, or sorted set,
respectively.

`node-redis` handles scan iteration using the `scanIterator()` method
(and the corresponding `hscanIterator()`, `sscanIterator()`, and
`zscanIterator()` methods). These return a collection object for
each page scanned by the cursor (this can be helpful to improve
efficiency using [`MGET`]() and
other multi-key commands):

[code example]

title: Subscribing to channels

text:
`ioredis` reports incoming pub/sub messages with a `message`
event on the client object (see
[Publish/subscribe]() for more
information about messages):

[code example]

With `node-redis`, you use the `subscribe()` command to register the
message callback. Also, when you use a connection to subscribe, that
connection can't issue any other commands, so you must create a
dedicated connection for the subscription. Use the `client.duplicate()`
method to create a new connection with the same settings as the original:

[code example]

title: `SETNX` command

text:
`ioredis` implements the  [`SETNX`]()
command with an explicit method:

[code example]

`node-redis` provides a `SETNX` method but this command is deprecated. Use the `NX` option to the [`SET`]()
command to get the same functionality as `SETNX`:

[code example]

title: `HMSET` command

text:
The [`HMSET`]() command has been deprecated
since Redis v4.0.0, but it is still supported by `ioredis`. With `node-redis`
you should use the [`HSET`]() command with
multiple key-value pairs. See the [`HSET`]()
command page for more information.

title: `CONFIG` command

text:
`ioredis` supports a `config()` method to set or get server configuration
options:

[code example]

`node-redis` doesn't have a `config()` method, but instead supports the
standard commands [`configSet()`](),
[`configGet()`](),
[`configResetStat()`](), and
[`configRewrite`]():

[code example]

code:
const client = require('ioredis');

// Connects to localhost:6379 on instantiation.
const client = new Redis();

section_id: initial-connection

code:
import { createClient } from 'redis';

const client = createClient();
await client.connect(); // Requires explicit connection.

code:
// ioredis
client.hset('key', 'field', 'value');

// node-redis
client.HSET('key', 'field', 'value');

// ...or
client.hSet('key', 'field', 'value');

section_id: command-case

code:
// Equivalent to the command line `SET key 100 EX 10`.
client.set('key', 100, 'EX', 10);

section_id: command-argument-handling

code:
// These commands are all equivalent.
client.hset('user', {
    name: 'Bob',
    age: 20,
    description: 'I am a programmer',
});

client.hset('user', ['name', 'Bob', 'age', 20, 'description', 'I am a programmer']);

client.hset('user', 'name', 'Bob', 'age', 20, 'description', 'I am a programmer');

code:
// Equivalent to the command line `SET bike:5 bike EX 10`.
client.set('bike:5', 'bike', {EX: 10});

code:
// Callback
client.get('mykey', (err, result) => {
  if (err) {
    console.error(err);
  } else {
    console.log(result);
  }
});

// Promise
client.get('mykey').then(
    (result) => {
        console.log(result);
    },
    (err) => {
        console.error(err);
    }
);

code:
// Promise
await client.set('mykey', 'myvalue');

// Callback
const legacyClient = client.legacy();
legacyClient.set("mykey", "myvalue", (err, result) => {
  if (err) {
    console.error(err);
  } else {
    console.log(result);
  }
});

section_id: arbitrary-command-execution

code: await client.sendCommand(['hset', 'hash2', 'number', '3']);

code:
const responses = await client.multi()
  .addCommand(['hset', 'hash3', 'number', '4'])
  .hGet('hash3', 'number')
  .exec();

code:
// ioredis example
client.pipeline()
    .set('foo', '1')
    .get('foo')
    .set('foo', '2')
    .incr('foo')
    .get('foo')
    .exec(function (err, results) {
      // Handle results or errors.
    });

section_id: pipelining

code:
client.multi()
    .set('seat:3', '#3')
    .set('seat:4', '#4')
    .set('seat:5', '#5')
    .execAsPipeline()
    .then((results) => {
        // Handle array of results.
    },
    (err) => {
        // Handle errors.
    });

code:
const client = new Redis();
// Create a readable stream (object mode)
const stream = client.scanStream();
stream.on('data', (resultKeys) => {
  // `resultKeys` is an array of strings representing key names.
  // Note that resultKeys may contain 0 keys, and that it will sometimes
  // contain duplicates due to SCAN's implementation in Redis.
  for (let i = 0; i < resultKeys.length; i++) {
    console.log(resultKeys[i]);
  }
});
stream.on('end', () => {
  console.log('all keys have been visited');
});

section_id: scan-iteration

code:
for await (const keys of client.scanIterator()) {
  const values = await client.mGet(keys);
  // Process values...
}

code:
client.on('message', (channel, message) => {
   console.log(Received message from ${channel}: ${message});
});

section_id: subscribing-to-channels

code:
const subscriber = client.duplicate();
await subscriber.connect();

await subscriber.subscribe('channel', (message) => {
   console.log(Received message: ${message});
});

code: client.setnx('bike:1', 'bike');

section_id: setnx-command

code: await client.set('bike:1', 'bike', {'NX': true});

section_id: config-command


title: Probabilistic data types

title: Overview

text:
Redis supports several
[probabilistic data types]()
that let you calculate values approximately rather than exactly.
The types fall into two basic categories:

-   [Set operations](#set-operations): These types let you calculate (approximately)
    the number of items in a set of distinct values, and whether or not a given value is
    a member of a set.
-   [Statistics](#statistics): These types give you an approximation of
    statistics such as the quantiles, ranks, and frequencies of numeric data points in
    a list.

To see why these approximate calculations would be useful, consider the task of
counting the number of distinct IP addresses that access a website in one day.

Assuming that you already have code that supplies you with each IP
address as a string, you could record the addresses in Redis using
a [set]():

[code example]

The set can only contain each key once, so if the same address
appears again during the day, the new instance will not change
the set. At the end of the day, you could get the exact number of
distinct addresses using the `sCard()` function:

[code example]

This approach is simple, effective, and precise but if your website
is very busy, the `ip_tracker` set could become very large and consume
a lot of memory.

You would probably round the count of distinct IP addresses to the
nearest thousand or more to deliver the usage statistics, so
getting it exactly right is not important. It would be useful
if you could trade off some accuracy in exchange for lower memory
consumption. The probabilistic data types provide exactly this kind of
trade-off. Specifically, you can count the approximate number of items in a
set using the [HyperLogLog](#set-cardinality) data type, as described below.

In general, the probabilistic data types let you perform approximations with a
bounded degree of error that have much lower memory consumption or execution
time than the equivalent precise calculations.

title: Set operations

text:
Redis supports the following approximate set operations:

-   [Membership](#set-membership): The
    [Bloom filter]() and
    [Cuckoo filter]()
    data types let you track whether or not a given item is a member of a set.
-   [Cardinality](#set-cardinality): The
    [HyperLogLog]()
    data type gives you an approximate value for the number of items in a set, also
    known as the *cardinality* of the set.

The sections below describe these operations in more detail.

title: Set membership

text:
[Bloom filter]() and
[Cuckoo filter]()
objects provide a set membership operation that lets you track whether or not a
particular item has been added to a set. These two types provide different
trade-offs for memory usage and speed, so you can select the best one for your
use case. Note that for both types, there is an asymmetry between presence and
absence of items in the set. If an item is reported as absent, then it is definitely
absent, but if it is reported as present, then there is a small chance it may really be
absent.

Instead of storing strings directly, like a [set](),
a Bloom filter records the presence or absence of the
[hash value](https://en.wikipedia.org/wiki/Hash_function) of a string.
This gives a very compact representation of the
set's membership with a fixed memory size, regardless of how many items you
add. The following example adds some names to a Bloom filter representing
a list of users and checks for the presence or absence of users in the list.
Note that you must use the `bf` property to access the Bloom filter commands.

[code example]

A Cuckoo filter has similar features to a Bloom filter, but also supports
a deletion operation to remove hashes from a set, as shown in the example
below. Note that you must use the `cf` property to access the Cuckoo filter
commands.

[code example]

Which of these two data types you choose depends on your use case.
Bloom filters are generally faster than Cuckoo filters when adding new items,
and also have better memory usage. Cuckoo filters are generally faster
at checking membership and also support the delete operation. See the
[Bloom filter]() and
[Cuckoo filter]()
reference pages for more information and comparison between the two types.

title: Set cardinality

text:
A [HyperLogLog]()
object calculates the cardinality of a set. As you add
items, the HyperLogLog tracks the number of distinct set members but
doesn't let you retrieve them or query which items have been added.
You can also merge two or more HyperLogLogs to find the cardinality of the
[union](https://en.wikipedia.org/wiki/Union_(set_theory)) of the sets they
represent.

[code example]

The main benefit that HyperLogLogs offer is their very low
memory usage. They can count up to 2^64 items with less than
1% standard error using a maximum 12KB of memory. This makes
them very useful for counting things like the total of distinct
IP addresses that access a website or the total of distinct
bank card numbers that make purchases within a day.

title: Statistics

text:
Redis supports several approximate statistical calculations
on numeric data sets:

-   [Frequency](#frequency): The
    [Count-min sketch]()
    data type lets you find the approximate frequency of a labeled item in a data stream.
-   [Quantiles](#quantiles): The
    [t-digest]()
    data type estimates the quantile of a query value in a data stream.
-   [Ranking](#ranking): The
    [Top-K]() data type
    estimates the ranking of labeled items by frequency in a data stream.

The sections below describe these operations in more detail.

title: Frequency

text:
A [Count-min sketch]()
(CMS) object keeps count of a set of related items represented by
string labels. The count is approximate, but you can specify
how close you want to keep the count to the true value (as a fraction)
and the acceptable probability of failing to keep it in this
desired range. For example, you can request that the count should
stay within 0.1% of the true value and have a 0.05% probability
of going outside this limit. The example below shows how to create
a Count-min sketch object, add data to it, and then query it.
Note that you must use the `cms` property to access the Count-min
sketch commands.

[code example]

The advantage of using a CMS over keeping an exact count with a
[sorted set]()
is that that a CMS has very low and fixed memory usage, even for
large numbers of items. Use CMS objects to keep daily counts of
items sold, accesses to individual web pages on your site, and
other similar statistics.

title: Quantiles

text:
A [quantile](https://en.wikipedia.org/wiki/Quantile) is the value
below which a certain fraction of samples lie. For example, with
a set of measurements of people's heights, the quantile of 0.75 is
the value of height below which 75% of all people's heights lie.
[Percentiles](https://en.wikipedia.org/wiki/Percentile) are equivalent
to quantiles, except that the fraction is expressed as a percentage.

A [t-digest]()
object can estimate quantiles from a set of values added to it
without having to store each value in the set explicitly. This can
save a lot of memory when you have a large number of samples.

The example below shows how to add data samples to a t-digest
object and obtain some basic statistics, such as the minimum and
maximum values, the quantile of 0.75, and the
[cumulative distribution function](https://en.wikipedia.org/wiki/Cumulative_distribution_function)
(CDF), which is effectively the inverse of the quantile function. It also
shows how to merge two or more t-digest objects to query the combined
data set. Note that you must use the `tDigest` property to access the
t-digest commands.

[code example]

A t-digest object also supports several other related commands, such
as querying by rank. See the
[t-digest]()
reference for more information.

title: Ranking

text:
A [Top-K]()
object estimates the rankings of different labeled items in a data
stream according to frequency. For example, you could use this to
track the top ten most frequently-accessed pages on a website, or the
top five most popular items sold.

The example below adds several different items to a Top-K object
that tracks the top three items (this is the second parameter to
the `topK.reserve()` method). It also shows how to list the
top *k* items and query whether or not a given item is in the
list. Note that you must use the `topK` property to access the
Top-K commands.

[code example]

section_id: overview

code: const num_distinct_ips = await client.sCard("ip_tracker");

code:
const res1 = await client.bf.mAdd(
    "recorded_users",
    ["andy", "cameron", "david", "michelle"]
);
console.log(res1);  // >>> [true, true, true, true]

const res2 = await client.bf.exists("recorded_users", "cameron");
console.log(res2);  // >>> true

const res3 = await client.bf.exists("recorded_users", "kaitlyn");
console.log(res3);  // >>> false

section_id: set-membership

code:
const res4 = await client.cf.add("other_users", "paolo");
console.log(res4);  // >>> true

const res5 = await client.cf.add("other_users", "kaitlyn");
console.log(res5);  // >>> true

const res6 = await client.cf.add("other_users", "rachel");
console.log(res6);  // >>> true

const res7 = await client.cf.exists("other_users", "paolo");
const res7a = await client.cf.exists("other_users", "kaitlyn");
const res7b = await client.cf.exists("other_users", "rachel");
const res7c = await client.cf.exists("other_users", "andy");
console.log([res7, res7a, res7b, res7c]);  // >>> [true, true, true, false]

const res8 = await client.cf.del("other_users", "paolo");
console.log(res8);  // >>> true

const res9 = await client.cf.exists("other_users", "paolo");
console.log(res9);  // >>> false

code:
const res10 = await client.pfAdd("group:1", ["andy", "cameron", "david"]);
console.log(res10);  // >>> true

const res11 = await client.pfCount("group:1");
console.log(res11);  // >>> 3

const res12 = await client.pfAdd("group:2", ["kaitlyn", "michelle", "paolo", "rachel"]);
console.log(res12);  // >>> true

const res13 = await client.pfCount("group:2");
console.log(res13);  // >>> 4

const res14 = await client.pfMerge("both_groups", ["group:1", "group:2"]);
console.log(res14);  // >>> OK

const res15 = await client.pfCount("both_groups");
console.log(res15);  // >>> 7

section_id: set-cardinality

code:
// Specify that you want to keep the counts within 0.01
// (1%) of the true value with a 0.005 (0.5%) chance
// of going outside this limit.
const res16 = await client.cms.initByProb("items_sold", 0.01, 0.005);
console.log(res16);  // >>> OK

// The parameters for `incrBy()` are passed as an array of objects
// each containing an `item` and `incrementBy` property.
const res17 = await client.cms.incrBy(
    "items_sold",
    [
        { item: "bread", incrementBy: 300},
        { item: "tea", incrementBy: 200},
        { item: "coffee", incrementBy: 200},
        { item: "beer", incrementBy: 100}
    ]
);
console.log(res17);  // >>> [300, 200, 200, 100]

const res18 = await client.cms.incrBy(
    "items_sold",
    [
        { item: "bread", incrementBy: 100},
        { item: "coffee", incrementBy: 150}
    ]
);
console.log(res18);  // >>> [400, 350]

const res19 = await client.cms.query(
    "items_sold",
    ["bread", "tea", "coffee", "beer"]
);
console.log(res19);  // >>> [400, 200, 350, 100]

section_id: frequency

code:
const res20 = await client.tDigest.create("male_heights");
console.log(res20);  // >>> OK

const res21 = await client.tDigest.add(
    "male_heights",
    [175.5, 181, 160.8, 152, 177, 196, 164]
);
console.log(res21);  // >>> OK

const res22 = await client.tDigest.min("male_heights");
console.log(res22);  // >>> 152

const res23 = await client.tDigest.max("male_heights");
console.log(res23);  // >>> 196

const res24 = await client.tDigest.quantile("male_heights", [0.75]);
console.log(res24);  // >>> [181]

// Note that the CDF value for 181 is not exactly
// 0.75. Both values are estimates.
const res25 = await client.tDigest.cdf("male_heights", [181]);
console.log(res25);  // >>> [0.7857142857142857]

const res26 = await client.tDigest.create("female_heights");
console.log(res26);  // >>> OK

const res27 = await client.tDigest.add(
    "female_heights",
    [155.5, 161, 168.5, 170, 157.5, 163, 171]
);
console.log(res27);  // >>> OK

const res28 = await client.tDigest.quantile("female_heights", [0.75]);
console.log(res28);  // >>> [170]

const res29 = await client.tDigest.merge(
    "all_heights", ["male_heights", "female_heights"]
);
console.log(res29);  // >>> OK

const res30 = await client.tDigest.quantile("all_heights", [0.75]);
console.log(res30);  // >>> [175.5]

section_id: quantiles

code:
// The `reserve()` method creates the Top-K object with
// the given key. The parameters are the number of items
// in the ranking and values for `width`, `depth`, and
// `decay`, described in the Top-K reference page.
const res31 = await client.topK.reserve(
    "top_3_songs", 3,
    { width: 7, depth: 8, decay: 0.9 }
);
console.log(res31);  // >>> OK

// The parameters for `incrBy()` are passed as an array of objects
// each containing an `item` and `incrementBy` property.
const res32 = await client.topK.incrBy(
    "top_3_songs",
    [
        { item: "Starfish Trooper", incrementBy: 3000},
        { item: "Only one more time", incrementBy: 1850},
        { item: "Rock me, Handel", incrementBy: 1325},
        { item: "How will anyone know?", incrementBy: 3890},
        { item: "Average lover", incrementBy: 4098},
        { item: "Road to everywhere", incrementBy: 770}
    ]
);
console.log(res32);
// >>> [null, null, null, 'Rock me, Handel', 'Only one more time', null]

const res33 = await client.topK.list("top_3_songs");
console.log(res33);
// >>> ['Average lover', 'How will anyone know?', 'Starfish Trooper']

const res34 = await client.topK.query(
    "top_3_songs", ["Starfish Trooper", "Road to everywhere"]
);
console.log(res34);  // >>> [true, false]

section_id: ranking


title: Production usage

title: Overview

text:
This guide offers recommendations to get the best reliability and
performance in your production environment.

title: Checklist

text:
Each item in the checklist below links to the section
for a recommendation. Use the checklist icons to record your
progress in implementing the recommendations.

[code example]

title: Recommendations

title: Handling errors

text:
Node-Redis provides [multiple events to handle various scenarios](https://github.com/redis/node-redis?tab=readme-ov-file#events), among which the most critical is the `error` event.

This event is triggered whenever an error occurs within the client, and
it is very important to set a handler to listen for it.
See [Error events]()
for more information and an example of setting an error handler.

title: Handling reconnections

text:
When the socket closes unexpectedly (without calling the `quit()` or `disconnect()` methods),
the client can automatically restore the connection.  A simple
[exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) strategy
for reconnection is enabled by default, but you can replace this with your
own custom strategy. See
[Reconnect after disconnection]()
for more information.

title: Timeouts

text:
To set a timeout for a connection, use the `connectTimeout` option
(the default timeout is 5 seconds):

[code example]

You can also set timeouts for individual commands using `AbortController`:

[code example]

title: Command execution reliability

text:
By default, `node-redis` reconnects automatically when the connection is lost
(but see [Handling reconnections](#handling-reconnections), if you want to
customize this behavior). While the connection is down, any commands that you
execute will be queued and sent to the server when the connection is restored.
This might occasionally cause problems if the connection fails while a
[non-idempotent](https://en.wikipedia.org/wiki/Idempotence) command
is being executed. In this case, the command could change the data on the server
without the client removing it from the queue. When the connection is restored,
the command will be sent again, resulting in incorrect data.

If you need to avoid this situation, set the `disableOfflineQueue` option
to `true` when you create the client. This will cause the client to discard
unexecuted commands rather than queuing them:

[code example]

Use a separate connection with the queue disabled if you want to avoid queuing
only for specific commands.

title: Smart client handoffs

text:
*Smart client handoffs (SCH)* is a feature of Redis Cloud and
Redis Software servers that lets them actively notify clients
about planned server maintenance shortly before it happens. This
lets a client take action to avoid disruptions in service.

See [Smart client handoffs]()
for more information about SCH and
[Connect using Smart client handoffs]()
for example code.

code:
- [ ] [Handling errors](#handling-errors)
- [ ] [Handling reconnections](#handling-reconnections)
- [ ] [Connection timeouts](#connection-timeouts)
- [ ] [Command execution reliability](#command-execution-reliability)
- [ ] [Smart client handoffs](#seamless-client-experience)

section_id: checklist

code:
const client = createClient({
  socket: {
    // setting a 10-second timeout
    connectTimeout: 10000 // in milliseconds
  }
});
client.on('error', error => console.error('Redis client error:', error));

section_id: timeouts

code:
import { createClient, commandOptions } from 'redis';

const client = createClient({ url: 'redis://localhost:6379' });
await client.connect();

const ac = new AbortController();
const t = setTimeout(() => ac.abort(), 1000);
try {
  const val = await client.get(commandOptions({ signal: ac.signal }), key);
} finally {
  clearTimeout(t);
}

code:
const client = createClient({
  disableOfflineQueue: true,
      .
      .
});

section_id: command-execution-reliability


title: Index and query documents

title: Initialize

text:
Make sure that you have [Redis Open Source]()
or another Redis server available. Also install the
[`node-redis`]() client library if you
haven't already done so.

Add the following dependencies:

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, JavaScript (Node.js)

**JavaScript (Node.js):**

[code example]

title: Create data

text:
Create some test data to add to your database. The example data shown
below is compatible with both JSON and hash objects.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, JavaScript (Node.js)

**JavaScript (Node.js):**

[code example]

title: Add the index

text:
Connect to your Redis database. The code below shows the most
basic connection but see
[Connect to the server]()
to learn more about the available connection options.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, JavaScript (Node.js)

**JavaScript (Node.js):**

[code example]

Create an index. In this example, only JSON documents with the key prefix `user:` are indexed. For more information, see [Query syntax]().

First, drop any existing index to avoid a collision. (The callback is required
to avoid an error if the index doesn't already exist.)

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, JavaScript (Node.js)

**JavaScript (Node.js):**

[code example]

Then create the index:

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, JavaScript (Node.js)

**JavaScript (Node.js):**

[code example]

title: Add the data

text:
Add the three sets of user data to the database as
[JSON]() objects.
If you use keys with the `user:` prefix then Redis will index the
objects automatically as you add them. Note that placing
the commands in a `Promise.all()` call is an easy way to create a
[pipeline](),
which is more efficient than sending the commands individually.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, JavaScript (Node.js)

**JavaScript (Node.js):**

[code example]

title: Query the data

text:
You can now use the index to search the JSON objects. The
[query]()
below searches for objects that have the text "Paul" in any field
and have an `age` value in the range 30 to 40:

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, JavaScript (Node.js)

**JavaScript (Node.js):**

[code example]

Specify query options to return only the `city` field:

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, JavaScript (Node.js)

**JavaScript (Node.js):**

[code example]

Use an
[aggregation query]()
to count all users in each city.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, JavaScript (Node.js)

**JavaScript (Node.js):**

[code example]

title: Differences with hash documents

text:
Indexing for hash documents is very similar to JSON indexing but you
need to specify some slightly different options.

When you create the schema for a hash index, you don't need to
add aliases for the fields, since you use the basic names to access
the fields anyway. Also, you must use `HASH` for the `ON` option
when you create the index. The code below shows these changes with
a new index called `hash-idx:users`, which is otherwise the same as
the `idx:users` index used for JSON documents in the previous examples.

First, drop any existing index to avoid a collision.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, JavaScript (Node.js)

**JavaScript (Node.js):**

[code example]

Then create the new index:

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, JavaScript (Node.js)

**JavaScript (Node.js):**

[code example]

You use [`hSet()`]() to add the hash
documents instead of [`json.set()`](),
but the same flat `userX` objects work equally well with either
hash or JSON:

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, JavaScript (Node.js)

**JavaScript (Node.js):**

[code example]

The query commands work the same here for hash as they do for JSON (but
the name of the hash index is different). The format of the result is
also the same:

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, JavaScript (Node.js)

**JavaScript (Node.js):**

[code example]

title: More information

text:
See the [Redis Search]() docs
for a full description of all query features with examples.

code:
import {
    createClient,
    SCHEMA_FIELD_TYPE,
    FT_AGGREGATE_GROUP_BY_REDUCERS,
    FT_AGGREGATE_STEPS,
} from 'redis';

section_id: initialize

code:
const user1 = {
    name: 'Paul John',
    email: 'paul.john@example.com',
    age: 42,
    city: 'London'
};

const user2 = {
    name: 'Eden Zamir',
    email: 'eden.zamir@example.com',
    age: 29,
    city: 'Tel Aviv'
};

const user3 = {
    name: 'Paul Zamir',
    email: 'paul.zamir@example.com',
    age: 35,
    city: 'Tel Aviv'
};

section_id: create-data

code:
const client = await createClient();
await client.connect();

section_id: add-the-index

code: await client.ft.dropIndex('idx:users', { DD: true }).then(() => {}, () => {});

code:
await client.ft.create('idx:users', {
    '$.name': {
        type: SCHEMA_FIELD_TYPE.TEXT,
        AS: 'name'
    },
    '$.city': {
        type: SCHEMA_FIELD_TYPE.TEXT,
        AS: 'city'
    },
    '$.age': {
        type: SCHEMA_FIELD_TYPE.NUMERIC,
        AS: 'age'
    }
}, {
    ON: 'JSON',
    PREFIX: 'user:'
});

code:
const [user1Reply, user2Reply, user3Reply] = await Promise.all([
    client.json.set('user:1', '$', user1),
    client.json.set('user:2', '$', user2),
    client.json.set('user:3', '$', user3)
]);

section_id: add-the-data

code:
let findPaulResult = await client.ft.search('idx:users', 'Paul @age:[30 40]');

console.log(findPaulResult.total); // >>> 1

findPaulResult.documents.forEach(doc => {
    console.log(`ID: ${doc.id}, name: ${doc.value.name}, age: ${doc.value.age}`);
});
// >>> ID: user:3, name: Paul Zamir, age: 35

section_id: query-the-data

code:
let citiesResult = await client.ft.search('idx:users', '*',{
    RETURN: 'city'
});

console.log(citiesResult.total); // >>> 3

citiesResult.documents.forEach(cityDoc => {
    console.log(cityDoc.value);
});
// >>> { city: 'London' }
// >>> { city: 'Tel Aviv' }
// >>> { city: 'Tel Aviv' }

code:
let aggResult = await client.ft.aggregate('idx:users', '*', {
    STEPS: [{
        type: FT_AGGREGATE_STEPS.GROUPBY,
        properties: '@city',
        REDUCE: [{
            type: FT_AGGREGATE_GROUP_BY_REDUCERS.COUNT,
            AS: 'count'
        }]
    }]
});

console.log(aggResult.total); // >>> 2

aggResult.results.forEach(result => {
    console.log(`${result.city} - ${result.count}`);
});
// >>> London - 1
// >>> Tel Aviv - 2

code: await client.ft.dropIndex('hash-idx:users', { DD: true }).then(() => {}, () => {});

section_id: differences-with-hash-documents

code:
await client.ft.create('hash-idx:users', {
    'name': {
        type: SCHEMA_FIELD_TYPE.TEXT
    },
    'city': {
        type: SCHEMA_FIELD_TYPE.TEXT
    },
    'age': {
        type: SCHEMA_FIELD_TYPE.NUMERIC
    }
}, {
    ON: 'HASH',
    PREFIX: 'huser:'
});

code:
const [huser1Reply, huser2Reply, huser3Reply] = await Promise.all([
    client.hSet('huser:1', user1),
    client.hSet('huser:2', user2),
    client.hSet('huser:3', user3)
]);

code:
let findPaulHashResult = await client.ft.search(
    'hash-idx:users', 'Paul @age:[30 40]'
);

console.log(findPaulHashResult.total); // >>> 1

findPaulHashResult.documents.forEach(doc => {
    console.log(`ID: ${doc.id}, name: ${doc.value.name}, age: ${doc.value.age}`);
});
// >>> ID: huser:3, name: Paul Zamir, age: 35


title: Pipelines and transactions

title: Overview

text:
Redis lets you send a sequence of commands to the server together in a batch.
There are two types of batch that you can use:

-   **Pipelines** avoid network and processing overhead by sending several commands
    to the server together in a single communication. The server then sends back
    a single communication with all the responses. See the
    [Pipelining]() page for more
    information.
-   **Transactions** guarantee that all the included commands will execute
    to completion without being interrupted by commands from other clients.
    See the [Transactions]()
    page for more information.

title: Execute a pipeline

text:
There are two ways to execute commands in a pipeline. Firstly, `node-redis` will
automatically pipeline commands that execute within the same "tick" of the
[event loop](https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick#what-is-the-event-loop).
You can ensure that commands happen in the same tick very easily by including them in a
[`Promise.all()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)
call, as shown in the following example. The chained `then(...)` callback is optional
and you can often omit it for commands that write data and only return a
status result.

[code example]

You can also create a pipeline object using the
[`multi()`]() method
and then add commands to it using methods that resemble the standard
command methods (for example, `set()` and `get()`). The commands are
buffered in the pipeline and only execute when you call the
`execAsPipeline()` method on the pipeline object. Again, the
`then(...)` callback is optional.

[code example]

The two approaches are almost equivalent, but they have different behavior
when the connection is lost during the execution of the pipeline. After
the connection is re-established, a `Promise.all()` pipeline will
continue execution from the point where the interruption happened,
but a `multi()` pipeline will discard any remaining commands that
didn't execute.

title: Execute a transaction

text:
A transaction works in a similar way to a pipeline. Create a
transaction object with the `multi()` command, call command methods
on that object, and then call the transaction object's
`exec()` method to execute it.

[code example]

title: Watch keys for changes

text:
Redis supports *optimistic locking* to avoid inconsistent updates
to different keys. The basic idea is to watch for changes to any
keys that you use in a transaction while you are are processing the
updates. If the watched keys do change, you must restart the updates
with the latest data from the keys. See
[Transactions]()
for more information about optimistic locking.

The code below reads a string
that represents a `PATH` variable for a command shell, then appends a new
command path to the string before attempting to write it back. If the watched
key is modified by another client before writing, the transaction aborts.
Note that you should call read-only commands for the watched keys synchronously on
the usual `client` object but you still call commands for the transaction on the
transaction object created with `multi()`.

For production usage, you would generally call code like the following in
a loop to retry it until it succeeds or else report or log the failure.

[code example]

In an environment where multiple concurrent requests are sharing a connection
(such as a web server), you must use a connection pool to get an isolated connection,
as shown below:

[code example]

This is important because the server tracks the state of the WATCH on a
per-connection basis, and concurrent WATCH and MULTI/EXEC calls on the same
connection will interfere with one another. See
[`RedisClientPool`](https://github.com/redis/node-redis/blob/master/docs/pool.md)
for more information.

code:
await Promise.all([
  client.set('seat:0', '#0'),
  client.set('seat:1', '#1'),
  client.set('seat:2', '#2'),
]).then((results) =>{
    console.log(results);
    // >>> ['OK', 'OK', 'OK']
});

await Promise.all([
    client.get('seat:0'),
    client.get('seat:1'),
    client.get('seat:2'),
]).then((results) =>{
    console.log(results);
    // >>> ['#0', '#1', '#2']
});

section_id: execute-a-pipeline

code:
await client.multi()
    .set('seat:3', '#3')
    .set('seat:4', '#4')
    .set('seat:5', '#5')
    .execAsPipeline()
    .then((results) => {
        console.log(results);
        // >>> ['OK', 'OK', 'OK']
    });

code:
const [res1, res2, res3] = await client.multi()
    .incrBy("counter:1", 1)
    .incrBy("counter:2", 2)
    .incrBy("counter:3", 3)
    .exec();

console.log(res1); // >>> 1
console.log(res2); // >>> 2
console.log(res3); // >>> 3

section_id: execute-a-transaction

code:
// Set initial value of `shellpath`.
client.set('shellpath', '/usr/syscmds/');

// Watch the key we are about to update.
await client.watch('shellpath');

const currentPath = await client.get('shellpath');
const newPath = currentPath + ':/usr/mycmds/';

// Attempt to write the watched key.
await client.multi()
    .set('shellpath', newPath)
    .exec()
    .then((result) => {
        // This is called when the pipeline executes
        // successfully.
        console.log(result);
    }, (err) => {
        // This is called when a watched key was changed.
        // Handle the error here.
        console.log(err);
    });

const updatedPath = await client.get('shellpath');
console.log(updatedPath);
// >>> /usr/syscmds/:/usr/mycmds/

section_id: watch-keys-for-changes

code:
import { createClientPool } from 'redis';

const pool = await createClientPool()
  .on('error', err => console.error('Redis Client Pool Error', err));

try {
  await pool.execute(async client => {
    await client.watch('key');

    const multi = client.multi()
      .ping()
      .get('key');

    if (Math.random() > 0.5) {
      await client.watch('another-key');
      multi.set('another-key', await client.get('another-key') / 2);
    }

    return multi.exec();
  });
} catch (err) {
  if (err instanceof WatchError) {
    // the transaction aborted
  }
}