From 4fc1c95a16d88130670caf8bff3f33b5e5ca4245 Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Wed, 5 Mar 2025 09:08:42 +0000 Subject: [PATCH 1/8] DOC-4942 start ioredis migration page --- content/develop/clients/nodejs/migration.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 content/develop/clients/nodejs/migration.md diff --git a/content/develop/clients/nodejs/migration.md b/content/develop/clients/nodejs/migration.md new file mode 100644 index 0000000000..8da4067c32 --- /dev/null +++ b/content/develop/clients/nodejs/migration.md @@ -0,0 +1,18 @@ +--- +categories: +- docs +- develop +- stack +- oss +- rs +- rc +- oss +- kubernetes +- clients +description: Learn the differences between ioredis and node-redis +linkTitle: Migrate from ioredis +title: Migrate from ioredis +weight: 6 +--- + + From 3b30d00df4a233bdaceb080fb38bf2f50e9958ab Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Wed, 5 Mar 2025 13:02:42 +0000 Subject: [PATCH 2/8] DOC-4942 started features table --- content/develop/clients/nodejs/migration.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/content/develop/clients/nodejs/migration.md b/content/develop/clients/nodejs/migration.md index 8da4067c32..d47d8bd933 100644 --- a/content/develop/clients/nodejs/migration.md +++ b/content/develop/clients/nodejs/migration.md @@ -15,4 +15,14 @@ title: Migrate from ioredis weight: 6 --- +Redis previously recommended the [`ioredis`](https://github.com/redis/ioredis) +client library for development with [`Node.js`](https://nodejs.org/en), +but this library is now deprecated in favor of +[`node-redis`]({{< relref "/develop/clients/nodejs" >}}). This guide +outlines the main similarities and differences between the two libraries that +you should be aware of 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` +| Feature | `ioredis` | `node-redis` | +| :-- | :-- | :-- | +| Handling asynchronous command results | Callbacks and Promises | Promises only | \ No newline at end of file From a8591051de62e1c992e6de60c4de49500e6f9396 Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Thu, 6 Mar 2025 10:28:29 +0000 Subject: [PATCH 3/8] DOC-4942 started ioredis migration page --- content/develop/clients/nodejs/migration.md | 149 +++++++++++++++++++- content/develop/clients/nodejs/transpipe.md | 6 +- 2 files changed, 147 insertions(+), 8 deletions(-) diff --git a/content/develop/clients/nodejs/migration.md b/content/develop/clients/nodejs/migration.md index d47d8bd933..bf1b720a92 100644 --- a/content/develop/clients/nodejs/migration.md +++ b/content/develop/clients/nodejs/migration.md @@ -9,20 +9,157 @@ categories: - oss - kubernetes - clients -description: Learn the differences between ioredis and node-redis +description: Learn the differences between `ioredis` and `node-redis` linkTitle: Migrate from ioredis title: Migrate from ioredis weight: 6 --- Redis previously recommended the [`ioredis`](https://github.com/redis/ioredis) -client library for development with [`Node.js`](https://nodejs.org/en), +client library for development with [Node.js](https://nodejs.org/en), but this library is now deprecated in favor of [`node-redis`]({{< relref "/develop/clients/nodejs" >}}). This guide -outlines the main similarities and differences between the two libraries that -you should be aware of 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` +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` + +The table below summarizes how `ioredis` and `node-redis` implement some +key features of Redis. See the following sections for more information about +each feature. | Feature | `ioredis` | `node-redis` | | :-- | :-- | :-- | -| Handling asynchronous command results | Callbacks and Promises | Promises only | \ No newline at end of file +| [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 only | +| [Pipelining](#pipelining) | Automatic, or with `pipeline()` command | Automatic, or with `multi()` command | + +## Command case + +Command methods in `ioredis` are always lowercase. With `node-redis`, you can +use uppercase or camel case versions of the method names. + +```js +// ioredis +redis.hset("key", "field", "value"); + +// node-redis +redis.HSET("key", "field", "value"); + +// ...or +redis.hSet("key", "field", "value"); +``` + +## Command argument handling + +`ioredis` parses command arguments to strings and then passes them to +the server in order, like [`redis-cli`]({{< relref "/develop/tools/cli" >}}) +commands. + +```js +// Equivalent to the command line `SET key 100 EX 10`. +redis.set("key", 100, "EX", 10); +``` + +Arrays passed as arguments are flattened into individual elements and +objects are flattened into sequential key-value pairs: + +```js +// These commands are all equivalent. +redis.hset("user" { + name: "Bob", + age: 20, + description: "I am a programmer", +}); + +redis.hset("user", ["name", "Bob", "age", 20, "description", "I am a programmer"]); + +redis.hset("user", "name", "Bob", "age", 20, "description", "I am a programmer"); +``` + +`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: + +```js +// Equivalent to the command line `SET key 100 EX 10`. +redis.set("bike:5", "bike", {EX: 10}); +``` + +## Asynchronous command result handling {#async-result} + +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: + +```js +// Callback +redis.get("mykey", (err, result) => { + if (err) { + console.error(err); + } else { + console.log(result); + } +}); + +// Promise +redis.get("mykey").then( + (result) => { + console.log(result); + }, + (err) => { + console.error(err); + } +); +``` + +`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. + +## Pipelining + +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 +[Pipelines and transactions]({{< relref "/develop/clients/nodejs/transpipe" >}}) +for more information). + +You can also create a pipeline with explicit commands in both clients. +For `ioredis`, you use the `pipeline()` command with a chain of +commands, ending with `exec()` to run the pipeline: + +```js +// ioredis example +redis + .pipeline() + .set("foo", "1") + .get("foo") + .set("foo", "2") + .incr("foo") + .get("foo") + .exec(function (err, results) { + // Handle results or errors. + }); +``` + +For `node-redis`, the approach is similar, except that you call the `multi()` +command to start the pipeline and `execAsPipeline()` to run it: + +```js +redis.multi() + .set('seat:3', '#3') + .set('seat:4', '#4') + .set('seat:5', '#5') + .execAsPipeline() + .then((results) => { + // Handle array of results. + }, + (err) => { + // Handle errors. + }); +``` diff --git a/content/develop/clients/nodejs/transpipe.md b/content/develop/clients/nodejs/transpipe.md index 8934379e5b..d47deee9ba 100644 --- a/content/develop/clients/nodejs/transpipe.md +++ b/content/develop/clients/nodejs/transpipe.md @@ -30,8 +30,10 @@ There are two types of batch that you can use: ## Execute a pipeline -There are two ways to execute commands in a pipeline. The first is -to include the commands in a +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 From 635ac840c32ac5fa710aff9b4ee35cd4d8635c9e Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Thu, 6 Mar 2025 12:52:55 +0000 Subject: [PATCH 4/8] DOC-4942 small changes --- content/develop/clients/nodejs/migration.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/content/develop/clients/nodejs/migration.md b/content/develop/clients/nodejs/migration.md index bf1b720a92..8b14ec6754 100644 --- a/content/develop/clients/nodejs/migration.md +++ b/content/develop/clients/nodejs/migration.md @@ -53,8 +53,7 @@ redis.hSet("key", "field", "value"); ## Command argument handling `ioredis` parses command arguments to strings and then passes them to -the server in order, like [`redis-cli`]({{< relref "/develop/tools/cli" >}}) -commands. +the server, in a similar way to [`redis-cli`]({{< relref "/develop/tools/cli" >}}). ```js // Equivalent to the command line `SET key 100 EX 10`. From 05d7ef808d0e23c383728d83d911e16def294f71 Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Fri, 7 Mar 2025 13:19:40 +0000 Subject: [PATCH 5/8] DOC-4942 added more items and separated tables --- content/develop/clients/nodejs/connect.md | 78 ++++++++++++++++++++- content/develop/clients/nodejs/migration.md | 75 ++++++++++++++++++-- 2 files changed, 147 insertions(+), 6 deletions(-) diff --git a/content/develop/clients/nodejs/connect.md b/content/develop/clients/nodejs/connect.md index cb1d11e739..9857f47878 100644 --- a/content/develop/clients/nodejs/connect.md +++ b/content/develop/clients/nodejs/connect.md @@ -67,7 +67,7 @@ createClient({ ``` 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). -### Connect to a Redis cluster +## Connect to a Redis cluster To connect to a Redis cluster, use `createCluster`. @@ -97,7 +97,7 @@ console.log(value); // returns 'bar' await cluster.quit(); ``` -### Connect to your production Redis with TLS +## Connect to your production Redis with TLS When you deploy your application, use TLS and follow the [Redis security]({{< relref "/operate/oss_and_stack/management/security/" >}}) guidelines. @@ -127,3 +127,77 @@ await client.disconnect(); ``` 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). + +## Reconnect after disconnection + +By default, `node-redis` doesn't attempt to reconnect automatically when +the connection to the server is lost. However, you can 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`: (Default) Don't attempt to reconnect. +- `number`: Wait for this number of milliseconds and then attempt to reconnect. +- ``: Use a custom + function to decide how to handle reconnection. + +The custom function has the following signature: + +```js +(retries: number, cause: Error) => false | number | Error +``` + +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](https://en.wikipedia.org/wiki/Exponential_backoff) +strategy: + +```js +createClient({ + socket: { + reconnectStrategy: retries => { + // Generate a random jitter between 0 – 200 ms: + const jitter = Math.floor(Math.random() * 200); + + // Delay is an exponential back off, (times^2) * 50 ms, with a + // maximum value of 2000 ms: + const delay = Math.min(Math.pow(2, retries) * 50, 2000); + + return delay + jitter; + } + } +}); +``` + +## Connection events + +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. + +Use code like the following to respond to these events: + +```js +client.on('error', error => { + console.error(`Redis client error:`, error); +}); +``` diff --git a/content/develop/clients/nodejs/migration.md b/content/develop/clients/nodejs/migration.md index 8b14ec6754..cde4623020 100644 --- a/content/develop/clients/nodejs/migration.md +++ b/content/develop/clients/nodejs/migration.md @@ -23,18 +23,85 @@ 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` +## Comparison of `ioredis` and `node-redis` + The table below summarizes how `ioredis` and `node-redis` implement some key features of Redis. See the following sections for more information about each feature. +### Connection + +| 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 | Manual by default | +| [Connection events](#connection-events) | Emits `connect`, `ready`, `error`, and `close` events. | Emits `connect`, `ready`, `error`, `end`, and `reconnecting` events. | + +### Command handling + | 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 only | + +### Specific commands + +| Feature | `ioredis` | `node-redis` | +| :-- | :-- | :-- | + + +### Techniques + +| Feature | `ioredis` | `node-redis` | +| :-- | :-- | :-- | | [Pipelining](#pipelining) | Automatic, or with `pipeline()` command | Automatic, or with `multi()` command | -## Command case +## Details + +The sections below explain the points of comparison between `ioredis` and +`node-redis` in more detail. + +### Initial connection + +`ioredis` makes the connection to the Redis server when you create an instance +of the client object: + +```js +const Redis = require('ioredis'); + +// Connects to localhost:6379 on instantiation. +const redis = new Redis(); +``` + +`node-redis` requires you to call the `connect()` method on the client object +to make the connection: + +```js +import { createClient } from 'redis'; + +const client = await createClient(); +await client.connect(); // Requires explicit connection. +``` + +### Reconnection after a connection is lost {#reconnection} + +`ioredis` automatically attempts to reconnect if the connection +was lost due to an error. By default, `node-redis` doesn't attempt +to reconnect, but you can enable a custom reconnection strategy +when you create the client object. See +[Reconnect after disconnection]({{< relref "/develop/clients/nodejs/connect#reconnect-after-disconnection" >}}) +for more information. + +### Connection events + +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 []({{< relref "/develop/clients/nodejs/connect#connection-events" >}}) +for more information. + +### Command case Command methods in `ioredis` are always lowercase. With `node-redis`, you can use uppercase or camel case versions of the method names. @@ -50,7 +117,7 @@ redis.HSET("key", "field", "value"); redis.hSet("key", "field", "value"); ``` -## Command argument handling +### Command argument handling `ioredis` parses command arguments to strings and then passes them to the server, in a similar way to [`redis-cli`]({{< relref "/develop/tools/cli" >}}). @@ -86,7 +153,7 @@ the method arguments you pass: redis.set("bike:5", "bike", {EX: 10}); ``` -## Asynchronous command result handling {#async-result} +### Asynchronous command result handling {#async-result} All commands for both `ioredis` and `node-redis` are executed asynchronously. `ioredis` supports both callbacks and @@ -119,7 +186,7 @@ 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. -## Pipelining +### Pipelining Both `ioredis` and `node-redis` will pipeline commands automatically if they are executed in the same "tick" of the From 0b9090d650c654b45258217472883ac3e4e005ce Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Fri, 21 Mar 2025 13:46:46 +0000 Subject: [PATCH 6/8] DOC-4942 SETNX command details --- content/develop/clients/nodejs/migration.md | 27 +++++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/content/develop/clients/nodejs/migration.md b/content/develop/clients/nodejs/migration.md index cde4623020..5fc9ca3d47 100644 --- a/content/develop/clients/nodejs/migration.md +++ b/content/develop/clients/nodejs/migration.md @@ -45,17 +45,17 @@ each feature. | [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 only | -### Specific commands +### Techniques | Feature | `ioredis` | `node-redis` | | :-- | :-- | :-- | +| [Pipelining](#pipelining) | Automatic, or with `pipeline()` command | Automatic, or with `multi()` command | +### Specific commands -### Techniques - -| Feature | `ioredis` | `node-redis` | +| Command | `ioredis` | `node-redis` | | :-- | :-- | :-- | -| [Pipelining](#pipelining) | Automatic, or with `pipeline()` command | Automatic, or with `multi()` command | +| [`SETNX`](#setnx-command) | Supported explicitly | Supported as an option for `SET` | ## Details @@ -229,3 +229,20 @@ redis.multi() // Handle errors. }); ``` + +### `SETNX` command + +`ioredis` implements the [`SETNX`]({{< relref "/commands/setnx" >}}) +command with an explicit method: + +```js +client.setnx("bike:1", "bike"); +``` + +`node-redis` doesn't provide a `SETNX` method but implements the same +functionality with the `NX` option to the [`SET`]({{< relref "/commands/set" >}}) +command: + +```js +await client.set("bike:1", "bike", {'NX': true}); +``` \ No newline at end of file From 674e5860c5ccc82f5c436a54c093c5e2e088c675 Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Mon, 24 Mar 2025 13:59:00 +0000 Subject: [PATCH 7/8] DOC-4942 added scan iters and pub/sub --- content/develop/clients/nodejs/migration.md | 91 ++++++++++++++++++--- 1 file changed, 80 insertions(+), 11 deletions(-) diff --git a/content/develop/clients/nodejs/migration.md b/content/develop/clients/nodejs/migration.md index 5fc9ca3d47..6c773b8bbd 100644 --- a/content/develop/clients/nodejs/migration.md +++ b/content/develop/clients/nodejs/migration.md @@ -50,6 +50,8 @@ each feature. | 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 | ### Specific commands @@ -201,16 +203,15 @@ commands, ending with `exec()` to run the pipeline: ```js // ioredis example -redis - .pipeline() - .set("foo", "1") - .get("foo") - .set("foo", "2") - .incr("foo") - .get("foo") - .exec(function (err, results) { - // Handle results or errors. - }); +redis.pipeline() + .set("foo", "1") + .get("foo") + .set("foo", "2") + .incr("foo") + .get("foo") + .exec(function (err, results) { + // Handle results or errors. + }); ``` For `node-redis`, the approach is similar, except that you call the `multi()` @@ -230,6 +231,74 @@ redis.multi() }); ``` +### Scan iteration + +`ioredis` supports the `scanStream()` method to create a readable stream +from the set of keys returned by the [`SCAN`]({{< relref "/commands/scan" >}}) +command: + +```js +const redis = new Redis(); +// Create a readable stream (object mode) +const stream = redis.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"); +}); +``` + +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`]({{< relref "/commands/mget" >}}) and +other multi-key commands): + +```js +for await (const keys of client.scanIterator()) { + const values = await client.mGet(keys); +} +``` + +### Subscribing to channels + +`ioredis` reports incoming pub/sub messages with a `message` +event on the client object (see +[Publish/subscribe]({{< relref "/develop/interact/pubsub" >}}) for more +information about messages): + +```js +client.on('message', (channel, message) => { + console.log(Received message from ${channel}: ${message}); +}); +``` + +With `node-redis`, you use the `subscribe()` command to register the +message callback. Also, when you use a connection to subscribe, the +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: + +```js +const subscriber = client.duplicate(); +await subscriber.connect(); + +await subscriber.subscribe('channel', (message) => { + console.log(Received message: ${message}); +}); +``` + ### `SETNX` command `ioredis` implements the [`SETNX`]({{< relref "/commands/setnx" >}}) @@ -245,4 +314,4 @@ command: ```js await client.set("bike:1", "bike", {'NX': true}); -``` \ No newline at end of file +``` From c1f649fe502d703d45fe4d08da169b4bffcfc50e Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Mon, 24 Mar 2025 16:05:44 +0000 Subject: [PATCH 8/8] DOC-4942 added remaining commands plus fixes --- content/develop/clients/nodejs/migration.md | 134 ++++++++++++++------ 1 file changed, 96 insertions(+), 38 deletions(-) diff --git a/content/develop/clients/nodejs/migration.md b/content/develop/clients/nodejs/migration.md index 6c773b8bbd..b89fbb19ad 100644 --- a/content/develop/clients/nodejs/migration.md +++ b/content/develop/clients/nodejs/migration.md @@ -9,7 +9,7 @@ categories: - oss - kubernetes - clients -description: Learn the differences between `ioredis` and `node-redis` +description: Discover the differences between `ioredis` and `node-redis`. linkTitle: Migrate from ioredis title: Migrate from ioredis weight: 6 @@ -21,11 +21,11 @@ but this library is now deprecated in favor of [`node-redis`]({{< relref "/develop/clients/nodejs" >}}). 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` +start a new Node.js project or migrate an existing `ioredis` project to `node-redis`. ## Comparison of `ioredis` and `node-redis` -The table below summarizes how `ioredis` and `node-redis` implement some +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. @@ -35,7 +35,7 @@ each feature. | :-- | :-- | :-- | | [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 | Manual by default | -| [Connection events](#connection-events) | Emits `connect`, `ready`, `error`, and `close` events. | Emits `connect`, `ready`, `error`, `end`, and `reconnecting` events. | +| [Connection events](#connection-events) | Emits `connect`, `ready`, `error`, and `close` events | Emits `connect`, `ready`, `error`, `end`, and `reconnecting` events | ### Command handling @@ -44,13 +44,14 @@ each feature. | [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 only | +| [Arbitrary command execution](#arbitrary-command-execution) | Uses the `call()` method | Uses the `sendCommand()` method | ### Techniques | 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 | +| [Scan iteration](#scan-iteration) | Uses `scanStream()`, etc | Uses `scanIterator()`, etc | | [Subscribing to channels](#subscribing-to-channels) | Uses `client.on('message', ...)` event | Uses `subscribe(...)` command | ### Specific commands @@ -58,6 +59,8 @@ each feature. | 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 ## Details @@ -70,10 +73,10 @@ The sections below explain the points of comparison between `ioredis` and of the client object: ```js -const Redis = require('ioredis'); +const client = require('ioredis'); // Connects to localhost:6379 on instantiation. -const redis = new Redis(); +const client = new Redis(); ``` `node-redis` requires you to call the `connect()` method on the client object @@ -100,7 +103,7 @@ for more information. 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 []({{< relref "/develop/clients/nodejs/connect#connection-events" >}}) +See [Connection events]({{< relref "/develop/clients/nodejs/connect#connection-events" >}}) for more information. ### Command case @@ -110,13 +113,13 @@ use uppercase or camel case versions of the method names. ```js // ioredis -redis.hset("key", "field", "value"); +client.hset('key', 'field', 'value'); // node-redis -redis.HSET("key", "field", "value"); +client.HSET('key', 'field', 'value'); // ...or -redis.hSet("key", "field", "value"); +client.hSet('key', 'field', 'value'); ``` ### Command argument handling @@ -126,7 +129,7 @@ the server, in a similar way to [`redis-cli`]({{< relref "/develop/tools/cli" >} ```js // Equivalent to the command line `SET key 100 EX 10`. -redis.set("key", 100, "EX", 10); +client.set('key', 100, 'EX', 10); ``` Arrays passed as arguments are flattened into individual elements and @@ -134,15 +137,15 @@ objects are flattened into sequential key-value pairs: ```js // These commands are all equivalent. -redis.hset("user" { - name: "Bob", +client.hset('user' { + name: 'Bob', age: 20, - description: "I am a programmer", + description: 'I am a programmer', }); -redis.hset("user", ["name", "Bob", "age", 20, "description", "I am a programmer"]); +client.hset('user', ['name', 'Bob', 'age', 20, 'description', 'I am a programmer']); -redis.hset("user", "name", "Bob", "age", 20, "description", "I am a programmer"); +client.hset('user', 'name', 'Bob', 'age', 20, 'description', 'I am a programmer'); ``` `node-redis` uses predefined formats for command arguments. These include specific @@ -151,8 +154,8 @@ of the CLI command. Internally, `node-redis` constructs the correct command usin the method arguments you pass: ```js -// Equivalent to the command line `SET key 100 EX 10`. -redis.set("bike:5", "bike", {EX: 10}); +// Equivalent to the command line `SET bike:5 bike EX 10`. +client.set('bike:5', 'bike', {EX: 10}); ``` ### Asynchronous command result handling {#async-result} @@ -164,7 +167,7 @@ return values to respond to command results: ```js // Callback -redis.get("mykey", (err, result) => { +client.get('mykey', (err, result) => { if (err) { console.error(err); } else { @@ -173,7 +176,7 @@ redis.get("mykey", (err, result) => { }); // Promise -redis.get("mykey").then( +client.get('mykey').then( (result) => { console.log(result); }, @@ -188,27 +191,54 @@ 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. +### Arbitrary command execution + +`ioredis` lets you issue arbitrary commands in a similar format to +[`redis-cli`]({{< relref "/develop/tools/cli" >}}) using the `call()` +command: + +```js +await client.call('JSON.SET', 'doc', "$", '{"f1": {"a":1}, "f2":{"a":2}}'); +``` + +In `node-redis`, you can get the same effect outside a transaction using `sendCommand()`: + +```js +await client.sendCommand(['hset', 'hash2', 'number', '3']); +``` + +Within a transaction, use `addCommand()` to include arbitrary commands. Note that +you can freely mix `addCommand()` calls with standard commands in the same +transaction: + +```js +const responses = await client.multi() + .addCommand(['hset', 'hash3', 'number', '4']) + .hGet('hash3', 'number') + .exec(); +``` + ### Pipelining 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 -[Pipelines and transactions]({{< relref "/develop/clients/nodejs/transpipe" >}}) +[Execute a pipeline]({{< relref "/develop/clients/nodejs/transpipe#execute-a-pipeline" >}}) for more information). You can also create a pipeline with explicit commands in both clients. -For `ioredis`, you use the `pipeline()` command with a chain of +With `ioredis`, you use the `pipeline()` command with a chain of commands, ending with `exec()` to run the pipeline: ```js // ioredis example -redis.pipeline() - .set("foo", "1") - .get("foo") - .set("foo", "2") - .incr("foo") - .get("foo") +client.pipeline() + .set('foo', '1') + .get('foo') + .set('foo', '2') + .incr('foo') + .get('foo') .exec(function (err, results) { // Handle results or errors. }); @@ -218,7 +248,7 @@ For `node-redis`, the approach is similar, except that you call the `multi()` command to start the pipeline and `execAsPipeline()` to run it: ```js -redis.multi() +client.multi() .set('seat:3', '#3') .set('seat:4', '#4') .set('seat:5', '#5') @@ -238,10 +268,10 @@ from the set of keys returned by the [`SCAN`]({{< relref "/commands/scan" >}}) command: ```js -const redis = new Redis(); +const client = new Redis(); // Create a readable stream (object mode) -const stream = redis.scanStream(); -stream.on("data", (resultKeys) => { +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. @@ -249,8 +279,8 @@ stream.on("data", (resultKeys) => { console.log(resultKeys[i]); } }); -stream.on("end", () => { - console.log("all keys have been visited"); +stream.on('end', () => { + console.log('all keys have been visited'); }); ``` @@ -268,6 +298,7 @@ other multi-key commands): ```js for await (const keys of client.scanIterator()) { const values = await client.mGet(keys); + // Process values... } ``` @@ -285,7 +316,7 @@ client.on('message', (channel, message) => { ``` With `node-redis`, you use the `subscribe()` command to register the -message callback. Also, when you use a connection to subscribe, 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: @@ -305,7 +336,7 @@ await subscriber.subscribe('channel', (message) => { command with an explicit method: ```js -client.setnx("bike:1", "bike"); +client.setnx('bike:1', 'bike'); ``` `node-redis` doesn't provide a `SETNX` method but implements the same @@ -313,5 +344,32 @@ functionality with the `NX` option to the [`SET`]({{< relref "/commands/set" >}} command: ```js -await client.set("bike:1", "bike", {'NX': true}); +await client.set('bike:1', 'bike', {'NX': true}); +``` + +### `HMSET` command + +The [`HMSET`]({{< relref "/commands/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`]({{< relref "/commands/hset" >}}) command with +multiple key-value pairs. See the [`HSET`]({{< relref "/commands/hset" >}}) +command page for more information. + +### `CONFIG` command + +`ioredis` supports a `config()` method to set or get server configuration +options: + +```js +client.config('SET', 'notify-keyspace-events', 'KEA'); +``` + +`node-redis` doesn't have a `config()` method, but instead supports the +standard commands [`configSet()`]({{< relref "/commands/config-set" >}}), +[`configGet()`]({{< relref "/commands/config-get" >}}), +[`configResetStat()`]({{< relref "/commands/config-resetstat" >}}), and +[`configRewrite`]({{< relref "/commands/config-rewrite" >}}): + +```js +await client.configSet('maxclients', '2000'); ```