← Back to Docs
Recipes

Redis Patterns

Battle-tested Upstash KV patterns for rate limiting, caching, sessions, and distributed state in serverless environments.

Rate Limiter

Sliding-window rate limiter using sorted sets. Atomically evict stale entries and count active requests in a single pipeline.

const ok = await redis.zadd(
  `ratelimit:${key}`, { score: now, member: now + '-' + crypto.randomUUID() }
);
await redis.zremrangebyscore(`ratelimit:${key}`, 0, now - windowMs);
const count = await redis.zcard(`ratelimit:${key}`);
await redis.expire(`ratelimit:${key}`, Math.ceil(windowMs / 1000));
return count <= limit;

Cache-Aside

Check Redis first; on miss, fetch from origin, populate cache with TTL. Use JSON serialization for structured data.

let data = await redis.get(`cache:${cacheKey}`);
if (!data) {
  data = await fetchFromOrigin();
  await redis.set(`cache:${cacheKey}`, JSON.stringify(data), { ex: 300 });
} else {
  data = JSON.parse(data);
}

Distributed Lock

Acquire a lock with NX and a unique token. Release only if the token matches to prevent accidental unlocks.

const token = crypto.randomUUID();
const acquired = await redis.set(`lock:${resource}`, token, { nx: true, ex: 10 });
if (!acquired) return null;
try { /* critical section */ }
finally {
  const script = 'if redis.call("get",KEYS[1])==ARGV[1] then return redis.call("del",KEYS[1]) end';
  await redis.eval(script, [`lock:${resource}`], [token]);
}

Session Store

Store session blobs keyed by session ID with sliding expiration. Refresh TTL on every access.

const session = await redis.get(`session:${sid}`);
if (session) await redis.expire(`session:${sid}`, 3600);
return session ? JSON.parse(session) : null;

Idempotency Keys

Deduplicate payment or mutation requests. Store the response under the idempotency key after first processing.

const existing = await redis.get(`idem:${key}`);
if (existing) return JSON.parse(existing);
const result = await processRequest();
await redis.set(`idem:${key}`, JSON.stringify(result), { ex: 86400 });
return result;

All patterns assume an Upstash Redis client initialized with @upstash/redis. For production, wrap every call in try/catch with exponential backoff on connection failures.