Node.js Clients for Redis vs Memcached
The Node.js client situation tells you something about the relative size of the two ecosystems. Redis has two excellent, actively-developed clients with mature TypeScript types. Memcached has one maintained client and a long tail of stale forks. The gap shapes the developer experience more than the engine differences themselves.
node-redis: the official client in 2026
node-redis is the Redis-Inc.-maintained official client. After versions 1.x through 3.x went through a long period of community maintenance, Redis Inc. brought the project in-house and shipped v4 in 2021 with a clean rewrite, then v5 in 2024 with full TypeScript native and modern async/await throughout. As of v5.x (May 2026) it ships with first-class support for every Redis 8.0 feature, including vector sets, hash field TTL, and the latest stream commands.
The API style is method-per-command: client.hSet(key, field, value), client.xAdd(stream, "*", {field: value}). The TypeScript types know each command's return shape, so most usage errors show up at compile time. Connection management is automatic: you create one client per Redis instance and use it for all commands; the client multiplexes commands over a single connection by default and queues them in pipeline mode if you batch.
For Redis Cluster the API is createCluster({rootNodes: [{url: ...}]}) returning a client with topology auto-discovery. The client routes commands by hash slot, handles MOVED redirects, and reconnects on topology changes. Pub/sub, streams, scripts (EVAL / EVALSHA), and transactions all have first-class support. For new Node.js projects in 2026 starting from scratch, node-redis is the safest default.
Side-by-side: a session store
import { createClient } from "redis";
const client = createClient({ url: "redis://localhost" });
await client.connect();
async function saveSession(id, data, ttlSec = 3600) {
await client.hSet(`sess:${id}`, data);
await client.expire(`sess:${id}`, ttlSec);
}
async function readSession(id) {
return await client.hGetAll(`sess:${id}`);
}
async function dropSession(id) {
await client.del(`sess:${id}`);
}import memjs from "memjs";
const client = memjs.Client.create();
async function saveSession(id, data, ttlSec = 3600) {
// Memcached has no hash type; JSON-encode the whole session
await client.set(`sess:${id}`, JSON.stringify(data), { expires: ttlSec });
}
async function readSession(id) {
const { value } = await client.get(`sess:${id}`);
return value ? JSON.parse(value.toString()) : null;
}
async function dropSession(id) {
await client.delete(`sess:${id}`);
}
// Note: full read-modify-write needed to update one field.
// And: on Memcached restart, all sessions disappear.ioredis: the workhorse
ioredis was the community-favourite Node.js Redis client for most of the 2010s. It is more event-driven than node-redis (emits 'ready', 'error', 'end' events), has a slightly different API style (camelCase methods plus an exec method for raw commands), and supports more advanced cluster scenarios out of the box.
The reason ioredis remains relevant is BullMQ. The most popular Node.js job-queue library depends on ioredis internally and the integration is tight: BullMQ uses ioredis-specific features for Lua scripts, pipeline batching, and blocking pop semantics. If you are using BullMQ (or a wrapper around it like Hatchet, BullBoard, or just the raw BullMQ API), you are using ioredis whether you import it directly or not.
For new projects that use BullMQ, just use ioredis directly for any other Redis access in the same app to avoid running two clients on different connections. For projects that do not use BullMQ and are starting fresh, node-redis is fine. For projects migrating from older versions of either, the migration paths are well-documented; the API shapes are similar enough that mechanical translation is straightforward.
The serverless angle: @upstash/redis
For Cloudflare Workers, Vercel Edge Functions, Lambda@Edge, and other sandboxed serverless runtimes that cannot hold persistent TCP connections, the @upstash/redis client is the canonical choice. It speaks Upstash's HTTP/REST API rather than the Redis binary protocol, which is the only way to talk to Redis from within Workers (the runtime does not expose raw sockets).
The API is intentionally identical to node-redis where possible, so application code looks like normal Redis usage. Under the hood, every command translates to an HTTP fetch to the Upstash REST endpoint, with the auth token in the header and the response parsed into the expected shape. Pipelines are supported by batching multiple commands into one HTTP call.
The performance penalty versus a native TCP Redis client is the per-command HTTP overhead, mitigated by Upstash deploying replicas globally so the fetch is to a nearby region. For Workers running at the edge, the latency is typically 5-30ms per command against the nearest Upstash replica; for Workers calling a Redis somewhere distant the latency would be the actual transcontinental round trip plus HTTP overhead. Memcached has no equivalent serverless client because, as discussed elsewhere on this site, the protocol does not lend itself to HTTP wrapping.
FAQ
ioredis or node-redis?
Both are excellent. ioredis is older, has broader battle-testing, better cluster support, and a more event-driven API. node-redis (the official client) is now maintained by Redis Inc., has cleaner TypeScript types, and aligns with Redis's own roadmap for new commands. For new projects in 2026, node-redis is a defensible default; ioredis remains preferred for cluster-heavy workloads and BullMQ integration.
memjs vs node-memcached?
memjs is the maintained pure-JavaScript Memcached client. node-memcached (the older library) is less actively maintained. For new code use memjs. For AWS ElastiCache for Memcached with IAM auth, use memcached-elasticache-iam, the official wrapper.
Do I need a connection pool?
For Redis with ioredis or node-redis, no, the client manages a single multiplexed connection (or one per cluster node) by default. For Memcached with memjs, the library handles pooling internally. Manual connection pooling is rarely needed in Node.js because the event loop multiplexes naturally.
What is the cluster topology with ioredis?
new Redis.Cluster([{host, port}]) returns a client that connects to one seed node, discovers the full topology via CLUSTER NODES, then routes commands to the correct node based on key hash slot. It handles MOVED and ASK redirections automatically. For Valkey 8.x cluster, the same API works because the protocol is identical.
How do I use Redis Streams from Node.js?
Both ioredis and node-redis support the stream commands (XADD, XREAD, XREADGROUP, XACK). For high-level queue work, use BullMQ which sits on top of Streams. For lower-level stream consumption, the raw commands work fine; remember that XREADGROUP with BLOCK is a blocking call on the connection and you may want a dedicated connection for it.