| name | redis-patterns |
| description | Implements Redis patterns for caching, sessions, rate limiting, pub/sub, and distributed locks with best practices. Use when users request "Redis caching", "session storage", "rate limiter", "pub/sub messaging", or "distributed locks". |
Redis Patterns
Implement common Redis patterns for high-performance applications.
Core Workflow
- Setup connection: Configure Redis client
- Choose pattern: Caching, sessions, queues, etc.
- Implement operations: CRUD with proper TTL
- Handle errors: Reconnection, fallbacks
- Monitor performance: Memory, latency
- Optimize: Pipelining, clustering
Connection Setup
import { Redis } from 'ioredis';
export const redis = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD,
db: parseInt(process.env.REDIS_DB || '0'),
maxRetriesPerRequest: 3,
retryStrategy(times) {
const delay = Math.min(times * 50, 2000);
return delay;
},
enableReadyCheck: true,
enableOfflineQueue: true,
connectTimeout: 10000,
tls: process.env.NODE_ENV === 'production' ? {} : undefined,
});
redis.on('connect', () => console.log('Redis connecting...'));
redis.on('ready', () => console.log('Redis ready'));
redis.on('error', (err) => console.error('Redis error:', err));
redis.on('close', () => console.log('Redis connection closed'));
export const cluster = new Redis.Cluster([
{ host: 'redis-node-1', port: 6379 },
{ host: 'redis-node-2', port: 6379 },
{ host: 'redis-node-3', port: 6379 },
], {
redisOptions: {
password: process.env.REDIS_PASSWORD,
},
scaleReads: 'slave',
maxRedirections: 16,
});
process.on('SIGTERM', async () => {
await redis.quit();
});
Caching Pattern
import { redis } from './client';
interface CacheOptions {
ttl?: number;
prefix?: string;
}
export class Cache {
private prefix: string;
private defaultTTL: number;
constructor(options: CacheOptions = {}) {
this.prefix = options.prefix || 'cache:';
this.defaultTTL = options.ttl || 3600;
}
private key(key: string): string {
return `${this.prefix}${key}`;
}
async get<T>(key: string): Promise<T | null> {
const data = await redis.get(this.key(key));
if (!data) ;
{
.(data) T;
} {
data T;
}
}
set<T>(: , : T, ?: ): <> {
serialized = value ===
? value
: .(value);
redis.(.(key), ttl || ., serialized);
}
getOrSet<T>(
: ,
: <T>,
?:
): <T> {
cached = .<T>(key);
(cached !== ) cached;
value = ();
.(key, value, ttl);
value;
}
(: ): <> {
redis.(.(key));
}
(: ): <> {
keys = redis.(.(pattern));
(keys. > ) {
redis.(...keys);
}
}
getStale<T>(
: ,
: <T>,
: { : ; : }
): <T> {
cacheKey = .(key);
staleKey = ;
cached = redis.(cacheKey);
(cached) {
.(cached);
}
stale = redis.(staleKey);
(stale) {
.(key, fetcher, options).(.);
.(stale);
}
.(key, fetcher, options);
}
refreshCache<T>(
: ,
: <T>,
: { : ; : }
): <T> {
value = ();
serialized = .(value);
pipeline = redis.();
pipeline.(.(key), options., serialized);
pipeline.(, options., serialized);
pipeline.();
value;
}
}
cache = ({ : , : });
() {
cache.(, () => {
db..(id);
}, );
}
Session Storage
import { redis } from './client';
import { nanoid } from 'nanoid';
interface Session {
id: string;
userId: string;
data: Record<string, any>;
createdAt: number;
expiresAt: number;
}
export class SessionStore {
private prefix = 'session:';
private userPrefix = 'user:sessions:';
private ttl = 86400 * 7;
private key(sessionId: string): string {
return `${this.prefix}${sessionId}`;
}
async create(userId: string, data: Record<string, any> = {}): Promise<Session> {
const session: = {
: (),
userId,
data,
: .(),
: .() + . * ,
};
pipeline = redis.();
pipeline.(.(session.), ., .(session));
pipeline.(, session.);
pipeline.(, .);
pipeline.();
session;
}
(: ): < | > {
data = redis.(.(sessionId));
(!data) ;
session = .(data) ;
(session. < .()) {
.(sessionId);
;
}
session;
}
(: , : <, >): <> {
session = .(sessionId);
(!session) ();
session. = { ...session., ...data };
redis.(
.(sessionId),
.,
.(session)
);
}
(: ): <> {
session = .(sessionId);
(!session) ;
session. = .() + . * ;
redis.(
.(sessionId),
.,
.(session)
);
}
(: ): <> {
session = .(sessionId);
(!session) ;
pipeline = redis.();
pipeline.(.(sessionId));
pipeline.(, sessionId);
pipeline.();
}
(: ): <> {
sessionIds = redis.();
(sessionIds. > ) {
keys = sessionIds.( .(id));
redis.(...keys, );
}
}
}
Rate Limiting
import { redis } from './client';
interface RateLimitResult {
allowed: boolean;
remaining: number;
resetAt: number;
}
export class RateLimiter {
async fixedWindow(
key: string,
limit: number,
windowSeconds: number
): Promise<RateLimitResult> {
const redisKey = `ratelimit:fixed:${key}`;
const now = Math.floor(Date.now() / 1000);
const window = Math.floor(now / windowSeconds);
const windowKey = `${redisKey}:${window}`;
const count = await redis.incr(windowKey);
if (count === 1) {
await redis.expire(windowKey, windowSeconds);
}
{
: count <= limit,
: .(, limit - count),
: ( + ) * windowSeconds * ,
};
}
(
: ,
: ,
:
): <> {
redisKey = ;
now = .();
windowStart = now - windowSeconds * ;
pipeline = redis.();
pipeline.(redisKey, , windowStart);
pipeline.(redisKey, now, );
pipeline.(redisKey);
pipeline.(redisKey, windowSeconds);
results = pipeline.();
count = results?.[]?.[] ;
{
: count <= limit,
: .(, limit - count),
: now + windowSeconds * ,
};
}
(
: ,
: ,
: ,
: =
): <> {
redisKey = ;
now = .();
script = ;
result = redis.(
script,
,
redisKey,
bucketSize,
refillRate,
tokensNeeded,
now
) [, ];
{
: result[] === ,
: .(result[]),
: now + .((tokensNeeded - result[]) / refillRate) * ,
};
}
}
() {
(: , : , : ) => {
key = req. || ;
result = limiter.(key, options., options.);
res.(, options.);
res.(, result.);
res.(, result.);
(!result.) {
res.().({
: ,
: .((result. - .()) / ),
});
}
();
};
}
Distributed Locks
import { redis } from './client';
import { nanoid } from 'nanoid';
export class DistributedLock {
private prefix = 'lock:';
async acquire(
resource: string,
ttlMs: number = 10000
): Promise<string | null> {
const lockKey = `${this.prefix}${resource}`;
const lockValue = nanoid();
const ttlSeconds = Math.ceil(ttlMs / 1000);
const acquired = await redis.set(
lockKey,
lockValue,
'EX',
ttlSeconds,
'NX'
);
return acquired === 'OK' ? lockValue : null;
}
async release(resource: string, lockValue: string): Promise<boolean> {
const lockKey = `${.prefix}`;
script = ;
result = redis.(script, , lockKey, lockValue);
result === ;
}
(
: ,
: ,
:
): <> {
lockKey = ;
ttlSeconds = .(ttlMs / );
script = ;
result = redis.(script, , lockKey, lockValue, ttlSeconds);
result === ;
}
withLock<T>(
: ,
: <T>,
: { ?: ; ?: ; ?: } = {}
): <T> {
{ ttl = , retries = , retryDelay = } = options;
: | = ;
attempts = ;
(attempts < retries) {
lockValue = .(resource, ttl);
(lockValue) ;
attempts++;
( (resolve, retryDelay));
}
(!lockValue) {
();
}
{
();
} {
.(resource, lockValue);
}
}
}
lock = ();
() {
lock.(, () => {
order = db..(orderId);
(order. !== ) {
();
}
(order);
db..(orderId, { : });
});
}
Pub/Sub Pattern
import { Redis } from 'ioredis';
const subscriber = new Redis(process.env.REDIS_URL!);
const publisher = new Redis(process.env.REDIS_URL!);
type MessageHandler<T> = (message: T, channel: string) => void | Promise<void>;
export class PubSub {
private handlers: Map<string, Set<MessageHandler<any>>> = new Map();
async subscribe<T>(channel: string, handler: MessageHandler<T>): Promise<void> {
if (!this.handlers.has(channel)) {
this.handlers.set(channel, new ());
subscriber.(channel);
}
..(channel)!.(handler);
}
(: , ?: <>): <> {
handlers = ..(channel);
(!handlers) ;
(handler) {
handlers.(handler);
(handlers. === ) {
..(channel);
subscriber.(channel);
}
} {
..(channel);
subscriber.(channel);
}
}
publish<T>(: , : T): <> {
publisher.(channel, .(message));
}
subscribePattern<T>(: , : <T>): <> {
(!..(pattern)) {
..(pattern, ());
subscriber.(pattern);
}
..(pattern)!.(handler);
}
}
subscriber.(, {
pubsub = ();
handlers = pubsub[].(channel);
(!handlers) ;
parsed = .(message);
handlers.( (parsed, channel));
});
pubsub = ();
pubsub.<{ : ; : }>(, (msg) => {
.();
});
pubsub.(, { : , : });
Leaderboard Pattern
import { redis } from './client';
export class Leaderboard {
private key: string;
constructor(name: string) {
this.key = `leaderboard:${name}`;
}
async addScore(member: string, score: number): Promise<void> {
await redis.zadd(this.key, score, member);
}
async incrementScore(member: string, increment: number): Promise<number> {
return redis.zincrby(this.key, increment, member);
}
async getTop(count: number): Promise<Array<{ member: string; score: number }>> {
const results = redis.(., , count - , );
: <{ : ; : }> = [];
( i = ; i < results.; i += ) {
entries.({
: results[i],
: (results[i + ]),
});
}
entries;
}
(: ): < | > {
rank = redis.(., member);
rank !== ? rank + : ;
}
(: ): < | > {
score = redis.(., member);
score !== ? (score) : ;
}
(
: ,
:
): <<{ : ; : ; : }>> {
rank = redis.(., member);
(rank === ) [];
start = .(, rank - .(count / ));
results = redis.(., start, start + count - , );
: <{ : ; : ; : }> = [];
( i = ; i < results.; i += ) {
entries.({
: results[i],
: (results[i + ]),
: start + i / + ,
});
}
entries;
}
}
Best Practices
- Connection pooling: Reuse connections
- Pipelining: Batch multiple commands
- TTL everywhere: Prevent memory leaks
- Key naming: Use consistent prefixes
- Lua scripts: Atomic operations
- Cluster ready: Design for horizontal scaling
- Error handling: Graceful degradation
- Memory management: Monitor and set maxmemory
Output Checklist
Every Redis implementation should include: