How to Handle Redis Connection Errors in TypeScript
By Nark Team
Redis clients in Node.js emit an error event when the connection drops. If your code does not register a handler for that event, Node.js treats it as an unhandled error and crashes the process. This is the most common Redis-related production incident in TypeScript applications. The fix is a single .on('error') call, but most codebases miss it because neither ESLint nor TypeScript's type system warns you about it.
Quick Answer: Always register
.on('error', handler)on your Redis client immediately after creating it. Without this handler, a Redis connection failure crashes your Node.js process. To check your entire codebase for missing Redis error handlers automatically:npx nark --tsconfig ./tsconfig.json
Why Does Redis Crash My Node.js Process?
Node.js EventEmitter has a built-in behavior: if an error event is emitted and no listener is registered, Node.js throws the error as an uncaught exception. This terminates the process.
Redis clients (ioredis, node-redis, redis) are EventEmitters. When the TCP connection to your Redis server drops, times out, or is refused, the client emits an error event. If you never called .on('error', ...), your process dies.
import Redis from 'ioredis';
// This crashes your process when Redis goes down
const redis = new Redis({ host: 'redis.example.com', port: 6379 });
// If Redis disconnects, Node.js throws:
// Error: connect ECONNREFUSED 10.0.0.1:6379
// at TCPConnectWrap.afterConnect [as oncomplete]
// Process exits with code 1
This is not a bug in Redis or ioredis. It is how Node.js EventEmitters work. The fix is to register an error handler.
How to Handle Redis Errors with ioredis
ioredis is the most popular Redis client for TypeScript. Here is the correct pattern:
import Redis from 'ioredis';
const redis = new Redis({
host: process.env.REDIS_HOST ?? 'localhost',
port: Number(process.env.REDIS_PORT ?? 6379),
maxRetriesPerRequest: 3,
retryStrategy(times) {
const delay = Math.min(times * 200, 5000);
return delay; // retry after delay ms, or return null to stop
},
});
// Register error handler immediately — prevents process crash
redis.on('error', (error) => {
console.error('Redis connection error:', error.message);
// Don't re-throw — that would crash the process
// Log, send to monitoring, and let ioredis retry
});
redis.on('connect', () => {
console.log('Redis connected');
});
redis.on('close', () => {
console.warn('Redis connection closed');
});
The .on('error') handler is the critical line. Without it, any connection interruption terminates your process.
How to Handle Redis Errors with node-redis
If you use the redis package (node-redis v4+), the pattern is similar:
import { createClient } from 'redis';
const redis = createClient({
url: process.env.REDIS_URL ?? 'redis://localhost:6379',
socket: {
reconnectStrategy(retries) {
if (retries > 10) return new Error('Max retries reached');
return Math.min(retries * 200, 5000);
},
},
});
// Register error handler before connecting
redis.on('error', (error) => {
console.error('Redis client error:', error.message);
});
await redis.connect();
With node-redis v4, you must also explicitly call .connect(). But the .on('error') handler is still required to prevent process crashes during connection drops after the initial connect.
What Errors Does Redis Throw?
Redis clients emit several types of errors through the error event:
| Error | When It Happens | Impact Without Handler |
|---|---|---|
ECONNREFUSED | Redis server is down or unreachable | Process crash |
ECONNRESET | Connection dropped mid-request | Process crash |
ETIMEDOUT | Connection attempt timed out | Process crash |
ENOTFOUND | DNS resolution failed for Redis host | Process crash |
MaxRetriesPerRequestError | All retry attempts exhausted (ioredis) | Process crash |
ReconnectStrategyError | Reconnection strategy returned an error (node-redis) | Process crash |
| Auth failure | Wrong password or ACL rejection | Process crash |
Every single one of these crashes your process if you do not have .on('error') registered.
Common Mistakes
Mistake 1: Registering the Handler Too Late
import Redis from 'ioredis';
const redis = new Redis({ host: 'redis.example.com' });
// ... 50 lines of other setup code ...
// Too late — if Redis fails during connection, the error fires
// before this handler is registered
redis.on('error', (err) => console.error(err));
Register .on('error') immediately after creating the client. Do not put other code between the constructor and the error handler.
Mistake 2: Re-throwing Inside the Error Handler
redis.on('error', (error) => {
// This defeats the purpose — re-throwing crashes the process
throw error;
});
The error handler exists to absorb the error and let the client retry. Log it. Send it to your monitoring service. Do not re-throw it.
Mistake 3: Only Handling Errors on Commands
try {
await redis.get('user:123');
} catch (error) {
// This catches command-level errors, but NOT connection-level errors
// Connection errors fire on the EventEmitter, not on individual commands
console.error(error);
}
Try-catch on individual commands handles command failures (wrong key type, etc.). It does not handle connection-level errors that fire on the EventEmitter. You need both.
Mistake 4: Forgetting the Error Handler in Tests
// test/cache.test.ts
const redis = new Redis({ host: 'localhost' });
// No .on('error') — if Redis isn't running locally, test suite crashes
// instead of failing gracefully
Register error handlers in test setup too. A missing local Redis instance should fail your test, not crash the entire test runner.
The Complete Pattern
Here is the production-ready pattern that handles connection errors, reconnection, and graceful degradation:
import Redis from 'ioredis';
export function createRedisClient(): Redis {
const redis = new Redis({
host: process.env.REDIS_HOST ?? 'localhost',
port: Number(process.env.REDIS_PORT ?? 6379),
password: process.env.REDIS_PASSWORD,
maxRetriesPerRequest: 3,
retryStrategy(times) {
if (times > 10) {
return null; // stop retrying after 10 attempts
}
return Math.min(times * 200, 5000);
},
});
redis.on('error', (error) => {
console.error('Redis error:', error.message);
// Send to monitoring (Sentry, Datadog, etc.)
});
redis.on('connect', () => {
console.log('Redis connected');
});
redis.on('reconnecting', () => {
console.warn('Redis reconnecting...');
});
return redis;
}
And for individual commands where you want graceful degradation (cache miss instead of crash):
async function getCachedUser(userId: string): Promise<User | null> {
try {
const cached = await redis.get(`user:${userId}`);
if (cached) return JSON.parse(cached);
return null;
} catch (error) {
// Redis is down — treat as cache miss, not fatal error
console.warn('Redis unavailable, skipping cache:', error);
return null;
}
}
How Nark Catches Missing Redis Error Handlers
Nark's profile for Redis checks whether your code registers .on('error') on Redis client instances. If you create a Redis client without an error handler, Nark flags it:
ERROR ioredis Redis client created without .on('error') handler
src/lib/redis.ts:5 in createRedisClient()
ioredis emits 'error' events on connection failure
Fix: register redis.on('error', handler) immediately after creation
This check works for both ioredis and redis (node-redis) packages. Nark detects the client creation pattern and verifies that .on('error') is called on the same instance.
npx nark --tsconfig ./tsconfig.json
Frequently Asked Questions
Does ioredis auto-reconnect?
Yes. By default, ioredis retries the connection with exponential backoff. But it still emits error events during each failed retry attempt. Without .on('error'), each retry failure crashes your process.
Does node-redis v4 auto-reconnect?
Yes, if you provide a reconnectStrategy in the socket options. Like ioredis, it emits error events during reconnection attempts that require a handler.
What about Redis Cluster?
Same pattern. Redis.Cluster in ioredis also extends EventEmitter. Register .on('error') on the cluster instance.
const cluster = new Redis.Cluster([
{ host: 'redis-1.example.com', port: 6379 },
{ host: 'redis-2.example.com', port: 6379 },
]);
cluster.on('error', (error) => {
console.error('Redis Cluster error:', error.message);
});
Will ESLint catch a missing Redis error handler?
No. ESLint has no concept of EventEmitter error handling requirements. It does not know that a new Redis() call needs a corresponding .on('error'). Nark checks this specifically.
Try It Now
npx nark --tsconfig ./tsconfig.json
Nark checks 160+ packages — including axios, prisma, stripe, and redis — for correct error handling, including Redis client error handler registration. Run it on your project to find missing .on('error') handlers before they crash your production process.