redis
>=5.0.0 <7.0.0postconditions28functions27last verified2026-06-23coverage score100%Postconditions: what we check
- createClient · missing-error-listenererrorWhencreateClient() called without .on('error', handler) registeredRequired handlingMUST call client.on('error', handler) immediately after createClient(). Handler should log error details and optionally trigger reconnection logic or graceful shutdown.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1]
- connect · connect-no-error-handlingerrorWhenclient.connect() called without try-catch or .catch() handlerThrows
Connection errors (ECONNREFUSED, ETIMEDOUT, ECONNRESET, EAI_AGAIN)Required handlingMUST wrap await client.connect() in try-catch block. Catch block should check error.code and implement retry logic with exponential backoff for recoverable errors. Non-recoverable errors should trigger graceful shutdown or fallback mode.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - get · get-no-error-handlingerrorWhenclient.get() called without try-catch or .catch() handlerThrows
Connection errors, timeout errors, or WRONGTYPE errorsRequired handlingMUST wrap await client.get() in try-catch block. For connection errors, implement graceful degradation (fallback to database). For WRONGTYPE errors, fix data schema. For timeout errors, retry with backoff or return cached/default value.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - set · set-no-error-handlingerrorWhenclient.set() called without try-catch or .catch() handlerThrows
Connection errors, timeout errors, or command errorsRequired handlingMUST wrap await client.set() in try-catch block. For connection errors, consider queueing write for retry. For critical writes, re-throw error to caller. For non-critical cache writes, log error and continue without cache.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - del · del-no-error-handlingerrorWhenclient.del() called without try-catch or .catch() handlerThrows
Connection errors or timeout errorsRequired handlingMUST wrap await client.del() in try-catch block. For connection errors, log error and decide whether to retry, fail operation, or continue. Critical deletes should re-throw error to caller for proper handling.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - hSet · hset-no-error-handlingerrorWhenclient.hSet() called without try-catch or .catch() handlerThrows
ErrorReply (WRONGTYPE when key exists as non-hash), connection errors (ECONNREFUSED, ETIMEDOUT, ECONNRESET, EAI_AGAIN), SocketClosedUnexpectedlyErrorRequired handlingMUST wrap await client.hSet() in try-catch. Check err.message.includes('WRONGTYPE') to distinguish type errors from connection errors. Type errors indicate a data schema bug; connection errors may be retried.costmediumin prodimmediate exceptionusers seelost datavisibilityvisible - hGet · hget-no-error-handlingerrorWhenclient.hGet() called without try-catch or .catch() handlerThrows
ErrorReply (WRONGTYPE if key is not a hash), connection errors (ECONNREFUSED, ETIMEDOUT, ECONNRESET, EAI_AGAIN)Required handlingMUST wrap await client.hGet() in try-catch. Return null fallback for missing fields is correct — do not confuse this with thrown errors.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - hGetAll · hgetall-no-error-handlingerrorWhenclient.hGetAll() called without try-catch or .catch() handlerThrows
ErrorReply (WRONGTYPE if key is not a hash), connection errors (ECONNREFUSED, ETIMEDOUT, ECONNRESET, EAI_AGAIN)Required handlingMUST wrap in try-catch. Also check if the returned object is empty ({}) to detect missing sessions/profiles — returning {} is not an error but often indicates "not found" semantics.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - incr · incr-no-error-handlingerrorWhenclient.incr() called without try-catch or .catch() handlerThrows
ErrorReply ("ERR value is not an integer or out of range") when key holds a non-integer string; connection errors (ECONNREFUSED, ETIMEDOUT)Required handlingMUST wrap await client.incr() in try-catch. Check err.message for "not an integer" to distinguish type errors (data schema bug) from connection errors (retriable).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - incrBy · incrby-no-error-handlingerrorWhenclient.incrBy() called without try-catch or .catch() handlerThrows
ErrorReply ("ERR value is not an integer or out of range") when key holds a non-integer; connection errors (ECONNREFUSED, ETIMEDOUT)Required handlingMUST wrap await client.incrBy() in try-catch. Same handling as incr().costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - expire · expire-no-error-handlingerrorWhenclient.expire() called without try-catch or .catch() handlerThrows
Connection errors (ECONNREFUSED, ETIMEDOUT, ECONNRESET, EAI_AGAIN); ErrorReply on invalid argument typesRequired handlingMUST wrap await client.expire() in try-catch. Prefer atomic client.set(key, value, {EX: ttl}) to avoid the set-then-expire race condition entirely. If separate expire() is required, log and alert on failure — persistent keys are a security and memory risk.costmediumin prodimmediate exceptionusers seedegraded performancevisibilitysilent - exists · exists-no-error-handlingerrorWhenclient.exists() called without try-catch or .catch() handlerThrows
Connection errors (ECONNREFUSED, ETIMEDOUT, ECONNRESET, EAI_AGAIN)Required handlingMUST wrap await client.exists() in try-catch. Distinguish connection errors from logic errors. For cache-pattern guards, fail safe (treat as "not exists" and proceed to authoritative source).costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - lPush · lpush-no-error-handlingerrorWhenclient.lPush() called without try-catch or .catch() handlerThrows
ErrorReply (WRONGTYPE if key is not a list), connection errors (ECONNREFUSED, ETIMEDOUT, ECONNRESET, EAI_AGAIN)Required handlingMUST wrap await client.lPush() in try-catch. For queue patterns, re-throw so callers can handle job failure. Check err.message.includes('WRONGTYPE') to detect key type collisions.costmediumin prodimmediate exceptionusers seelost datavisibilityvisible - lRange · lrange-no-error-handlingerrorWhenclient.lRange() called without try-catch or .catch() handlerThrows
ErrorReply (WRONGTYPE if key is not a list), connection errors (ECONNREFUSED, ETIMEDOUT, ECONNRESET, EAI_AGAIN)Required handlingMUST wrap await client.lRange() in try-catch. An empty array is a valid result, not an error condition. Check for ErrorReply type to distinguish Redis server errors from connection errors.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - sAdd · sadd-no-error-handlingerrorWhenclient.sAdd() called without try-catch or .catch() handlerThrows
ErrorReply (WRONGTYPE if key is not a set), connection errors (ECONNREFUSED, ETIMEDOUT, ECONNRESET, EAI_AGAIN)Required handlingMUST wrap await client.sAdd() in try-catch. For deduplication, re-throw on error to prevent silent data loss. Log WRONGTYPE errors — they indicate a key namespace collision that requires investigation.costmediumin prodimmediate exceptionusers seelost datavisibilityvisible - sMembers · smembers-no-error-handlingerrorWhenclient.sMembers() called without try-catch or .catch() handlerThrows
ErrorReply (WRONGTYPE if key is not a set), connection errors (ECONNREFUSED, ETIMEDOUT, ECONNRESET, EAI_AGAIN)Required handlingMUST wrap await client.sMembers() in try-catch. An empty Set is a valid result. WRONGTYPE errors indicate data schema problems requiring code investigation.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - zAdd · zadd-no-error-handlingerrorWhenclient.zAdd() called without try-catch or .catch() handlerThrows
ErrorReply (WRONGTYPE if key is not a sorted set; "not a float" if score is NaN/Infinity), connection errors (ECONNREFUSED, ETIMEDOUT)Required handlingMUST wrap await client.zAdd() in try-catch. Validate score is a finite number before passing to zAdd(). WRONGTYPE indicates a key collision; score errors indicate a computation bug upstream.costmediumin prodimmediate exceptionusers seelost datavisibilityvisible - zRange · zrange-no-error-handlingerrorWhenclient.zRange() called without try-catch or .catch() handlerThrows
ErrorReply (WRONGTYPE if key is not a sorted set), connection errors (ECONNREFUSED, ETIMEDOUT, ECONNRESET, EAI_AGAIN)Required handlingMUST wrap await client.zRange() in try-catch. An empty array is a valid result. Implement fallback for connection errors in leaderboard reads (return cached or empty data rather than throwing to the user).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - exec · multi-exec-watch-errorerrorWhenclient.watch() was used and the watched key was modified by another client before exec() was called; or client reconnected after WATCHThrows
WatchError (from @redis/client) — thrown from exec() when optimistic locking fails. The transaction was not executed.Required handlingMUST wrap exec() in try-catch when WATCH is used. Check err instanceof WatchError to implement retry logic. WatchError means the transaction was not executed — not a partial execution. Retry the full WATCH+MULTI+EXEC sequence.costmediumin prodimmediate exceptionusers seelost datavisibilityvisible - exec · multi-exec-connection-errorerrorWhenConnection is lost or client is closed before exec() completesThrows
SocketClosedUnexpectedlyError, ClientClosedError, or connection system errors (ECONNREFUSED, ECONNRESET)Required handlingMUST wrap exec() in try-catch. On connection error, the transaction state is unknown — implement reconciliation logic or re-read state before retrying.costmediumin prodimmediate exceptionusers seelost datavisibilityvisible - subscribe · subscribe-no-error-handlingerrorWhenclient.subscribe() called without try-catch or .catch() handlerThrows
ClientClosedError (if client is not connected), connection errors (ECONNREFUSED, ETIMEDOUT, ECONNRESET) on network failure during subscriptionRequired handlingMUST wrap await client.subscribe() in try-catch for connection failure. The client .on('error') listener handles ongoing connection errors after subscription. Both patterns are required: try-catch for subscribe() AND .on('error') on client.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - publish · publish-no-error-handlingerrorWhenclient.publish() called without try-catch or .catch() handlerThrows
Connection errors (ECONNREFUSED, ETIMEDOUT, ECONNRESET, EAI_AGAIN); ClientClosedError if client is disconnectedRequired handlingMUST wrap await client.publish() in try-catch. A return value of 0 is NOT an error — do not throw or retry based on the count alone. Only throw on connection errors or when guaranteed delivery is required (use a different pattern such as streams for guaranteed delivery).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - quit · quit-no-error-handlingwarningWhenclient.quit() called without try-catch or .catch() handlerThrows
Connection errors if the connection is already closed or lost before QUIT completes; SocketClosedUnexpectedlyErrorRequired handlingSHOULD wrap await client.quit() in try-catch in shutdown handlers. Errors during quit() can be logged and swallowed — the connection is being torn down anyway. Use `client.quit().catch(err => logger.warn('Redis quit error', err))` pattern.costlowin prodimmediate exceptionusers seedegraded performancevisibilityvisible - close · close-no-error-handlingwarningWhenclient.close() called without try-catch or .catch() handlerThrows
ClientClosedError if close() is called twice; connection-layer errors (ECONNRESET, ETIMEDOUT) if the socket dies during the graceful drain; unhandled promise rejection on any of the aboveRequired handlingSHOULD wrap await client.close() in try-catch in shutdown handlers. Errors during close() can be logged and swallowed — the connection is being torn down anyway. Use the same pattern as quit(): `client.close().catch(err => logger.warn('Redis close error', err))`. For graceful shutdown sequencing, await close() before terminating the process so pending commands flush.costlowin prodimmediate exceptionusers seedegraded performancevisibilityvisible - watch · watch-no-error-handlingerrorWhenclient.watch() called without try-catch or .catch() handlerThrows
Connection-layer errors (ECONNREFUSED, ETIMEDOUT, ECONNRESET, EAI_AGAIN); ClientClosedError if the client was closed before watch() ranRequired handlingMUST wrap await client.watch() in try-catch. On error, ABORT the entire transaction — do NOT proceed to multi().exec(). The exec() WatchError handler covers the "watched key changed" case; this handler covers the "watch() itself failed" case. Both are required for correct optimistic locking. Pattern: try { await client.watch('counter'); const value = await client.get('counter'); await client.multi().set('counter', String(Number(value) + 1)).exec(); } catch (err) { if (err instanceof WatchError) { /* retry: key changed */ } else { /* abort: watch or exec failed */ } }costmediumin prodimmediate exceptionusers seelost datavisibilityvisible - createClientPool · create-client-pool-no-error-handlingerrorWhencreateClientPool() factory called without a corresponding pool.on('error') listener AND without try-catch on the subsequent pool.connect()Throws
pool.connect() throws on connection failure (ECONNREFUSED, ETIMEDOUT, DNS failures); pool runtime errors are dispatched as 'error' events on the pool — uncaught events crash the process by defaultRequired handlingMUST register pool.on('error', handler) BEFORE calling pool.connect(), and MUST wrap await pool.connect() in try-catch. Pattern mirrors the createClient() / connect() / .on('error') triad — same rules apply.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - execute · pool-execute-no-error-handlingerrorWhenpool.execute(callback) called without try-catch or .catch() handlerThrows
WatchError (when callback uses WATCH+MULTI and the watched key was modified); any error thrown by the callback itself; connection-layer errors on the isolated client (ECONNREFUSED, ECONNRESET); ClientClosedError if the pool is closed mid-executeRequired handlingMUST wrap await pool.execute(...) in try-catch. Discriminate by error type: WatchError → retry the callback (the watched key changed, this is the designed retry signal). Other errors → fail the operation and log. The isolated client is returned to the pool regardless of outcome — do NOT attempt to close it inside the callback. Pattern: try { await pool.execute(async (client) => { await client.watch('key'); const v = await client.get('key'); return client.multi().set('key', updated(v)).exec(); }); } catch (err) { if (err instanceof WatchError) { /* retry */ } else { throw err; } }costmediumin prodimmediate exceptionusers seelost datavisibilityvisibleSources[14] - sendCommand · send-command-no-error-handlingerrorWhenclient.sendCommand() called without try-catch or .catch() handlerThrows
ErrorReply / SimpleError when Redis returns an error response (WRONGTYPE, NOSCRIPT, READONLY, MOVED, ASK, MASTERDOWN, etc.); connection-layer errors (ECONNREFUSED, ETIMEDOUT, ECONNRESET); ClientClosedError if the client is disconnected; AbortError if an AbortSignal was attached and abortedRequired handlingMUST wrap await client.sendCommand() in try-catch. Inspect err.name === 'ReplyError' to handle Redis-level errors (WRONGTYPE, NOSCRIPT, etc.) distinctly from connection-layer errors. If using AbortSignal, also handle AbortError. For commands that mutate state, the safe pattern is: catch → log → re-raise so the caller can decide whether to retry or fail the request.costmediumin prodimmediate exceptionusers seelost datavisibilitysilentSources[1]
Sources
Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.
- [1]redis.io/docs/latest/developError Handling
- [2]redis.io/commands/hsetHset
- [3]redis.io/commands/hgetHget
- [4]redis.io/commands/hgetallHgetall
- [5]redis.io/commands/incrIncr
- [6]redis.io/commands/incrbyIncrby
- [7]redis.io/commands/expireExpire
- [8]redis.io/commands/lpushLpush
- [9]redis.io/commands/lrangeLrange
- [10]redis.io/commands/saddSadd
- [11]redis.io/commands/smembersSmembers
- [12]redis.io/commands/zaddZadd
- [13]redis.io/commands/zrangeZrange
- [14]redis.io/docs/latest/developTranspipe
- [15]redis.io/commands/watchWatch
- [16]redis.io/commands/subscribeSubscribe
- [17]redis.io/commands/publishPublish
- [18]redis.io/commands/quitQuit
- [19]github.com/redis/node-redis/blobredis/node-redis · v4-to-v5.md
Research notes
Curator notes from SOURCES.md captured when the profile was written so you can verify the reasoning, not just the rules.
Sources: redis
This document tracks all research sources used to create the Nark profile for the redis npm package (node-redis v5).
Official Documentation
Primary Error Handling Documentation
- URL: https://redis.io/docs/latest/develop/clients/nodejs/error-handling/
- Date Accessed: 2026-02-25
- Key Findings:
- MUST register error event listener or process will crash
- Common error types: ECONNREFUSED, ETIMEDOUT, ECONNRESET, EAI_AGAIN
- ReplyError types: WRONGTYPE (non-recoverable), BUSY/TRYAGAIN/LOADING (retry with backoff)
- Four error handling patterns: fail fast, graceful degradation, retry with backoff, log and continue
v4 to v5 Migration Guide
- URL: https://github.com/redis/node-redis/blob/master/docs/v4-to-v5.md
- Date Accessed: 2026-02-25
- Key Changes:
- client.quit() → client.close() (graceful shutdown)
- client.disconnect() → client.destroy() (force disconnect)
- Pipeline behavior: v5 discards unwritten commands on disconnect (more predictable)
- Return type changes: Many commands now return number instead of boolean
npm Package Page
- URL: https://www.npmjs.com/package/redis
- Date Accessed: 2026-02-25
- Current Version: 5.11.0 (as of 2026-02-25)
- Key Info: Promise-based API, TypeScript support, automatic reconnection
GitHub Repository
- URL: https://github.com/redis/node-redis
- Date Accessed: 2026-02-25
- Key Info: Official Redis client for Node.js, actively maintained
Real-World Usage Analysis
parse-server (v5.10.0)
- Repository: https://github.com/parse-community/parse-server
- Files Analyzed:
- src/Adapters/Cache/RedisCacheAdapter.js
- src/Adapters/PubSub/RedisPubSub.js
- Patterns Observed:
- ✅ Good: Error listener registered on createClient
- ✅ Good: get() wrapped in try-catch
- ❌ Bad: put(), del(), clear() NOT wrapped in try-catch
- Connection management: connect(), close()
- Queue pattern for operation sequencing
nestjs
- Repository: https://github.com/nestjs/nest
- Files Analyzed:
- integration/microservices/e2e/broadcast-redis.spec.ts
- integration/microservices/e2e/sum-redis.spec.ts
- Patterns Observed:
- Redis used via Transport.REDIS abstraction
- Connection management handled by framework
- Host/port configuration pattern
typeorm
- Repository: https://github.com/typeorm/typeorm
- Files Analyzed:
- src/cache/RedisQueryResultCache.ts
- src/platform/PlatformTools.ts
- Patterns Observed:
- Redis as optional dependency
- Dynamic require() pattern
- Cache abstraction layer
Error Categories
1. Connection Errors (Recoverable)
- ECONNREFUSED: Connection refused - Redis server not running or not reachable
- ETIMEDOUT: Command timeout - Network latency or server overload
- ECONNRESET: Connection reset by peer - Network interruption
- EAI_AGAIN: DNS resolution failure - Temporary DNS issue
Recommended Handling: Retry with exponential backoff, fallback to alternative data source
2. Command Errors (Non-Recoverable)
- WRONGTYPE: Type mismatch - Attempting wrong operation on key type
- Example: LPUSH on a string key
- Fix: Correct the data schema or command
Recommended Handling: Fail fast, fix code or data
3. Command Errors (Recoverable with Bounded Retry)
- BUSY: Redis is busy (e.g., during BGSAVE)
- TRYAGAIN: Command failed, can retry (e.g., cluster redirect)
- LOADING: Redis is loading data from disk
Recommended Handling: Retry with exponential backoff (bounded attempts)
4. Error Events (Critical)
- No error listener: If client doesn't have at least one error listener registered, any error will be thrown and the Node.js process will exit
- AbortError: Command not yet executed but rejected
- InterruptError: Executed commands that failed (e.g., network drop during execution)
Recommended Handling: ALWAYS register error listener on createClient
Security Research
CVE Analysis
- Search Date: 2026-02-25
- Finding: No CVEs found for node-redis npm package client library
- Note: Redis server CVEs (CVE-2025-49844, CVE-2025-21605) are not relevant to client behavior
Conclusion: Focus on error handling best practices rather than security vulnerabilities
Error Handling Patterns
Pattern 1: Fail Fast (Non-Recoverable Errors)
try {
await client.get(key);
} catch (err) {
if (err.name === 'ReplyError' && /WRONGTYPE|ERR /.test(err.message)) {
throw err; // Fix code or data type
}
throw err;
}
Pattern 2: Graceful Degradation (Connection Errors)
try {
const val = await client.get(key);
if (val != null) return val;
} catch (err) {
if (['ECONNREFUSED','ECONNRESET','ETIMEDOUT','EAI_AGAIN'].includes(err.code)) {
logger.warn('Cache unavailable; falling back to DB');
return database.get(key);
}
throw err;
}
return database.get(key);
Pattern 3: Retry with Backoff (Temporary Errors)
async function getWithRetry(key, { attempts = 3, baseDelayMs = 100 } = {}) {
let delay = baseDelayMs;
for (let i = 0; i < attempts; i++) {
try {
return await client.get(key);
} catch (err) {
if (
i < attempts - 1 &&
(['ETIMEDOUT','ECONNRESET','EAI_AGAIN'].includes(err.code) ||
(err.name === 'ReplyError' && /(BUSY|TRYAGAIN|LOADING)/.test(err.message)))
) {
await new Promise(r => setTimeout(r, delay));
delay *= 2;
continue;
}
throw err;
}
}
}
Pattern 4: Log and Continue (Non-Critical Operations)
try {
await client.setEx(key, 3600, value);
} catch (err) {
if (['ECONNREFUSED','ECONNRESET','ETIMEDOUT','EAI_AGAIN'].includes(err.code)) {
logger.warn(`Failed to cache ${key}, continuing without cache`);
} else {
throw err;
}
}
Nark profile Rationale
Why These Functions?
- createClient(): Entry point - error listener is critical for process stability
- client.connect(): Connection establishment - common point of failure (ECONNREFUSED, ETIMEDOUT)
- client.get(): Most common read operation - connection/timeout errors
- client.set(): Most common write operation - data loss risk without error handling
- client.del(): Common deletion operation - connection errors
Why These Postconditions?
-
missing-error-listener (ERROR):
- Without error listener, ANY error will crash the Node.js process
- Severity: ERROR - process crash is unacceptable
- Real-world evidence: All production code registers error listeners
-
connect-no-error-handling (ERROR):
- Connection failures are common (server down, network issues)
- Unhandled promise rejection can crash process
- Severity: ERROR - application cannot function without connection handling
-
get-no-error-handling (ERROR):
- Read operations critical for application logic
- Unhandled errors cause crashes or incorrect behavior
- Severity: ERROR - must handle connection/timeout errors
-
set-no-error-handling (ERROR):
- Write operations risk data loss without error handling
- Silent failures can corrupt application state
- Severity: ERROR - data integrity depends on error handling
-
del-no-error-handling (ERROR):
- Deletion operations affect application state
- Unhandled errors can lead to inconsistent state
- Severity: ERROR - state consistency requires error handling
Deferred for Future Versions
- Pub/Sub patterns: subscribe(), publish(), pSubscribe()
- Transaction commands: multi(), exec(), watch()
- Pipeline operations: Batched command execution
- Cluster support: Cluster-specific commands and error handling
- Stream commands: xAdd(), xRead(), xRange()
- Advanced commands: keys(), scan(), eval()
Rationale: Starting with core CRUD operations that cover 80% of use cases
Version Compatibility
- Target Version Range: ^5.0.0
- Tested Against: v5.10.0, v5.11.0
- Breaking Changes from v4:
- API method renames (quit→close, disconnect→destroy)
- Pipeline behavior changes
- Return type changes (boolean→number)
References
- Error handling | Redis Node.js Docs
- node-redis v4 to v5 Migration
- redis - npm
- GitHub - redis/node-redis
- parse-server RedisCacheAdapter
- NestJS Redis Transport
- TypeORM Redis Cache
Last Updated: 2026-02-25 Researcher: Claude Sonnet 4.5 Contract Version: 1.0.0