content/develop/clients/nodejs/migration.md
Redis previously recommended the [ioredis]({{< relref "/develop/clients/ioredis" >}})
client library for development with Node.js,
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.
ioredis and node-redisThe tables below summarize 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 |
|---|---|---|
| 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 | Automatic by default | Automatic by default and configurable |
| Connection events | Emits connect, ready, error, and close events | Emits connect, ready, error, end, and reconnecting events |
| Feature | ioredis | node-redis |
|---|---|---|
| Command case | Lowercase only (eg, hset) | Uppercase or camel case (eg, HSET or hSet) |
| Command argument handling | Argument objects flattened and items passed directly | Argument objects parsed to generate correct argument list |
| Asynchronous command result handling | Callbacks and Promises | Promises (but supports callbacks via Legacy Mode) |
| Arbitrary command execution | Uses the call() method | Uses the sendCommand() method |
| Feature | ioredis | node-redis |
|---|---|---|
| Pipelining | Automatic, or with pipeline() command | Automatic, or with multi() command |
| Scan iteration | Uses scanStream(), etc | Uses scanIterator(), etc |
| Subscribing to channels | Uses client.on('message', ...) event | Uses subscribe(...) command |
| Command | ioredis | node-redis |
|---|---|---|
SETNX | Supported explicitly | Supported as an option for SET |
HMSET | Supported explicitly | Supported with standard HSET functionality |
CONFIG | Supported explicitly | Supported with separate configGet(), configSet(), etc |
The sections below explain the points of comparison between ioredis and
node-redis in more detail.
ioredis makes the connection to the Redis server when you create an instance
of the client object:
const client = require('ioredis');
// Connects to localhost:6379 on instantiation.
const client = new Redis();
node-redis requires you to call the connect() method on the client object
to make the connection:
import { createClient } from 'redis';
const client = createClient();
await client.connect(); // Requires explicit connection.
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]({{< relref "/develop/clients/nodejs/connect#reconnect-after-disconnection" >}})
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 [Connection events]({{< relref "/develop/clients/nodejs/connect#connection-events" >}})
for more information.
Command methods in ioredis are always lowercase. With node-redis, you can
use uppercase or camel case versions of the method names.
// ioredis
client.hset('key', 'field', 'value');
// node-redis
client.HSET('key', 'field', 'value');
// ...or
client.hSet('key', 'field', 'value');
ioredis parses command arguments to strings and then passes them to
the server, in a similar way to [redis-cli]({{< relref "/develop/tools/cli" >}}).
// Equivalent to the command line `SET key 100 EX 10`.
client.set('key', 100, 'EX', 10);
Arrays passed as arguments are flattened into individual elements and objects are flattened into sequential key-value pairs:
// 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');
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:
// Equivalent to the command line `SET bike:5 bike EX 10`.
client.set('bike:5', 'bike', {EX: 10});
All commands for both ioredis and node-redis are executed
asynchronously. ioredis supports both callbacks and
Promise
return values to respond to command results:
// 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);
}
);
node-redis supports only Promise objects for results, so
you must always use a then() handler or the
await
operator to receive them. However, you can still use callbacks with the legacy mode if you need them:
// 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);
}
});
ioredis lets you issue arbitrary commands in a similar format to
[redis-cli]({{< relref "/develop/tools/cli" >}}) using the call()
command:
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():
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:
const responses = await client.multi()
.addCommand(['hset', 'hash3', 'number', '4'])
.hGet('hash3', 'number')
.exec();
Both ioredis and node-redis will pipeline commands automatically if
they are executed in the same "tick" of the
event loop
(see
[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.
With ioredis, you use the pipeline() command with a chain of
commands, ending with exec() to run the pipeline:
// ioredis example
client.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:
client.multi()
.set('seat:3', '#3')
.set('seat:4', '#4')
.set('seat:5', '#5')
.execAsPipeline()
.then((results) => {
// Handle array of results.
},
(err) => {
// Handle errors.
});
ioredis supports the scanStream() method to create a readable stream
from the set of keys returned by the [SCAN]({{< relref "/commands/scan" >}})
command:
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');
});
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):
for await (const keys of client.scanIterator()) {
const values = await client.mGet(keys);
// Process values...
}
ioredis reports incoming pub/sub messages with a message
event on the client object (see
[Publish/subscribe]({{< relref "/develop/pubsub" >}}) for more
information about messages):
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, 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:
const subscriber = client.duplicate();
await subscriber.connect();
await subscriber.subscribe('channel', (message) => {
console.log(Received message: ${message});
});
SETNX commandioredis implements the [SETNX]({{< relref "/commands/setnx" >}})
command with an explicit method:
client.setnx('bike:1', 'bike');
node-redis provides a SETNX method but this command is deprecated. Use the NX option to the [SET]({{< relref "/commands/set" >}})
command to get the same functionality as SETNX:
await client.set('bike:1', 'bike', {'NX': true});
HMSET commandThe [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 commandioredis supports a config() method to set or get server configuration
options:
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" >}}):
await client.configSet('maxclients', '2000');