Redis Patterns
Quick Guide: Use Redis as an in-memory data store for caching, session management, rate limiting, pub/sub messaging, and job queues. Use ioredis (v5.x) as the primary client for its superior TypeScript support, Cluster/Sentinel integration, auto-pipelining, and Lua scripting. Use node-redis (v5.x) only when you need Redis Stack modules (JSON, Search, TimeSeries). Always setmaxRetriesPerRequest: nullfor BullMQ workers, use separate connections for Pub/Sub subscribers, and define Lua scripts viadefineCommandfor atomic multi-step operations.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)
(You MUST use a SEPARATE Redis connection for Pub/Sub subscribers -- a subscribed connection enters a special mode and cannot execute other commands)
(You MUST set maxRetriesPerRequest: null on any ioredis connection passed to BullMQ -- BullMQ requires infinite retries and will throw if this is not set)
(You MUST use Lua scripts (defineCommand or eval) for any operation requiring atomicity across multiple Redis commands -- separate commands are NOT atomic even in a pipeline)
(You MUST handle the error event on every Redis client instance -- unhandled errors crash the Node.js process)
</critical_requirements>
Examples
- [Core Patterns](examples/core.md) -- ioredis/node-redis connection, error handling, reconnection, cluster, sentinel, pipelining, transactions
- [Caching Patterns](examples/caching.md) -- Cache-aside, write-through, invalidation, stampede prevention, multi-key pipeline
- [Data Structures](examples/data-structures.md) -- Strings, hashes, lists, sets, sorted sets with typed helpers
- [Sessions](examples/sessions.md) -- Express connect-redis (node-redis required for v9+), Hono manual middleware
- [Pub/Sub](examples/pub-sub.md) -- Publish/subscribe, event broadcasting, pattern subscriptions
- [Rate Limiting](examples/rate-limiting.md) -- Sliding window (Lua), token bucket (Lua), middleware integration
- [Queues & Locks](examples/queues.md) -- BullMQ job queues, Redis Streams with consumer groups, distributed locks
Additional resources:
- [reference.md](reference.md) -- Command cheat sheet, connection options, anti-patterns, production checklist
Auto-detection: Redis, ioredis, node-redis, createClient, RedisStore, BullMQ, Queue, Worker, pub/sub, MULTI, EXEC, pipeline, Lua script, defineCommand, xadd, xread, cache-aside, rate limit, session store, connect-redis, Redis.Cluster, Sentinel
When to use:
- Caching database queries or API responses (cache-aside, write-through)
- Session storage for Express/Hono/Fastify applications
- Distributed rate limiting (sliding window, token bucket)
- Real-time messaging with Pub/Sub
- Background job processing with BullMQ queues
- Leaderboards, counters, and real-time analytics with sorted sets
- Distributed locks and atomic operations with Lua scripts
Key patterns covered:
- ioredis connection setup, configuration, and error handling
- Data structures (strings, hashes, lists, sets, sorted sets, streams)
- Cache-aside and write-through caching with TTL management
- Session storage with connect-redis
- Rate limiting with Lua scripts (sliding window, token bucket)
- Pub/Sub messaging with separate connections
- Redis Streams for persistent message queues
- BullMQ for job queues with retries and scheduling
- Pipelining and transactions (MULTI/EXEC)
- Lua scripting for atomic operations
- Cluster mode and Sentinel for high availability
When NOT to use:
- Primary database for relational data (use your relational database)
- Document storage with complex queries (use a document database)
- Large binary file storage (use S3/object storage)
- Data that must survive total memory loss without persistence configured
<philosophy>
Philosophy
Redis is an in-memory data store used as a cache, message broker, and streaming engine. The core principle: use Redis for fast, ephemeral, or real-time data -- not as a primary database.
Core principles:
- Cache, don't store -- Redis complements your primary database. Cache frequently accessed data, but always have a source of truth elsewhere.
- Atomic operations -- Use Lua scripts or MULTI/EXEC for operations spanning multiple keys. Individual Redis commands are atomic, but sequences are not.
- Separate concerns -- Use different Redis databases (or key prefixes) for caching, sessions, and queues. Use separate connections for Pub/Sub.
- Set TTLs on everything -- Memory is finite. Every cached key should expire. Use
EX(seconds) orPX(milliseconds) on SET commands. - Fail gracefully -- Redis is a cache, not a database. If Redis is down, the application should degrade gracefully (bypass cache, use database directly).
</philosophy>
<patterns>
Core Patterns
Pattern 1: ioredis Connection Setup
Configure ioredis with proper error handling and reconnection strategy. See [examples/core.md](examples/core.md) for full examples including node-redis and cluster configuration.
// ✅ Good Example - Proper ioredis setup with error handling
import Redis from "ioredis";
const RETRY_DELAY_BASE_MS = 50;
const RETRY_DELAY_MAX_MS = 2000;
function createRedisClient(): Redis {
const url = process.env.REDIS_URL;
if (!url) {
throw new Error("REDIS_URL environment variable is required");
}
const client = new Redis(url, {
maxRetriesPerRequest: 3,
retryStrategy(times) {
return Math.min(times * RETRY_DELAY_BASE_MS, RETRY_DELAY_MAX_MS);
},
lazyConnect: true,
});
client.on("error", (err) => {
console.error("Redis connection error:", err.message);
});
return client;
}
export { createRedisClient };
Why good: Environment variable validation, named constants for retry delays, lazyConnect prevents connection before ready, error event handler prevents process crash
// ❌ Bad Example - No error handling, hardcoded config
import Redis from "ioredis";
const redis = new Redis("redis://localhost:6379");
// No error handler, no retry strategy, hardcoded URL
Why bad: Missing error event handler crashes Node.js process on connection failure, hardcoded URL prevents environment-specific configuration
Pattern 2: Cache-Aside
Check cache first, fall back to database on miss, populate cache. See [examples/caching.md](examples/caching.md) for write-through, invalidation, stampede prevention, and multi-key cache patterns.
// ✅ Good Example - Generic cache-aside helper
const CACHE_TTL_SECONDS = 300;
async function cacheAside<T>(
redis: Redis,
key: string,
fetcher: () => Promise<T>,
ttlSeconds: number = CACHE_TTL_SECONDS,
): Promise<T> {
const cached = await redis.get(key);
if (cached !== null) {
return JSON.parse(cached) as T;
}
const data = await fetcher();
// Fire-and-forget cache write
redis.set(key, JSON.stringify(data), "EX", ttlSeconds).catch((err) => {
console.error(`Cache write failed for ${key}:`, err.message);
});
return data;
}
Why good: Generic cacheAside<T> works with any data type, fire-and-forget cache write prevents cache failure from blocking response, configurable TTL
Pattern 3: Pipelining and Transactions
Batch commands for network efficiency (pipeline) or atomicity (MULTI/EXEC). See [examples/core.md](examples/core.md) for full examples.
// ✅ Pipeline - batch for network efficiency (NOT atomic)
const pipeline = redis.pipeline();
pipeline.hset(key, { name: user.name, email: user.email });
pipeline.expire(key, TTL_SECONDS);
await pipeline.exec();
// ✅ Transaction - atomic execution with optimistic locking
await redis.watch(fromKey);
const results = await redis
.multi()
.decrby(fromKey, amount)
.in