Redis & Advanced Caching

Redis with Node.js

18 min Lesson 6 of 30

Redis with Node.js

In this lesson, we'll learn how to integrate Redis with Node.js applications using the ioredis library, one of the most popular and feature-rich Redis clients for Node.js.

Installing ioredis

First, install the ioredis package in your Node.js project:

npm install ioredis

You can also install with yarn:

yarn add ioredis

Basic Connection

Here's how to create a basic connection to Redis:

const Redis = require('ioredis');

// Connect to default localhost:6379
const redis = new Redis();

// Or specify connection options
const redisCustom = new Redis({
  host: '127.0.0.1',
  port: 6379,
  password: 'your_password',
  db: 0
});

Connection with Environment Variables

Best practice is to use environment variables for configuration:

require('dotenv').config();
const Redis = require('ioredis');

const redis = new Redis({
  host: process.env.REDIS_HOST || 'localhost',
  port: process.env.REDIS_PORT || 6379,
  password: process.env.REDIS_PASSWORD,
  db: process.env.REDIS_DB || 0,
  retryStrategy: (times) => {
    const delay = Math.min(times * 50, 2000);
    return delay;
  }
});
Tip: Create a .env file with your Redis credentials: REDIS_HOST=localhost, REDIS_PORT=6379, REDIS_PASSWORD=your_password

Basic Operations

Performing basic Redis operations with ioredis:

// SET command
await redis.set('user:1000', 'John Doe');

// GET command
const username = await redis.get('user:1000');
console.log(username); // 'John Doe'

// SET with expiration (EX in seconds)
await redis.set('session:abc123', 'user_data', 'EX', 3600);

// SETEX (shorthand for SET with expiration)
await redis.setex('token:xyz789', 1800, 'auth_token_value');

// DEL command
await redis.del('user:1000');

// EXISTS command
const exists = await redis.exists('user:1000');
console.log(exists); // 0 (false) or 1 (true)

Working with JSON Data

Store and retrieve JavaScript objects as JSON strings:

const user = {
  id: 1000,
  name: 'John Doe',
  email: 'john@example.com',
  role: 'admin'
};

// Store object as JSON string
await redis.set('user:1000', JSON.stringify(user));

// Retrieve and parse
const userData = await redis.get('user:1000');
const parsedUser = JSON.parse(userData);
console.log(parsedUser.name); // 'John Doe'

Hash Operations

Redis hashes are perfect for storing objects without JSON serialization:

// HSET - set hash field
await redis.hset('user:1000', 'name', 'John Doe');
await redis.hset('user:1000', 'email', 'john@example.com');

// HMSET - set multiple fields at once
await redis.hmset('user:1001', {
  name: 'Jane Smith',
  email: 'jane@example.com',
  age: 28
});

// HGET - get single field
const name = await redis.hget('user:1000', 'name');

// HGETALL - get all fields
const user = await redis.hgetall('user:1000');
console.log(user); // { name: 'John Doe', email: 'john@example.com' }

// HDEL - delete field
await redis.hdel('user:1000', 'email');

Connection Events

Monitor Redis connection status with event listeners:

const Redis = require('ioredis');
const redis = new Redis();

redis.on('connect', () => {
  console.log('✓ Connected to Redis');
});

redis.on('ready', () => {
  console.log('✓ Redis is ready to accept commands');
});

redis.on('error', (err) => {
  console.error('✗ Redis error:', err);
});

redis.on('close', () => {
  console.log('Connection closed');
});

redis.on('reconnecting', () => {
  console.log('Reconnecting to Redis...');
});

Error Handling

Always implement proper error handling when working with Redis:

async function getUserData(userId) {
  try {
    const userData = await redis.get(`user:${userId}`);
    if (!userData) {
      return null;
    }
    return JSON.parse(userData);
  } catch (error) {
    console.error('Redis GET error:', error);
    throw new Error('Failed to retrieve user data');
  }
}

async function setUserData(userId, data) {
  try {
    await redis.setex(
      `user:${userId}`,
      3600,
      JSON.stringify(data)
    );
    return true;
  } catch (error) {
    console.error('Redis SET error:', error);
    return false;
  }
}
Warning: Never expose Redis connection errors directly to users. Log them internally and return generic error messages to clients.

Graceful Shutdown

Always close Redis connections when your application shuts down:

// Graceful shutdown handler
process.on('SIGTERM', async () => {
  console.log('SIGTERM received, closing Redis connection...');
  await redis.quit();
  process.exit(0);
});

process.on('SIGINT', async () => {
  console.log('SIGINT received, closing Redis connection...');
  await redis.quit();
  process.exit(0);
});
Note: Use redis.quit() for graceful shutdown (waits for pending commands) or redis.disconnect() for immediate disconnection.

Connection Pooling

For applications with high concurrency, ioredis automatically manages connection pooling:

const Redis = require('ioredis');

const redis = new Redis({
  host: 'localhost',
  port: 6379,
  maxRetriesPerRequest: 3,
  enableReadyCheck: true,
  enableOfflineQueue: true,
  connectTimeout: 10000,
  lazyConnect: false
});
Exercise: Create a Node.js module that exports a configured Redis client with proper error handling, environment-based configuration, and connection event logging. Test it by storing and retrieving a user object with a 5-minute expiration.