Connecting Applications to Redis
Redis speaks a simple text-based protocol called RESP (REdis Serialization Protocol) over a TCP socket, and every mainstream language has a client library that handles the wire format so your code just calls methods like get() or hset(). In Node.js the two dominant clients are node-redis (the official client, promise-based since v4) and ioredis; in Python the standard is redis-py, which since version 4 unifies the older redis and aioredis packages into one package with both sync and async APIs.
Cricket analogy: Just as a scorer relays every ball bowled in a fixed shorthand notation (dot, four, six, wicket) that both teams instantly understand, RESP encodes every Redis command and reply in a compact, unambiguous text format both client and server parse instantly.
Node.js: node-redis and ioredis
node-redis (v4+) exposes a promise-based API: createClient() builds a client, client.connect() opens the TCP connection, and afterward you call methods like client.set(), client.hSet(), or client.expire() with await. ioredis, a popular alternative, offers a similar promise API plus first-class support for Redis Cluster and Sentinel topologies out of the box, and both libraries emit an 'error' event that must be handled or Node will crash the process on a connection drop.
Cricket analogy: Like a captain who must formally call for DRS before the review clock starts, node-redis requires an explicit client.connect() call before any command like client.set() can be awaited.
import { createClient } from 'redis';
const client = createClient({ url: 'redis://localhost:6379' });
client.on('error', (err) => console.error('Redis Client Error', err));
await client.connect();
await client.set('session:42', JSON.stringify({ userId: 42 }), { EX: 3600 });
const session = await client.get('session:42');
console.log(JSON.parse(session));
await client.quit();Python: redis-py
redis-py's Redis class takes host, port, db, and decode_responses as constructor arguments, and since version 4 the same package also ships redis.asyncio.Redis for async/await code under frameworks like FastAPI; a redis.Redis instance is thread-safe and typically created once at startup rather than per request, with the pipeline() context manager used to batch multiple commands into a single round trip.
Cricket analogy: Like a franchise signing one head coach for the whole IPL season rather than hiring a new coach for every match, a redis.Redis client is created once at startup and reused across every request.
import redis
r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
r.set('session:42', '{"userId": 42}', ex=3600)
session = r.get('session:42')
print(session)
with r.pipeline() as pipe:
pipe.incr('page_views')
pipe.expire('page_views', 86400)
pipe.execute()Connection Pooling, Pipelining, and Pitfalls
Because Redis is single-threaded for command execution, pooling connections (via redis.ConnectionPool in Python, or the built-in pool in ioredis) lets many concurrent requests share a small number of sockets instead of exhausting file descriptors; pipelining groups commands like INCR and EXPIRE into one network round trip using pipe.execute(). The most common pitfall is issuing a blocking command such as BLPOP on a connection that's also used for regular request-serving traffic, which stalls every other operation queued behind it until the block times out or resolves.
Cricket analogy: Like a stadium having a limited number of turnstiles that fans queue through in orderly rotation rather than each fan digging a new entrance, a connection pool lets many requests share a fixed number of Redis sockets.
In Python, always set decode_responses=True unless you have a specific reason to work with raw bytes — otherwise every GET or HGETALL returns bytes objects instead of str, which trips up string formatting and JSON parsing.
Never run a blocking command like BLPOP, BRPOPLPUSH, or WAIT on a connection shared with regular request-serving traffic — every other command queued behind it on that socket stalls until the block resolves or times out, which can look like a full application hang under load.
- node-redis v4+ is promise-based; wrap connect() in try/catch and listen for the 'error' event to avoid crashing the process.
- redis-py 4+ merges sync and async clients into one package; use redis.asyncio for async code under frameworks like FastAPI.
- Always set decode_responses=True in Python (or handle Buffers in Node) to avoid working with raw bytes.
- Use connection pooling rather than opening a new connection per request.
- Pipeline multiple commands to cut round trips instead of issuing sequential awaited calls one at a time.
- Avoid running blocking commands like BLPOP on a connection shared with other request handling.
- Set EX/PX expiry on ephemeral keys like sessions and cache entries to prevent unbounded memory growth.
Practice what you learned
1. Which protocol do Redis client libraries use to communicate with the server?
2. In redis-py 4+, what is the recommended way to use Redis asynchronously?
3. What does decode_responses=True do in redis-py?
4. Why is pipelining useful when issuing multiple Redis commands from application code?
5. What is a key risk of running a blocking command like BLPOP on a shared client connection?
Was this page helpful?
You May Also Like
Redis Security Basics
Core practices for securing a Redis deployment: authentication, ACLs, TLS encryption, and network hardening.
Redis Quick Reference
A condensed cheat sheet of the most-used Redis commands across data types, key management, and server administration.
Redis Streams
How Redis Streams model an append-only log, and how XADD, consumer groups, and trimming work together for durable event processing.