npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@keyv/redis

v6.1.0

Published

Redis storage adapter for Keyv

Readme

@keyv/redis

Redis storage adapter for Keyv

build codecov npm npm

Redis storage adapter for Keyv.

Features

  • Built on top of @redis/client.
  • TTL is handled directly by Redis via absolute PXAT expiry, with an automatic relative PX fallback for servers older than 6.2 (see Expiration and TTL).
  • Supports Redis Clusters.
  • Url connection string support or pass in your Redis Options
  • Easily add in your own Redis client.
  • Namespace support for key management.
  • Unlink as default delete method for performance.
  • Access to the Redis client for advanced use cases.
  • Keyv and Redis Libraries are exported for advanced use cases.
  • createKeyv function for easy creation of Keyv instances.
  • jsDoc comments for easy documentation.
  • CJS / ESM and TypeScript supported out of the box.

Table of Contents

Installation

npm install --save keyv @keyv/redis

Usage

Here is a standard use case where we implement Keyv and @keyv/redis:

import Keyv from 'keyv';
import KeyvRedis from '@keyv/redis';

const keyv = new Keyv(new KeyvRedis('redis://user:pass@localhost:6379'));
keyv.on('error', handleConnectionError);

Here is the same example but with the Keyv instance created with the createKeyv function:

import { createKeyv } from '@keyv/redis';

const keyv = createKeyv('redis://user:pass@localhost:6379');

You only have to import the @keyv/redis library if you are using the createKeyv function. 🎉 Otherwise, you can import Keyv and @keyv/redis independently.

Here you can pass in the Redis options directly:

import Keyv from 'keyv';
import KeyvRedis from '@keyv/redis';

const uri = "redis://localhost:6379";

// NOTE: please use the settings that you need to configure. Check out Keyv Redis Options section
const options = {
  namespace: "test",
  namespaceSeparator: "->",
  clearBatchSize: 100,
  useUnlink: true,
  noNamespaceAffectsAll: true,
};

const keyvRedis = new KeyvRedis(uri, options);

const keyv = new Keyv(keyvRedis);

Or you can create a new Redis instance and pass it in with KeyvOptions such as setting the store:

import Keyv from 'keyv';
import KeyvRedis, { createClient } from '@keyv/redis';

const redis = createClient({ url: 'redis://user:pass@localhost:6379' });
const keyvRedis = new KeyvRedis(redis);
const keyv = new Keyv({ store: keyvRedis});

Migrating from v5 to v6

@keyv/redis v6 tracks Keyv's shared version. The adapter already implemented the v6 storage contract (capabilities.expires); these are the redis-specific things to know:

  • @redis/client is now v6. createClient, createCluster, and createSentinel are still exported from this package. createClient takes a RedisClientOptions object ({ url: 'redis://...' }), not a URI string.
  • Adapters receive absolute expires, not relative ttl. When you call keyv.set(key, value, 1000), Keyv converts that millisecond ttl to a Unix-ms deadline and passes it to the adapter. Direct adapter calls should pass expires (Date.now() + ttl), not a relative ttl.
  • Keyv no longer prefixes keys. Namespacing lives on the adapter (namespace + namespaceSeparator, default ::). You do not need useKeyPrefix: false — that option was removed from Keyv.
  • keyPrefixSeparator is now namespaceSeparator. The option, getter, and setter were renamed to match the other adapters. In plain JavaScript the old option is ignored and the store uses ::, so rename it wherever you set it.
  • Keys written by v5 need matching settings. v5's default setup, new Keyv(new KeyvRedis(uri)), applied its default keyv namespace twice, once in Keyv and once in the adapter, so foo was stored as keyv::keyv:foo. Read those keys with new Keyv(new KeyvRedis(uri, { namespaceSeparator: '::keyv:' }), { namespace: 'keyv' }). The v5 to v6 migration guide lists the settings for other v5 setups.
  • createKeyv accepts cluster and sentinel options the same way the KeyvRedis constructor does.
  • A failure is reported once. When the adapter rejects because of throwOnConnectError or throwOnErrors, it no longer also emits error. A failed connection rejects with the connection error as the rejection's cause. See Gracefully Handling Errors and Timeouts.

Migrating from v4 to v5

The major change from v4 to v5 is that we are now using v5 of the @redis/client library which has a new API. This means that some methods have changed but it should be a drop-in replacement for most use cases.

Keyv Redis Options

You can pass in options to the KeyvRedis constructor. Here are the available options:

export type KeyvRedisOptions = {
	/**
	 * Namespace for the current instance.
	 */
	namespace?: string;
	/**
	 * Separator to use between namespace and key.
	 */
	namespaceSeparator?: string;
	/**
	 * Number of keys to delete in a single batch.
	 */
	clearBatchSize?: number;
	/**
	 * Enable Unlink instead of using Del for clearing keys. This is more performant but may not be supported by all Redis versions.
	 */
	useUnlink?: boolean;

	/**
	 * Whether to allow clearing all keys when no namespace is set.
	 * If set to true and no namespace is set, iterate() will return all keys.
	 * @default false
	 */
	noNamespaceAffectsAll?: boolean;

	/**
	 * Throw an error if the client is not connected when trying to connect. By default this is
	 * `true` so a failed Redis connection throws.
	 * @default true
	 */
	throwOnConnectError?: boolean;

	/**
	 * Throw an error if any operation fails. When `false`, failures emit an `error` event
	 * and return no-op responses (`undefined` for gets, `false` for writes/deletes).
	 * @default false
	 */
	throwOnErrors?: boolean;

	/**
	 * Timeout in milliseconds for the full connect handshake (TCP plus Redis HELLO / AUTH).
	 * When undefined, the Redis client default is used. If set, a connection that does not
	 * succeed within this time throws, the in-flight attempt is aborted, and leftover sockets
	 * are not left open. When Keyv constructs the client, this is also passed as
	 * `socket.connectTimeout` unless that option is already set. Auto-reconnect is disabled
	 * unless you pass `socket.reconnectStrategy`.
	 * @default undefined
	 */
	connectionTimeout?: number;
};

You can pass these options when creating a new KeyvRedis instance:

import Keyv from 'keyv';
import KeyvRedis from '@keyv/redis';

const keyvRedis = new KeyvRedis('redis://user:pass@localhost:6379', {
  namespace: 'my-namespace',
  namespaceSeparator: ':',
  clearBatchSize: 1000,
  useUnlink: true,
  noNamespaceAffectsAll: false,
  connectionTimeout: 200
});

const keyv = new Keyv({ store: keyvRedis });

You can also set these options after the fact by using the KeyvRedis instance properties:

import {createKeyv} from '@keyv/redis';

const keyv = createKeyv('redis://user:pass@localhost:6379');
keyv.store.namespace = 'my-namespace';

See API for the full property and method list. Missing keys and swallowed errors return undefined (never null). Events are emitted through Hookified — see Events.

Using the createKeyv function

The createKeyv function is a convenience function that creates a new Keyv instance with the @keyv/redis store. It applies namespace on both Keyv and the adapter so keys are prefixed once. Here is an example of how to use it:

import { createKeyv } from '@keyv/redis';
const keyv = createKeyv('redis://user:pass@localhost:6379');

To use a namespace you can do it here and this will set Keyv up correctly to avoid the double namespace issue:

import { createKeyv } from '@keyv/redis';
const keyv = createKeyv('redis://user:pass@localhost:6379', {namespace: 'my-namespace'});

Using the createKeyvNonBlocking function

The createKeyvNonBlocking function is a convenience function that creates a new Keyv instance with the @keyv/redis store. It does what createKeyv does but also turns off the adapter's throwOnConnectError and throwOnErrors, the Redis offline queue, and the reconnect strategy, so that when used as a secondary cache in libraries such as cacheable it does not block the primary cache. This is useful when you want to use Redis as a secondary cache and do not want to block the primary cache on connection errors or timeouts when using nonBlocking. Concurrent operations share the in-flight Redis connect so they are not dropped while the client is still opening. Here is an example of how to use it:

import { createKeyvNonBlocking } from '@keyv/redis';
const keyv = createKeyvNonBlocking('redis://user:pass@localhost:6379');
keyv.on('error', (error) => console.error('Redis error', error));

As with any Keyv instance, attach an error listener so a failed operation returns a fallback value instead of rejecting. See Gracefully Handling Errors and Timeouts.

With a URI or client options, createKeyvNonBlocking creates the client with the offline queue and reconnect already off. When Redis is down, an operation fails as soon as its connection attempt does and returns its fallback value, instead of waiting for Redis to come back. A refused connection fails immediately. A host that doesn't respond waits for the socket's connectTimeout (5 seconds by default), which the connectionTimeout option can shorten.

node-redis fixes a client's reconnect strategy when the client is created, so a client you pass in keeps its own. Create it with socket: { reconnectStrategy: false } to get the same behavior. Its offline queue is still turned off:

import { createClient, createKeyvNonBlocking } from '@keyv/redis';
const client = createClient({ url: 'redis://localhost:6379', socket: { reconnectStrategy: false } });
const keyv = createKeyvNonBlocking(client);

Cluster and sentinel connections keep their own offline queue and reconnect settings. Only the throw settings change.

Namespaces

By default namespacing is turned off, this is done because it causes much more memory / performance usage for Redis.

Redis does not treat colons (:) or namespaces as special—there are no hierarchical keys or namespace mechanics internally. The dramatic memory savings you see when removing prefixes like namespace: come from one thing: key length. Redis stores every key as a full string in memory, wrapped in an SDS structure and allocated by jemalloc. Longer keys (e.g., namespace:key123) fall into larger jemalloc size classes, require more bytes for the SDS header, and cause more fragmentation. Shorter keys (e.g., key123) fit into much smaller slabs, pack tightly, and result in far more stable and predictable memory usage.

This means the colon is not the issue—the extra characters are. A key that goes from ~18 bytes to ~6 bytes can use half the memory per key once overhead, allocator classes, and fragmentation are considered. Multiply that by hundreds of thousands or millions of keys, and memory usage becomes significantly smaller and much more stable simply because the keys are shorter.

How to use Namespaces

You can set a namespace for your keys. This is useful if you want to manage your keys in a more organized way. Here is an example of how to set a namespace with the store option:

import Keyv from 'keyv';
import KeyvRedis, { createClient } from '@keyv/redis';

const redis = createClient({ url: 'redis://user:pass@localhost:6379' });
const keyvRedis = new KeyvRedis(redis);
const keyv = new Keyv({ store: keyvRedis, namespace: 'my-namespace' });

To make this easier, you can use the createKeyv function which will automatically set the namespace option to the KeyvRedis instance:

import { createKeyv } from '@keyv/redis';
const keyv = createKeyv('redis://user:pass@localhost:6379', { namespace: 'my-namespace' });

This will prefix all keys with my-namespace:: (the default namespaceSeparator is ::). You can also set the namespace after the fact:

keyv.namespace = 'my-namespace';

clear() and iterator() only touch keys that start with the namespace and separator (my-namespace::). Both are matched literally, so *, ?, [, ] and \ in them have no special meaning to SCAN: clearing tenant* leaves tenant-prod alone. Clearing my-namespace does also clear a namespace that extends it with the separator, such as my-namespace::archive, because a key can contain the separator too. Keep the separator out of namespace names if you need them kept apart.

NOTE: If you plan to do many clears or deletes, it is recommended to read the Performance Considerations section.

Fixing Double Prefixing of Keys

In v6, Keyv does not prefix keys. The Redis adapter owns namespacing, so this:

import Keyv from 'keyv';
import KeyvRedis from '@keyv/redis';

const keyv = new Keyv(new KeyvRedis('redis://user:pass@localhost:6379'), { namespace: 'my-namespace' });

stores keys as my-namespace::key. createKeyv does the same wiring for you:

import { createKeyv } from '@keyv/redis';
const keyv = createKeyv('redis://user:pass@localhost:6379', { namespace: 'my-namespace' });

If you are upgrading from v5, drop useKeyPrefix — it no longer exists. Do not also set Redis client's keyPrefix; use the adapter namespace instead so SCAN/clear/iterator stay in sync.

Using Generic Types

When initializing KeyvRedis, you can specify the type of the values you are storing and you can also specify types when calling methods:

import Keyv from 'keyv';
import KeyvRedis, { createClient } from '@keyv/redis';


type User = {
  id: number
  name: string
}

const redis = createClient({ url: 'redis://user:pass@localhost:6379' });

const keyvRedis = new KeyvRedis<User>(redis);
const keyv = new Keyv({ store: keyvRedis });

await keyv.set("user:1", { id: 1, name: "Alice" })
const user = await keyv.get("user:1")
console.log(user.name) // 'Alice'

// specify types when calling methods
const user = await keyv.get<User>("user:1")
console.log(user.name) // 'Alice'

Performance Considerations

With namespaces being prefix based it is critical to understand some of the performance considerations we have made:

  • clear() - We use the SCAN command to iterate over keys. This is a non-blocking command that is more efficient than KEYS. In addition we are using UNLINK by default instead of DEL. Even with that if you are iterating over a large dataset it can still be slow. It is highly recommended to use the namespace option to limit the keys that are being cleared and if possible to not use the clear() method in high performance environments. If you don't set namespaces, you can enable noNamespaceAffectsAll to clear all keys using the FLUSHDB command which is faster and can be used in production environments.

  • delete() - By default we are now using UNLINK instead of DEL for deleting keys. This is a non-blocking command that is more efficient than DEL. If you are deleting a large number of keys it is recommended to use the deleteMany() method instead of delete().

  • clearBatchSize - The clearBatchSize option is set to 1000 by default. This is because Redis has a limit of 1000 keys that can be deleted in a single batch. If no namespace is defined and noNamespaceAffectsAll is set to true this option will be ignored and the FLUSHDB command will be used instead.

  • useUnlink - This option is set to true by default. This is because UNLINK is a non-blocking command that is more efficient than DEL. If you are not using UNLINK and are doing a lot of deletes it is recommended to set this option to true.

  • setMany, getMany, deleteMany - These methods are more efficient than their singular counterparts. These will be used by default in the Keyv library such as when using keyv.delete(string[]) it will use deleteMany().

If you want to see even better performance please see the Using Cacheable with Redis section as it has non-blocking and in-memory primary caching that goes along well with this library and Keyv.

Expiration and TTL

Keyv hands this adapter an absolute expiry — a Unix timestamp in milliseconds — computed once on the Keyv host. The adapter writes it to Redis with SET ... PXAT, the absolute-expiry option. Because the deadline is absolute, it is immune to clock skew and to any latency between Keyv computing the expiry and Redis receiving the command: a key set to expire at T expires at T no matter how long the write takes to arrive.

PXAT was added in Redis 6.2. For older servers the adapter automatically falls back to the relative PX option (the remaining lifetime in milliseconds):

  • On the first expiring write, the adapter runs INFO server once, parses redis_version, and caches whether the server is 6.2+. Every later write reuses that cached answer, so detection costs one INFO round-trip per connection, not per write.
  • 6.2 or newer → the write uses PXAT: expires (absolute).
  • Older than 6.2 → the write uses PX: max(1, expires - Date.now()) (relative, computed at write time). An expiry already in the past becomes PX: 1 because Redis rejects SET ... PX 0.
  • If the version cannot be determined — for example a cluster where INFO isn't directly available, or a transient error reading it — the adapter assumes PXAT is supported, since such deployments are overwhelmingly modern. The cached result means it won't keep retrying INFO on every write.

The same detection and fallback apply to setMany, including the per-hash-slot grouping used in cluster mode. No configuration is required; you always call keyv.set(key, value, ttl) with a relative millisecond ttl (or rely on the ttl option) and the adapter chooses the correct Redis option for your server.

The relative PX fallback re-derives the remaining lifetime store-side, so on pre-6.2 servers a large write-to-Redis latency could in theory shorten the effective TTL by that latency. On 6.2+ (PXAT) there is no such window. If you need the skew-immune absolute behaviour, run Redis 6.2 or later.

High Memory Usage on Redis Server

This is because we are using UNLINK by default instead of DEL. This is a non-blocking command that is more efficient than DEL but will slowly remove the memory allocation.

If you are deleting or clearing a large number of keys you can disable this by setting the useUnlink option to false. This will use DEL instead of UNLINK and should reduce the memory usage.

const keyv = new Keyv(new KeyvRedis('redis://user:pass@localhost:6379', { useUnlink: false }));
// Or
keyv.store.useUnlink = false;

Gracefully Handling Errors and Timeouts

Attach an error listener to the Keyv instance when you use @keyv/redis. Keyv handles errors the way a Node.js EventEmitter does: when an operation fails, it emits error, and then

  • with a listener attached, the operation returns a fallback value, such as undefined from get or false from set;
  • with no listener attached, the operation rejects with the error.

The Redis client also reports connection problems as error events outside any Keyv call, for example when a connection drops. Keyv forwards these to its own error event. With no listener attached, such an error is thrown with no call to catch it, which can crash your process.

import Keyv from 'keyv';
import KeyvRedis from '@keyv/redis';
const keyv = new Keyv(new KeyvRedis('redis://user:pass@localhost:6379'));
keyv.on('error', (error) => {
  console.error('error', error);
});

See Error Handling in the Keyv README for the fallback value each method returns.

The adapter has its own options that decide whether KeyvRedis rejects or reports a failure as an error event:

  • throwOnConnectError (default true): reject when the connection fails, with the connection error as the rejection's cause. When it is false, the adapter emits the connection error as an error event instead.
  • throwOnErrors (default false): reject when an operation fails. When it is false, the adapter emits error and returns a no-op value on get, getMany, set, setMany, delete, and deleteMany if the connection is lost.

The adapter reports each failure once: when it rejects, it does not also emit error. Used through Keyv, a rejection from the adapter is handled by the rule above: Keyv emits error, then returns a fallback value if a listener is attached or rejects if none is.

There is a default Reconnect Strategy if you pass in just a uri connection string we will automatically create a Redis client for you with the following reconnect strategy:

export const defaultReconnectStrategy = (attempts: number): number | Error => {
	// Exponential backoff base: double each time, capped at 2s.
	// Parentheses make it clear we do (2 ** attempts) first, then * 100
	const backoff = Math.min((2 ** attempts) * 100, 2000);

	// Add random jitter of up to ±50ms to avoid thundering herds:
	const jitter = (Math.random() - 0.5) * 100;

	return backoff + jitter;
};

Using Cacheable with Redis

If you are wanting to see even better performance with Redis, you can use Cacheable which is a multi-layered cache library that has in-memory primary caching and non-blocking secondary caching. Here is an example of how to use it with Redis:

import KeyvRedis from '@keyv/redis';
import Cacheable from 'cacheable';

const secondary = new KeyvRedis('redis://user:pass@localhost:6379');

const cache = new Cacheable( { secondary } );

For even higher performance you can set the nonBlocking option to true:

const cache = new Cacheable( { secondary, nonBlocking: true } );

This will make it so that the secondary does not block the primary cache and will be very fast. 🚀

Note: cacheable 2 depends on Keyv v5 and wraps a store like this one in its own v5 Keyv. A v5 Keyv passes a relative ttl where a v6 adapter expects an absolute expires, so entries expire at the wrong time, usually at once. Check which Keyv version your cacheable needs with npm view cacheable dependencies.keyv, and keep this setup on v5 until it supports v6. See Libraries That Embed Keyv v5.

Clustering

If you are using a Redis Cluster, you can pass in the redisOptions directly. Here is an example of how to do that:

import Keyv from 'keyv';
import KeyvRedis, { createCluster } from '@keyv/redis';

const cluster = createCluster({
    rootNodes: [
      {
        url: 'redis://127.0.0.1:7000',
      },
      {
        url: 'redis://127.0.0.1:7001',
      },
      {
        url: 'redis://127.0.0.1:7002',
      },
    ],
});

const keyv = new Keyv({ store: new KeyvRedis(cluster) });

You can learn more about the createCluster function in the documentation at https://github.com/redis/node-redis/tree/master/docs.

Sentinel

If you are using Sentinel to provide high availability for your Redis instances, you can pass in the sentinel options directly to the KeyvRedis constructor:

import Keyv from 'keyv';
import KeyvRedis from '@keyv/redis';

const keyv = new Keyv({
  store: new KeyvRedis({
    name: 'mymaster',
    sentinelRootNodes: [
      { host: '127.0.0.1', port: 26379 },
      { host: '127.0.0.1', port: 26380 },
      { host: '127.0.0.1', port: 26381 },
    ],
  }),
});

For TypeScript users, the RedisSentinelOptions type is exported from the package:

import KeyvRedis, { type RedisSentinelOptions } from '@keyv/redis';

const sentinelOptions: RedisSentinelOptions = {
  name: 'mymaster',
  sentinelRootNodes: [
    { host: '127.0.0.1', port: 26379 },
    { host: '127.0.0.1', port: 26380 },
    { host: '127.0.0.1', port: 26381 },
  ],
};

const keyvRedis = new KeyvRedis(sentinelOptions);

You can learn more about Sentinel configuration in the documentation at https://github.com/redis/node-redis/tree/master/docs.

TLS Support

Here is an example of how to use TLS using the redisOptions:

import Keyv from 'keyv';
import KeyvRedis from '@keyv/redis';

const tlsOptions = {
    socket: {
      host: 'localhost',
      port: 6379,
      tls: true,  // Enable TLS connection
      rejectUnauthorized: false,  // Ignore self-signed certificate errors (for testing)
      
      // Alternatively, provide CA, key, and cert for mutual authentication
      ca: fs.readFileSync('/path/to/ca-cert.pem'),
      cert: fs.readFileSync('/path/to/client-cert.pem'),  // Optional for client auth
      key: fs.readFileSync('/path/to/client-key.pem'),    // Optional for client auth
    }
};

const keyv = new Keyv({ store: new KeyvRedis(tlsOptions) });

Redis Cloud

You connect to Redis Cloud the same way you connect to any Redis server, with a URI or with Redis client options. KeyvRedis creates a client from a URI string. It passes an options object, unchanged except for connectionTimeout when you set it, to createClient from @redis/client. That means url, username, password, and every socket TLS option work exactly as documented for @redis/client. createKeyv accepts the same arguments.

Endpoint and credentials

The database endpoint (host and port) is on the database's Configuration tab in the Redis Cloud console: in the Access section for Essentials databases, or the General section for Pro databases. By default the database has a user named default. To see its password, use Default user > Configure for an Essentials database, or the Security section of the Configuration tab for a Pro database. If you turned on role-based access control and turned off the default user, use the username and password of a data access role instead.

With TLS turned on, the URI uses the rediss:// scheme (two s's). Without redis_ca.pem, the URI below fails to connect when the database's certificate was issued by a Redis Cloud CA, so trust that CA as shown in Trusting redis_ca.pem.

import Keyv from 'keyv';
import KeyvRedis from '@keyv/redis';

const keyv = new Keyv(new KeyvRedis('rediss://default:password@redis-host:12345'));
keyv.on('error', (error) => console.error('Redis error', error));

Replace password, redis-host, and 12345 with your values. Encode a password that contains reserved URI characters, such as @, :, /, or #, with encodeURIComponent. A redis:// URI connects without TLS, which a TLS-enabled database refuses.

Turning on TLS

TLS is off by default. Turn it on from the Security section of the database's Configuration tab. It is available on paid Essentials and Pro plans, but not on the Free plan. Redis Cloud recommends TLS for any database reached over its public endpoint. Once TLS is on, every connection must use it.

Trusting redis_ca.pem

Download the Redis Cloud CA bundle, redis_ca.pem, with Download server certificate on the database's Configuration tab, or from Account Settings > Security. The file holds several certificates, including Redis Cloud's own root CAs, which Node.js does not trust by default. Without the bundle, a rediss:// URI checks the certificate only against Node.js's built-in CAs, which fails for databases whose certificates were issued by a Redis Cloud CA. Pass the whole file as socket.ca. Node.js loads every certificate in it.

import fs from 'node:fs';
import Keyv from 'keyv';
import KeyvRedis from '@keyv/redis';

const keyv = new Keyv(new KeyvRedis({
  url: 'rediss://default:password@redis-host:12345',
  socket: {
    tls: true,
    ca: fs.readFileSync('/path/to/redis_ca.pem'),
  },
}));
keyv.on('error', (error) => console.error('Redis error', error));

You can set the host, port, and credentials as separate options instead of a url:

const keyv = new Keyv(new KeyvRedis({
  username: 'default',
  password: 'password',
  socket: {
    host: 'redis-host',
    port: 12345,
    tls: true,
    ca: fs.readFileSync('/path/to/redis_ca.pem'),
  },
}));

Keep these points in mind:

  • Leave rejectUnauthorized unset so Node.js verifies the certificate. The TLS Support sample sets it to false, which is only suitable for a local server with a self-signed certificate.
  • Connect with the endpoint hostname from the console, not an IP address. The certificate check compares hostnames.
  • socket.tls must match the URI scheme, so it must be true with rediss://. Otherwise @redis/client throws.
  • A URI string gets the adapter's defaultReconnectStrategy. With an options object, @redis/client uses its own reconnect behavior unless you set socket.reconnectStrategy, for example to defaultReconnectStrategy, which you can import from @keyv/redis.

Mutual TLS

If the database requires client authentication (Mutual TLS in the TLS settings), add the client certificate and private key you registered there to the same socket options:

const keyv = new Keyv(new KeyvRedis({
  username: 'default',
  password: 'password',
  socket: {
    host: 'redis-host',
    port: 12345,
    tls: true,
    ca: fs.readFileSync('/path/to/redis_ca.pem'),
    cert: fs.readFileSync('/path/to/client.crt'),
    key: fs.readFileSync('/path/to/client.key'),
  },
}));

Other hosted Redis services use the same pattern: a rediss:// URI or socket.tls: true, plus the CA certificate the provider gives you.

API

KeyvRedis extends Hookified (on, once, emit, hooks) and implements the Keyv storage adapter contract. Missing keys and swallowed errors return undefined (never null).

Properties

  • capabilities - Adapter capability descriptor. capabilities.expires is true (absolute Unix-ms expires on set / setMany).
  • client - The Redis client, cluster, or sentinel connection. Assigning a new connection re-wires Hookified event listeners and resets PXAT detection. Type: RedisClientConnectionType.
  • namespace - Namespace used to prefix keys. undefined means no prefixing. Default: undefined.
  • namespaceSeparator - Separator between namespace and key. May be "". Default: "::".
  • clearBatchSize - SCAN / delete batch size. Must be greater than 0 or an error event is emitted. Default: 1000.
  • useUnlink - Use UNLINK instead of DEL. Default: true.
  • noNamespaceAffectsAll - When no namespace is set, clear() / iterator() affect all keys (including namespaced ones). Default: false.
  • throwOnConnectError - Throw when connect fails, with the connection error as the cause, instead of emitting error. Default: true.
  • throwOnErrors - Throw on operation failures instead of emitting error and returning a no-op. Default: false.
  • connectionTimeout - Timeout in milliseconds for the full connect handshake (TCP plus Redis HELLO / AUTH). undefined uses the Redis client default. On expiry the in-flight connect is aborted so sockets and timers are not left active. When Keyv constructs the client, this is also passed as socket.connectTimeout unless that option is already set. Auto-reconnect is disabled unless you pass socket.reconnectStrategy.

Methods

  • constructor([connect], [options]) - connect is a URI string, client/cluster/sentinel options (KeyvRedisConnect), or an existing connection. See Keyv Redis Options.
  • getClient() - Return the ready client, connecting first if needed. Concurrent callers share the in-flight connect and wait until isReady (not only isOpen). On connectionTimeout, the hung handshake is aborted so sockets are not left open. Returns Promise<RedisClientConnectionType>.
  • set(key, value, [expires]) - Set a key. expires is an absolute Unix timestamp in milliseconds. Returns Promise<boolean>. Through Keyv, pass a relative millisecond ttl to keyv.set — Keyv converts it to expires.
  • setMany(entries) - Set KeyvStorageEntry objects ({ key, value, expires? }) via MULTI/EXEC. Returns Promise<boolean[]> (per-entry success). Cluster mode groups by hash slot.
  • get(key) - Get a key. Returns the value or undefined if missing (Redis null is mapped to undefined).
  • getMany(keys) - Get multiple keys. Each missing entry is undefined.
  • has(key) - Returns Promise<boolean>.
  • hasMany(keys) - Returns Promise<boolean[]>.
  • delete(key) - Returns Promise<boolean>.
  • deleteMany(keys) - Returns Promise<boolean[]>.
  • clear() - Clear keys in the namespace. With no namespace, clears un-prefixed keys unless noNamespaceAffectsAll is true (FLUSHDB).
  • iterator() - Async generator of [key, value] pairs. Uses the instance namespace. Missing values are undefined.
  • disconnect([force]) - Graceful close() (QUIT) when force is false; destroy() when true.
  • createKeyPrefix(key, [namespace]) - Returns namespace::key (or key when namespace is omitted).
  • getKeyWithoutPrefix(key, [namespace]) - Strips a leading namespace prefix.
  • isCluster() - true if the connection is a Redis cluster.
  • isSentinel() - true if the connection is a Redis sentinel.
  • getMasterNodes() - Cluster master node clients, or [client] when standalone.

Helpers

  • createKeyv([connect], [options]) - Keyv instance with this adapter. Applies namespace on both Keyv and the store. connect accepts the same KeyvRedisConnect types as the constructor (including cluster/sentinel). Defaults to "redis://localhost:6379" when connect is omitted.
  • createKeyvNonBlocking([connect], [options]) - Same as createKeyv, then disables throws, the offline queue, and reconnect for secondary-cache use. A client passed in keeps its own reconnect strategy, and cluster and sentinel connections only have their throw settings changed. See Using the createKeyvNonBlocking function. Concurrent operations wait for the shared in-flight connect instead of being dropped.
  • defaultReconnectStrategy(attempts) - Default socket reconnect delay when a URI string is passed to the constructor. Exponential backoff capped at 2s, plus up to ±50ms of jitter. Returns a delay in milliseconds.

Events

KeyvRedis extends Hookified and re-emits these events from the Redis client onto the adapter:

| Event | Payload | When | | --- | --- | --- | | error | Error (or a string for invalid clearBatchSize) | Client errors, and connect or operation failures that the adapter does not reject (see throwOnConnectError and throwOnErrors) | | connect | the Redis connection | The client connects | | disconnect | the Redis connection | The client disconnects | | reconnecting | reconnect info from @redis/client | The client is reconnecting |

import {createKeyv} from '@keyv/redis';

const keyv = createKeyv('redis://user:pass@localhost:6379');
const store = keyv.store;

store.on('error', (error) => {
  console.error('adapter error', error);
});

store.on('connect', () => {
  console.log('Redis connected');
});

store.once('disconnect', () => {
  console.log('Redis disconnected');
});

store.on('reconnecting', () => {
  console.log('Redis reconnecting');
});

error, connect, disconnect, and reconnecting are re-emitted on the adapter. Other Redis-client-only events (for example ready) can still be listened on store.client:

store.client.on('ready', () => {
  console.log('Redis client ready');
});

Client events: https://www.npmjs.com/package/redis#events

Migrating from v3 to v4

Note: This section applies to versions before v6. Keyv v6 removed the useKeyPrefix option: TypeScript rejects it and JavaScript ignores it. In v6 the adapter owns namespacing, so leave the option out. See Fixing Double Prefixing of Keys.

Overall the API is the same as v3 with additional options and performance improvements. Here are the main changes:

  • The ioredis library has been removed in favor of the redis aka node-redis library. If you want to use ioredis you can use @keyv/valkey
  • The useUnlink option has been added to use UNLINK instead of DEL and set to true by default.
  • The clearBatchSize option has been added to set the number of keys to delete in a single batch.
  • The clear() and delete() methods now use UNLINK instead of DEL. If you want to use DEL you can set the useUnlink option to false.
  • BREAKING: We no longer support redis sets. This is due to the fact that it caused significant performance issues and was not a good fit for the library.
  • BREAKING: YOUR PREVIOUS KEYS WILL NOT BE VALID. This is because of the fixe of the namespace support and how it is handled. Now, when using keyv with @keyv/redis as the storage adapter you can do the following:
import Keyv from 'keyv';
import KeyvRedis from '@keyv/redis';

const redis = new KeyvRedis('redis://user:pass@localhost:6379');
const keyv = new Keyv({ store: redis, namespace: 'my-namespace', useKeyPrefix: false });

This will make it so the storage adapter @keyv/redis will handle the namespace and not the keyv instance. If you leave it on it will just look duplicated like my-namespace:my-namespace:key.

About Redis Sets and its Support in v4

We no longer support redis sets. This is due to the fact that it caused significant performance issues and was not a good fit for the library.

Using with NestJS

You can integrate @keyv/redis with NestJS by creating a custom CacheModule. This allows you to use Keyv as a cache store in your application.

Note: cache-manager 7 depends on Keyv v5. It reads expiry data with store.get(key, { raw: true }), which a v6 Keyv ignores, so ttl() and the refresh logic in wrap() stop working with a v6 store. Check which Keyv version your cache-manager needs with npm view cache-manager dependencies.keyv. See Libraries That Embed Keyv v5.

1. Install Dependencies

npm install @keyv/redis keyv @nestjs/cache-manager cache-manager cacheable

2. Create a Cache Module

Create a file cache.module.ts:

import { Module } from '@nestjs/common';
import { CacheModule as NestCacheModule } from '@nestjs/cache-manager';
import { createKeyv } from '@keyv/redis';

@Module({
  imports: [
    NestCacheModule.registerAsync({
      useFactory: () => ({
        stores: [createKeyv('redis://localhost:6379')],
      }),
    }),
  ],
  providers: [],
  exports: [],
})
export class CacheModule {}

3. Import the Cache Module in AppModule

Update app.module.ts:

import { Module } from '@nestjs/common';
import { CacheModule } from './modules/config/cache/cache.module';

@Module({
  imports: [
    CacheModule, // Import your custom cache module
    // other modules...
  ],
})
export class AppModule {}

4. Create the Cache Service

Create a file cache.service.ts:

import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Inject, Injectable } from '@nestjs/common';
import { Cache } from 'cache-manager';

@Injectable()
export class CacheService {
  constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}

  async get<T>(key: string): Promise<T | undefined> {
    return this.cacheManager.get<T>(key);
  }

  async set<T>(key: string, value: T, ttl?: number): Promise<boolean> {
    await this.cacheManager.set(key, value, ttl);
    return true;
  }

  async delete(key: string): Promise<void> {
    await this.cacheManager.del(key);
  }
}

5. Register CacheService in CacheModule

Update cache.module.ts:

import { Module } from '@nestjs/common';
import { CacheModule as NestCacheModule } from '@nestjs/cache-manager';
import { createKeyv } from '@keyv/redis';
import { CacheService } from './services/cache.service';

@Module({
  imports: [
    NestCacheModule.registerAsync({
      useFactory: () => ({
        stores: [createKeyv('redis://localhost:6379')],
      }),
    }),
  ],
  providers: [CacheService],
  exports: [CacheService],
})
export class CacheModule {}

6. Import CacheModule in the Target Module (e.g. TaskModule)

import { Module } from '@nestjs/common';
import { TaskService } from './task.service';
import { TaskRepository } from './repositories/task.repository';
import { CacheModule } from 'src/modules/config/cache/cache.module';

@Module({
  imports: [CacheModule],
  providers: [TaskService, TaskRepository],
})
export class TaskModule {}

7. Using the Cache in a Service

import { Injectable, NotFoundException } from '@nestjs/common';
import { TaskRepository } from '../repositories/task.repository';
import { TaskDto } from '../dto/task.dto';
import { CacheService } from 'src/modules/config/cache/services/cache.service';

@Injectable()
export class TaskService {
  constructor(
    private readonly taskRepository: TaskRepository,
    private readonly cache: CacheService, // Inject the CacheService
  ) {}

  async findById(id: number): Promise<TaskDto> {
    const cacheKey = `task:${id}`;

    // 1. Try to get from cache
    const cached = await this.cache.get<TaskDto>(cacheKey);
    
	if (cached) {
      return cached;
    }

    // 2. If not found in cache, fetch from database
    const task = await this.taskRepository.findById(id);

    if (!task) {
      throw new NotFoundException('Task not found');
    }

    // 3. Set in cache for future requests
    await this.cache.set(cacheKey, task, 300 * 1000); // 5 minutes TTL
    return task;
  }
}

You can learn more about caching in NestJS in the official documentation.


License

MIT © Jared Wray