Api database redis
Skill agents-inc/skills/dist/plugins/api-database-redis/skills/api-database-redis
The official skills marketplace for Agents Inc, an agent composition framework that builds stacks and compiles specialized subagents for Claude Code
npx -y skills add agents-inc/skills --skill api-database-redisAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 18 stars18 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Redis in-memory data store patterns with ioredis and node-redis -- caching, sessions, rate limiting, pub/sub, streams, queues, transactions, cluster
SKILL.md
19.5 KB, ~4.6k tokens by cl100k_base, as published. Nobody here has run it
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 set
maxRetriesPerRequest: 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 -- ioredis/node-redis connection, error handling, reconnection, cluster, sentinel, pipelining, transactions
- Caching Patterns -- Cache-aside, write-through, invalidation, stampede prevention, multi-key pipeline
- Data Structures -- Strings, hashes, lists, sets, sorted sets with typed helpers
- Sessions -- Express connect-redis (node-redis required for v9+), Hono manual middleware
- Pub/Sub -- Publish/subscribe, event broadcasting, pattern subscriptions
- Rate Limiting -- Sliding window (Lua), token bucket (Lua), middleware integration
- Queues & Locks -- BullMQ job queues, Redis Streams with consumer groups, distributed locks
Additional resources:
- 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).
<patterns>
Core Patterns
Pattern 1: ioredis Connection Setup
Configure ioredis with proper error handling and reconnection strategy. See 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 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 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)
.incrby(toKey, amount)
.exec();
if (!results) {
/* WATCH detected change, retry */
}
Why good: Pipeline reduces round-trips, MULTI/EXEC provides atomicity, WATCH enables optimistic locking
// ❌ Bad Example - Non-atomic balance transfer
await redis.decrby("balance:user1", 100);
await redis.incrby("balance:user2", 100);
// Crash between commands causes data loss
Why bad: Two separate commands are not atomic, crash between them causes data inconsistency
Pattern 4: Lua Scripting for Atomicity
Lua scripts execute atomically on the Redis server. See examples/rate-limiting.md for complete Lua-based rate limiters.
// ✅ Define custom atomic command
redis.defineCommand("rateLimit", {
numberOfKeys: 1,
lua: `
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now .. '-' .. math.random(1000000))
redis.call('EXPIRE', key, window)
return 1
end
return 0
`,
});
Why good: defineCommand uses EVALSHA internally for performance, Lua script is atomic -- no race conditions
Pattern 5: Pub/Sub Messaging
Requires separate connections for subscribing and publishing. See examples/pub-sub.md for event broadcasting system.
// ✅ Good Example - Separate connections
const publisher = new Redis(url);
const subscriber = new Redis(url); // MUST be separate
await subscriber.subscribe("notifications");
subscriber.on("message", (channel, message) => {
handleNotification(JSON.parse(message));
});
await publisher.publish("notifications", JSON.stringify(data));
Why good: Separate connections for pub and sub (required by Redis protocol), error handlers on both
// ❌ Bad Example - Same connection
const redis = new Redis();
await redis.subscribe("channel");
await redis.set("key", "value"); // THROWS: connection is in subscriber mode
Why bad: A subscribed connection cannot execute non-pub/sub commands
Pattern 6: BullMQ Job Queues
Robust job queuing with retries, scheduling, and priorities. See examples/queues.md for complete examples with workers, events, and graceful shutdown.
// ✅ Good Example - BullMQ connection factory
function createBullMQConnection(): Redis {
return new Redis(process.env.REDIS_URL!, {
maxRetriesPerRequest: null, // REQUIRED for BullMQ
});
}
const emailQueue = new Queue<EmailJobData>(QUEUE_NAME, {
connection: createBullMQConnection(),
defaultJobOptions: {
attempts: MAX_RETRY_ATTEMPTS,
backoff: { type: "exponential", delay: BACKOFF_DELAY_MS },
},
});
Why good: maxRetriesPerRequest: null is required for BullMQ, separate connection per Queue/Worker
// ❌ Bad Example - Missing required config
const connection = new Redis(); // Missing maxRetriesPerRequest: null
const queue = new Queue("emails", { connection });
// BullMQ will throw: "maxRetriesPerRequest must be null"
Why bad: BullMQ requires infinite retries -- without null, ioredis gives up after a set number of attempts
Pattern 7: Redis Streams
Persistent, ordered message logs with consumer groups. See examples/queues.md for full producer/consumer implementation with pending message recovery.
// Producer
const entryId = await redis.xadd(
STREAM_KEY,
"*",
"orderId",
id,
"action",
action,
);
// Consumer group setup (idempotent)
try {
await redis.xgroup("CREATE", STREAM_KEY, GROUP_NAME, "0", "MKSTREAM");
} catch (err) {
if (!(err instanceof Error) || !err.message.includes("BUSYGROUP")) throw err;
}
// Consumer read + acknowledge
const results = await redis.xreadgroup(
"GROUP",
GROUP_NAME,
consumerName,
"COUNT",
"10",
"BLOCK",
"5000",
"STREAMS",
STREAM_KEY,
">",
);
await redis.xack(STREAM_KEY, GROUP_NAME, messageId);
Why good: MKSTREAM creates stream if absent, BUSYGROUP handling for idempotent setup, XACK confirms processing
</patterns><performance>
Performance Optimization
- Auto-pipelining -- Enable
enableAutoPipelining: trueto batch commands issued during the same event loop tick. Does NOT work with WATCH/MULTI or blocking commands. See examples/core.md for setup. - Manual pipelining -- Use
redis.pipeline()for explicit command batching. See examples/core.md. - Key expiration -- Set TTLs on all cache keys. Use named constants (
TTL_SHORT_SECONDS = 60,TTL_MEDIUM_SECONDS = 300, etc.). See examples/core.md. - SCAN over KEYS -- Never use
KEYSin production (blocks Redis). Useredis.scanStream({ match: "pattern", count: 100 }). See examples/core.md. - UNLINK over DEL -- Use
unlinkfor large keys (non-blocking deletion).
<decision_framework>
Decision Framework
Which Redis Client?
Which Redis client should I use?
├─ Need Redis Stack modules (JSON, Search, TimeSeries)? -> node-redis (v5.x)
├─ Using BullMQ for job queues? -> ioredis (BullMQ requires it)
├─ Need Cluster or Sentinel support? -> ioredis (built-in, battle-tested)
├─ Need auto-pipelining? -> ioredis (enableAutoPipelining option)
└─ General caching/sessions/pub-sub? -> ioredis (recommended default)
Which Caching Strategy?
How should I cache this data?
├─ Read-heavy, tolerates brief staleness? -> Cache-aside with TTL
├─ Needs strong consistency after writes? -> Write-through (update DB + invalidate cache)
├─ Write-heavy, can tolerate brief data loss? -> Write-behind (async cache update)
└─ Data changes rarely? -> Cache-aside with long TTL + manual invalidation
Which Data Structure?
What Redis data structure should I use?
├─ Simple key-value (cache, sessions)? -> Strings (GET/SET)
├─ Object with multiple fields? -> Hashes (HSET/HGET)
├─ Ordered ranking/leaderboard? -> Sorted Sets (ZADD/ZRANGE)
├─ Queue (FIFO/LIFO)? -> Lists (LPUSH/RPOP)
├─ Unique collection (tags, categories)? -> Sets (SADD/SMEMBERS)
├─ Persistent message log with consumers? -> Streams (XADD/XREAD)
└─ Rate limiting (sliding window)? -> Sorted Sets + Lua script
Which Messaging Pattern?
How should I implement real-time messaging?
├─ Fire-and-forget broadcast? -> Pub/Sub (no persistence)
├─ Need message persistence and replay? -> Streams with consumer groups
├─ Need reliable job processing with retries? -> BullMQ (built on Redis)
└─ Need request-reply pattern? -> Pub/Sub with correlation IDs
Atomicity Decision
Do I need atomicity across multiple commands?
├─ YES -> Are the commands on the same key?
│ ├─ YES -> Use a single atomic command (INCR, SETNX, etc.)
│ └─ NO -> Use Lua script (defineCommand)
├─ NO, but I want batching -> Use pipeline (non-atomic, single round-trip)
└─ Need optimistic locking? -> Use WATCH + MULTI/EXEC
</decision_framework>
<integration>
Integration Guide
Common integration patterns:
- Database caching -- Cache query results using cache-aside pattern; invalidate cache on writes
- HTTP framework sessions -- Session storage middleware, rate limiting middleware
- BullMQ job queues -- Built on Redis (requires ioredis with
maxRetriesPerRequest: null) - WebSocket scaling -- Redis adapter for distributing WebSocket connections across servers
Replaces / Conflicts with:
- In-memory caches -- Redis provides distributed caching across multiple app instances
- Database-backed sessions -- Redis sessions are faster and reduce database load
- Simple message queues -- Redis Streams and BullMQ cover most queue use cases; use dedicated message brokers for complex routing or massive throughput
<red_flags>
RED FLAGS
High Priority Issues:
- Using the same connection for Pub/Sub subscribe and regular commands -- subscribed connections cannot execute non-pub/sub commands
- Missing
maxRetriesPerRequest: nullon BullMQ connections -- BullMQ throws immediately without this setting - Using
KEYScommand in production -- blocks the entire Redis server while scanning all keys - No
errorevent handler on Redis client -- unhandled errors crash the Node.js process - Storing large objects (> 1 MB) in Redis -- degrades performance and wastes memory; store a reference and fetch from object storage
Medium Priority Issues:
- Missing TTL on cached keys -- causes unbounded memory growth until Redis runs out of memory
- Using
delwith many keys instead ofunlink--delblocks Redis;unlinkfrees memory asynchronously - Not using pipelining for batch operations -- each command is a separate network round-trip
- Serializing/deserializing complex objects without error handling -- malformed JSON in cache crashes on parse
- Sharing a single Redis connection across BullMQ Queue and Worker -- each needs its own connection
Common Mistakes:
- Assuming pipeline commands are atomic -- pipelines batch for network efficiency but do not provide atomicity (use MULTI/EXEC or Lua)
- Forgetting that
hgetallreturns an empty object{}for non-existent keys (notnull) -- checkObject.keys(result).length === 0 - Using
MULTI/EXECwithoutWATCHfor conditional updates -- transactions execute unconditionally unless you WATCH keys first - Not handling
nullreturns fromGET-- cache misses returnnull, notundefined - Connecting to Redis without TLS in production -- credentials sent in plaintext over the network
Gotchas & Edge Cases:
- Redis
HGETALLreturns all values as strings -- numbers stored withHSETcome back as strings, requiring explicit parsing EXPIREresets when a key is overwritten withSET-- if youSETa key that already has a TTL, the TTL is removed unless you includeEX/PXin theSETcommand- Pub/Sub messages are fire-and-forget -- if no subscriber is listening when a message is published, it is lost forever (use Streams for persistence)
- Redis Cluster does not support multi-key operations across different hash slots -- use
{hash-tag}prefix to force related keys to the same slot WATCHis connection-scoped -- concurrent requests sharing a connection will interfere with each other's WATCH state- ioredis auto-pipelining does not work with
WATCH/MULTIor blocking commands (BRPOP,BLPOP,XREAD BLOCK)
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
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)
Failure to follow these rules will cause pub/sub failures, BullMQ connection errors, race conditions, and application crashes.
</critical_reminders>