{
  "schema_version": 2,
  "id": "develop/clients/nodejs/migration",
  "title": "Migrate from ioredis",
  "url": "https://redis.io/docs/latest/develop/clients/nodejs/migration/",
  "summary": "Discover the differences between `ioredis` and `node-redis`.",
  "tags": [
    "docs",
    "develop",
    "stack",
    "oss",
    "rs",
    "rc",
    "oss",
    "kubernetes",
    "clients"
  ],
  "last_updated": "2026-09-08T15:03:39+01:00",
  "page_type": "content",
  "content_hash": "0f3fcdc4cc7ca2a9fd9bbb1bff8a4e3c63614d8e6bc03cfc8e14b7a37bcf919e",
  "sections": [
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "Redis previously recommended the [`ioredis`](https://redis.io/docs/latest/develop/clients/ioredis)\nclient library for development with [Node.js](https://nodejs.org/en),\nbut this library is now deprecated in favor of\n[`node-redis`](https://redis.io/docs/latest/develop/clients/nodejs). This guide\noutlines the main similarities and differences between the two libraries.\nYou may find this information useful if you are an `ioredis` user and you want to\nstart a new Node.js project or migrate an existing `ioredis` project to `node-redis`."
    },
    {
      "id": "comparison-of-ioredis-and-node-redis",
      "title": "Comparison of `ioredis` and `node-redis`",
      "role": "content",
      "text": "The tables below summarize how `ioredis` and `node-redis` implement some\nkey features of Redis. See the following sections for more information about\neach feature."
    },
    {
      "id": "connection",
      "title": "Connection",
      "role": "content",
      "text": "| Feature | `ioredis` | `node-redis` |\n| :-- | :-- | :-- |\n| [Initial connection](#initial-connection) | Happens when you create a client instance | Requires you to call a method on the client instance |\n| [Reconnection after a connection is lost](#reconnection) | Automatic by default | Automatic by default and configurable |\n| [Connection events](#connection-events) | Emits `connect`, `ready`, `error`, and `close` events | Emits `connect`, `ready`, `error`, `end`, and `reconnecting` events |"
    },
    {
      "id": "command-handling",
      "title": "Command handling",
      "role": "syntax",
      "text": "| Feature | `ioredis` | `node-redis` |\n| :-- | :-- | :-- |\n| [Command case](#command-case) | Lowercase only (eg, `hset`) | Uppercase or camel case (eg, `HSET` or `hSet`) |\n| [Command argument handling](#command-argument-handling) | Argument objects flattened and items passed directly | Argument objects parsed to generate correct argument list |\n| [Asynchronous command result handling](#async-result) | Callbacks and Promises | Promises (but supports callbacks via Legacy Mode) |\n| [Arbitrary command execution](#arbitrary-command-execution) | Uses the `call()` method | Uses the `sendCommand()` method |"
    },
    {
      "id": "techniques",
      "title": "Techniques",
      "role": "content",
      "text": "| Feature | `ioredis` | `node-redis` |\n| :-- | :-- | :-- |\n| [Pipelining](#pipelining) | Automatic, or with `pipeline()` command | Automatic, or with `multi()` command |\n| [Scan iteration](#scan-iteration) | Uses `scanStream()`, etc | Uses `scanIterator()`, etc |\n| [Subscribing to channels](#subscribing-to-channels) | Uses `client.on('message', ...)` event | Uses `subscribe(...)` command |"
    },
    {
      "id": "specific-commands",
      "title": "Specific commands",
      "role": "content",
      "text": "| Command | `ioredis` | `node-redis` |\n| :-- | :-- | :-- |\n| [`SETNX`](#setnx-command) | Supported explicitly | Supported as an option for `SET` |\n| [`HMSET`](#hmset-command) | Supported explicitly | Supported with standard `HSET` functionality |\n| [`CONFIG`](#config-command) | Supported explicitly | Supported with separate `configGet()`, `configSet()`, etc |co"
    },
    {
      "id": "details",
      "title": "Details",
      "role": "content",
      "text": "The sections below explain the points of comparison between `ioredis` and\n`node-redis` in more detail."
    },
    {
      "id": "initial-connection",
      "title": "Initial connection",
      "role": "content",
      "text": "`ioredis` makes the connection to the Redis server when you create an instance\nof the client object:\n\n[code example]\n\n`node-redis` requires you to call the `connect()` method on the client object\nto make the connection:\n\n[code example]"
    },
    {
      "id": "reconnection",
      "title": "Reconnection after a connection is lost",
      "role": "content",
      "text": "Both `ioredis` and `node-redis` automatically attempt to reconnect if the connection\nwas lost due to an error. `node-redis` also lets you add a custom reconnection strategy\nwhen you create the client object. See\n[Reconnect after disconnection](https://redis.io/docs/latest/develop/clients/nodejs/connect#reconnect-after-disconnection)\nfor more information."
    },
    {
      "id": "connection-events",
      "title": "Connection events",
      "role": "content",
      "text": "The `connect`, `ready`, `error`, and `close` events that `ioredis` emits\nare equivalent to the `connect`, `ready`, `error`, and `end` events\nin `node-redis`, but `node-redis` also emits a `reconnecting` event.\nSee [Connection events](https://redis.io/docs/latest/develop/clients/nodejs/connect#connection-events)\nfor more information."
    },
    {
      "id": "command-case",
      "title": "Command case",
      "role": "syntax",
      "text": "Command methods in `ioredis` are always lowercase. With `node-redis`, you can\nuse uppercase or camel case versions of the method names.\n\n[code example]"
    },
    {
      "id": "command-argument-handling",
      "title": "Command argument handling",
      "role": "syntax",
      "text": "`ioredis` parses command arguments to strings and then passes them to\nthe server, in a similar way to [`redis-cli`](https://redis.io/docs/latest/develop/tools/cli).\n\n[code example]\n\nArrays passed as arguments are flattened into individual elements and\nobjects are flattened into sequential key-value pairs:\n\n[code example]\n\n`node-redis` uses predefined formats for command arguments. These include specific\nclasses for command options that generally don't correspond to the syntax\nof the CLI command. Internally, `node-redis` constructs the correct command using\nthe method arguments you pass:\n\n[code example]"
    },
    {
      "id": "async-result",
      "title": "Asynchronous command result handling",
      "role": "content",
      "text": "All commands for both `ioredis` and `node-redis` are executed\nasynchronously. `ioredis` supports both callbacks and\n[`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)\nreturn values to respond to command results:\n\n[code example]\n\n`node-redis` supports only `Promise` objects for results, so\nyou must always use a `then()` handler or the\n[`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await)\noperator to receive them. However, you can still use callbacks with the legacy mode if you need them:\n\n[code example]"
    },
    {
      "id": "arbitrary-command-execution",
      "title": "Arbitrary command execution",
      "role": "content",
      "text": "`ioredis` lets you issue arbitrary commands in a similar format to\n[`redis-cli`](https://redis.io/docs/latest/develop/tools/cli) using the `call()`\ncommand:\n\n[code example]\n\nIn `node-redis`, you can get the same effect outside a transaction using `sendCommand()`:\n\n[code example]\n\nWithin a transaction, use `addCommand()` to include arbitrary commands. Note that\nyou can freely mix `addCommand()` calls with standard commands in the same\ntransaction:\n\n[code example]"
    },
    {
      "id": "pipelining",
      "title": "Pipelining",
      "role": "content",
      "text": "Both `ioredis` and `node-redis` will pipeline commands automatically if\nthey are executed in the same \"tick\" of the\n[event loop](https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick#what-is-the-event-loop)\n(see\n[Execute a pipeline](https://redis.io/docs/latest/develop/clients/nodejs/transpipe#execute-a-pipeline)\nfor more information).\n\nYou can also create a pipeline with explicit commands in both clients.\nWith `ioredis`, you use the `pipeline()` command with a chain of\ncommands, ending with `exec()` to run the pipeline:\n\n[code example]\n\nFor `node-redis`, the approach is similar, except that you call the `multi()`\ncommand to start the pipeline and `execAsPipeline()` to run it:\n\n[code example]"
    },
    {
      "id": "scan-iteration",
      "title": "Scan iteration",
      "role": "content",
      "text": "`ioredis` supports the `scanStream()` method to create a readable stream\nfrom the set of keys returned by the [`SCAN`](https://redis.io/docs/latest/commands/scan)\ncommand:\n\n[code example]\n\nYou can also use the similar `hscanStream()`, `sscanStream()`, and\n`zscanStream()` to iterate over the items of a hash, set, or sorted set,\nrespectively.\n\n`node-redis` handles scan iteration using the `scanIterator()` method\n(and the corresponding `hscanIterator()`, `sscanIterator()`, and\n`zscanIterator()` methods). These return a collection object for\neach page scanned by the cursor (this can be helpful to improve\nefficiency using [`MGET`](https://redis.io/docs/latest/commands/mget) and\nother multi-key commands):\n\n[code example]"
    },
    {
      "id": "subscribing-to-channels",
      "title": "Subscribing to channels",
      "role": "content",
      "text": "`ioredis` reports incoming pub/sub messages with a `message`\nevent on the client object (see\n[Publish/subscribe](https://redis.io/docs/latest/develop/pubsub) for more\ninformation about messages):\n\n[code example]\n\nWith `node-redis`, you use the `subscribe()` command to register the\nmessage callback. Also, when you use a connection to subscribe, that\nconnection can't issue any other commands, so you must create a\ndedicated connection for the subscription. Use the `client.duplicate()`\nmethod to create a new connection with the same settings as the original:\n\n[code example]"
    },
    {
      "id": "setnx-command",
      "title": "`SETNX` command",
      "role": "content",
      "text": "`ioredis` implements the  [`SETNX`](https://redis.io/docs/latest/commands/setnx)\ncommand with an explicit method:\n\n[code example]\n\n`node-redis` provides a `SETNX` method but this command is deprecated. Use the `NX` option to the [`SET`](https://redis.io/docs/latest/commands/set)\ncommand to get the same functionality as `SETNX`:\n\n[code example]"
    },
    {
      "id": "hmset-command",
      "title": "`HMSET` command",
      "role": "content",
      "text": "The [`HMSET`](https://redis.io/docs/latest/commands/hmset) command has been deprecated\nsince Redis v4.0.0, but it is still supported by `ioredis`. With `node-redis`\nyou should use the [`HSET`](https://redis.io/docs/latest/commands/hset) command with\nmultiple key-value pairs. See the [`HSET`](https://redis.io/docs/latest/commands/hset)\ncommand page for more information."
    },
    {
      "id": "config-command",
      "title": "`CONFIG` command",
      "role": "content",
      "text": "`ioredis` supports a `config()` method to set or get server configuration\noptions:\n\n[code example]\n\n`node-redis` doesn't have a `config()` method, but instead supports the\nstandard commands [`configSet()`](https://redis.io/docs/latest/commands/config-set),\n[`configGet()`](https://redis.io/docs/latest/commands/config-get),\n[`configResetStat()`](https://redis.io/docs/latest/commands/config-resetstat), and\n[`configRewrite`](https://redis.io/docs/latest/commands/config-rewrite):\n\n[code example]"
    }
  ],
  "examples": [
    {
      "id": "initial-connection-ex0",
      "language": "js",
      "code": "const client = require('ioredis');\n\n// Connects to localhost:6379 on instantiation.\nconst client = new Redis();",
      "section_id": "initial-connection"
    },
    {
      "id": "initial-connection-ex1",
      "language": "js",
      "code": "import { createClient } from 'redis';\n\nconst client = createClient();\nawait client.connect(); // Requires explicit connection.",
      "section_id": "initial-connection"
    },
    {
      "id": "command-case-ex0",
      "language": "js",
      "code": "// ioredis\nclient.hset('key', 'field', 'value');\n\n// node-redis\nclient.HSET('key', 'field', 'value');\n\n// ...or\nclient.hSet('key', 'field', 'value');",
      "section_id": "command-case"
    },
    {
      "id": "command-argument-handling-ex0",
      "language": "js",
      "code": "// Equivalent to the command line `SET key 100 EX 10`.\nclient.set('key', 100, 'EX', 10);",
      "section_id": "command-argument-handling"
    },
    {
      "id": "command-argument-handling-ex1",
      "language": "js",
      "code": "// These commands are all equivalent.\nclient.hset('user', {\n    name: 'Bob',\n    age: 20,\n    description: 'I am a programmer',\n});\n\nclient.hset('user', ['name', 'Bob', 'age', 20, 'description', 'I am a programmer']);\n\nclient.hset('user', 'name', 'Bob', 'age', 20, 'description', 'I am a programmer');",
      "section_id": "command-argument-handling"
    },
    {
      "id": "command-argument-handling-ex2",
      "language": "js",
      "code": "// Equivalent to the command line `SET bike:5 bike EX 10`.\nclient.set('bike:5', 'bike', {EX: 10});",
      "section_id": "command-argument-handling"
    },
    {
      "id": "async-result-ex0",
      "language": "js",
      "code": "// Callback\nclient.get('mykey', (err, result) => {\n  if (err) {\n    console.error(err);\n  } else {\n    console.log(result);\n  }\n});\n\n// Promise\nclient.get('mykey').then(\n    (result) => {\n        console.log(result);\n    },\n    (err) => {\n        console.error(err);\n    }\n);",
      "section_id": "async-result"
    },
    {
      "id": "async-result-ex1",
      "language": "js",
      "code": "// Promise\nawait client.set('mykey', 'myvalue');\n\n// Callback\nconst legacyClient = client.legacy();\nlegacyClient.set(\"mykey\", \"myvalue\", (err, result) => {\n  if (err) {\n    console.error(err);\n  } else {\n    console.log(result);\n  }\n});",
      "section_id": "async-result"
    },
    {
      "id": "arbitrary-command-execution-ex0",
      "language": "js",
      "code": "await client.call('JSON.SET', 'doc', \"$\", '{\"f1\": {\"a\":1}, \"f2\":{\"a\":2}}');",
      "section_id": "arbitrary-command-execution"
    },
    {
      "id": "arbitrary-command-execution-ex1",
      "language": "js",
      "code": "await client.sendCommand(['hset', 'hash2', 'number', '3']);",
      "section_id": "arbitrary-command-execution"
    },
    {
      "id": "arbitrary-command-execution-ex2",
      "language": "js",
      "code": "const responses = await client.multi()\n  .addCommand(['hset', 'hash3', 'number', '4'])\n  .hGet('hash3', 'number')\n  .exec();",
      "section_id": "arbitrary-command-execution"
    },
    {
      "id": "pipelining-ex0",
      "language": "js",
      "code": "// ioredis example\nclient.pipeline()\n    .set('foo', '1')\n    .get('foo')\n    .set('foo', '2')\n    .incr('foo')\n    .get('foo')\n    .exec(function (err, results) {\n      // Handle results or errors.\n    });",
      "section_id": "pipelining"
    },
    {
      "id": "pipelining-ex1",
      "language": "js",
      "code": "client.multi()\n    .set('seat:3', '#3')\n    .set('seat:4', '#4')\n    .set('seat:5', '#5')\n    .execAsPipeline()\n    .then((results) => {\n        // Handle array of results.\n    },\n    (err) => {\n        // Handle errors.\n    });",
      "section_id": "pipelining"
    },
    {
      "id": "scan-iteration-ex0",
      "language": "js",
      "code": "const client = new Redis();\n// Create a readable stream (object mode)\nconst stream = client.scanStream();\nstream.on('data', (resultKeys) => {\n  // `resultKeys` is an array of strings representing key names.\n  // Note that resultKeys may contain 0 keys, and that it will sometimes\n  // contain duplicates due to SCAN's implementation in Redis.\n  for (let i = 0; i < resultKeys.length; i++) {\n    console.log(resultKeys[i]);\n  }\n});\nstream.on('end', () => {\n  console.log('all keys have been visited');\n});",
      "section_id": "scan-iteration"
    },
    {
      "id": "scan-iteration-ex1",
      "language": "js",
      "code": "for await (const keys of client.scanIterator()) {\n  const values = await client.mGet(keys);\n  // Process values...\n}",
      "section_id": "scan-iteration"
    },
    {
      "id": "subscribing-to-channels-ex0",
      "language": "js",
      "code": "client.on('message', (channel, message) => {\n   console.log(Received message from ${channel}: ${message});\n});",
      "section_id": "subscribing-to-channels"
    },
    {
      "id": "subscribing-to-channels-ex1",
      "language": "js",
      "code": "const subscriber = client.duplicate();\nawait subscriber.connect();\n\nawait subscriber.subscribe('channel', (message) => {\n   console.log(Received message: ${message});\n});",
      "section_id": "subscribing-to-channels"
    },
    {
      "id": "setnx-command-ex0",
      "language": "js",
      "code": "client.setnx('bike:1', 'bike');",
      "section_id": "setnx-command"
    },
    {
      "id": "setnx-command-ex1",
      "language": "js",
      "code": "await client.set('bike:1', 'bike', {'NX': true});",
      "section_id": "setnx-command"
    },
    {
      "id": "config-command-ex0",
      "language": "js",
      "code": "client.config('SET', 'notify-keyspace-events', 'KEA');",
      "section_id": "config-command"
    },
    {
      "id": "config-command-ex1",
      "language": "js",
      "code": "await client.configSet('maxclients', '2000');",
      "section_id": "config-command"
    }
  ]
}
