← Back to Blog

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:

ErrorWhen It HappensImpact Without Handler
ECONNREFUSEDRedis server is down or unreachableProcess crash
ECONNRESETConnection dropped mid-requestProcess crash
ETIMEDOUTConnection attempt timed outProcess crash
ENOTFOUNDDNS resolution failed for Redis hostProcess crash
MaxRetriesPerRequestErrorAll retry attempts exhausted (ioredis)Process crash
ReconnectStrategyErrorReconnection strategy returned an error (node-redis)Process crash
Auth failureWrong password or ACL rejectionProcess 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.