🗄️ Database

Redis Caching & Data Store

Last updated: 2025-09-25 02:29:54

Redis In-Memory Data Store

Redis is an in-memory data structure store used as a database, cache, and message broker.

Basic Redis Operations

// Node.js Redis client
const redis = require('redis');
const client = redis.createClient({
  host: 'localhost',
  port: 6379,
  password: 'your-password', // if auth is enabled
  db: 0 // database number
});

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

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

// String operations
async function stringOperations() {
  // Set a key-value pair
  await client.set('user:1:name', 'John Doe');
  
  // Set with expiration (in seconds)
  await client.setex('session:abc123', 3600, JSON.stringify({
    userId: 1,
    loginTime: Date.now()
  }));
  
  // Get a value
  const userName = await client.get('user:1:name');
  console.log('User name:', userName);
  
  // Increment/decrement counters
  await client.incr('page:views');
  await client.incrby('user:1:score', 10);
  
  // Check if key exists
  const exists = await client.exists('user:1:name');
  console.log('Key exists:', exists);
  
  // Delete a key
  await client.del('temp:data');
  
  // Set multiple keys at once
  await client.mset('key1', 'value1', 'key2', 'value2');
  
  // Get multiple keys
  const values = await client.mget('key1', 'key2');
  console.log('Multiple values:', values);
}

// Hash operations
async function hashOperations() {
  const userKey = 'user:123';
  
  // Set hash fields
  await client.hset(userKey, 'name', 'Alice Smith');
  await client.hset(userKey, 'email', 'alice@example.com');
  await client.hset(userKey, 'age', '30');
  
  // Set multiple hash fields
  await client.hmset(userKey, {
    'city': 'New York',
    'country': 'USA',
    'occupation': 'Developer'
  });
  
  // Get hash field
  const name = await client.hget(userKey, 'name');
  console.log('User name:', name);
  
  // Get multiple hash fields
  const userInfo = await client.hmget(userKey, 'name', 'email', 'city');
  console.log('User info:', userInfo);
  
  // Get all hash fields and values
  const allUserData = await client.hgetall(userKey);
  console.log('All user data:', allUserData);
  
  // Increment hash field
  await client.hincrby(userKey, 'login_count', 1);
  
  // Check if hash field exists
  const hasEmail = await client.hexists(userKey, 'email');
  console.log('Has email:', hasEmail);
}

// List operations
async function listOperations() {
  const listKey = 'messages';
  
  // Push elements to list
  await client.lpush(listKey, 'message1', 'message2'); // Left push
  await client.rpush(listKey, 'message3', 'message4'); // Right push
  
  // Get list length
  const length = await client.llen(listKey);
  console.log('List length:', length);
  
  // Get list elements
  const messages = await client.lrange(listKey, 0, -1); // Get all
  console.log('All messages:', messages);
  
  // Get specific range
  const firstTwo = await client.lrange(listKey, 0, 1);
  console.log('First two messages:', firstTwo);
  
  // Pop elements
  const leftPop = await client.lpop(listKey);
  const rightPop = await client.rpop(listKey);
  console.log('Popped from left:', leftPop, 'from right:', rightPop);
  
  // Blocking pop (waits for element)
  // const blocked = await client.blpop(listKey, 10); // Wait 10 seconds
}

Advanced Data Structures

// Set operations
async function setOperations() {
  const setKey1 = 'tags:user:1';
  const setKey2 = 'tags:user:2';
  
  // Add members to set
  await client.sadd(setKey1, 'javascript', 'react', 'nodejs');
  await client.sadd(setKey2, 'python', 'django', 'javascript');
  
  // Get all set members
  const tags1 = await client.smembers(setKey1);
  console.log('User 1 tags:', tags1);
  
  // Check if member exists
  const hasReact = await client.sismember(setKey1, 'react');
  console.log('Has react:', hasReact);
  
  // Set operations
  const intersection = await client.sinter(setKey1, setKey2); // Common elements
  const union = await client.sunion(setKey1, setKey2); // All unique elements
  const difference = await client.sdiff(setKey1, setKey2); // Elements in set1 but not set2
  
  console.log('Intersection:', intersection);
  console.log('Union:', union);
  console.log('Difference:', difference);
  
  // Get random member
  const randomTag = await client.srandmember(setKey1);
  console.log('Random tag:', randomTag);
  
  // Remove member
  await client.srem(setKey1, 'nodejs');
}

// Sorted Set operations
async function sortedSetOperations() {
  const leaderboardKey = 'game:leaderboard';
  
  // Add members with scores
  await client.zadd(leaderboardKey, 100, 'player1');
  await client.zadd(leaderboardKey, 150, 'player2');
  await client.zadd(leaderboardKey, 120, 'player3');
  await client.zadd(leaderboardKey, 200, 'player4');
  
  // Get leaderboard (highest to lowest)
  const topPlayers = await client.zrevrange(leaderboardKey, 0, 2, 'WITHSCORES');
  console.log('Top 3 players:', topPlayers);
  
  // Get rank of player (0-based, lowest score = rank 0)
  const rank = await client.zrank(leaderboardKey, 'player2');
  const revRank = await client.zrevrank(leaderboardKey, 'player2'); // Highest score = rank 0
  console.log('Player2 rank:', rank, 'reverse rank:', revRank);
  
  // Get score of player
  const score = await client.zscore(leaderboardKey, 'player2');
  console.log('Player2 score:', score);
  
  // Increment score
  await client.zincrby(leaderboardKey, 25, 'player1');
  
  // Get players by score range
  const midRangePlayers = await client.zrangebyscore(leaderboardKey, 100, 150, 'WITHSCORES');
  console.log('Players with scores 100-150:', midRangePlayers);
  
  // Count elements in score range
  const count = await client.zcount(leaderboardKey, 100, 200);
  console.log('Players with scores 100-200:', count);
}

// Pub/Sub messaging
async function pubSubExample() {
  // Publisher
  const publisher = redis.createClient();
  
  // Subscriber
  const subscriber = redis.createClient();
  
  subscriber.on('message', (channel, message) => {
    console.log(`Received message from ${channel}: ${message}`);
  });
  
  subscriber.on('subscribe', (channel, count) => {
    console.log(`Subscribed to ${channel}. Total subscriptions: ${count}`);
  });
  
  // Subscribe to channels
  await subscriber.subscribe('news', 'updates');
  
  // Publish messages
  setTimeout(() => {
    publisher.publish('news', 'Breaking news: Redis is awesome!');
    publisher.publish('updates', 'New features available');
  }, 1000);
  
  // Pattern subscribe
  await subscriber.psubscribe('user:*');
  
  setTimeout(() => {
    publisher.publish('user:123:notifications', 'You have a new message');
  }, 2000);
}

Redis with Express.js

// Express.js caching middleware
const express = require('express');
const redis = require('redis');
const app = express();
const client = redis.createClient();

// Caching middleware
function cache(duration = 300) {
  return async (req, res, next) => {
    const key = `cache:${req.originalUrl || req.url}`;
    
    try {
      const cachedData = await client.get(key);
      
      if (cachedData) {
        console.log('Cache hit');
        return res.json(JSON.parse(cachedData));
      }
      
      console.log('Cache miss');
      
      // Store original res.json function
      const originalJson = res.json;
      
      // Override res.json to cache the response
      res.json = function(data) {
        // Cache the response
        client.setex(key, duration, JSON.stringify(data));
        
        // Send the response
        return originalJson.call(this, data);
      };
      
      next();
    } catch (error) {
      console.error('Cache error:', error);
      next();
    }
  };
}

// Session management with Redis
const session = require('express-session');
const RedisStore = require('connect-redis')(session);

app.use(session({
  store: new RedisStore({ client: client }),
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: false, // Set to true in production with HTTPS
    httpOnly: true,
    maxAge: 1000 * 60 * 60 * 24 // 24 hours
  }
}));

// Rate limiting with Redis
function rateLimit(windowMs, maxRequests) {
  return async (req, res, next) => {
    const key = `rate_limit:${req.ip}`;
    const current = await client.incr(key);
    
    if (current === 1) {
      await client.expire(key, Math.ceil(windowMs / 1000));
    }
    
    if (current > maxRequests) {
      return res.status(429).json({
        error: 'Too many requests',
        resetTime: await client.ttl(key)
      });
    }
    
    res.set({
      'X-RateLimit-Limit': maxRequests,
      'X-RateLimit-Remaining': maxRequests - current,
      'X-RateLimit-Reset': Date.now() + (await client.ttl(key) * 1000)
    });
    
    next();
  };
}

// Routes with caching and rate limiting
app.get('/api/users', 
  rateLimit(60000, 100), // 100 requests per minute
  cache(600), // Cache for 10 minutes
  async (req, res) => {
    // Simulate database query
    const users = await getUsersFromDatabase();
    res.json(users);
  }
);

app.get('/api/user/:id',
  rateLimit(60000, 200),
  cache(300),
  async (req, res) => {
    const userId = req.params.id;
    const user = await getUserById(userId);
    
    if (!user) {
      return res.status(404).json({ error: 'User not found' });
    }
    
    res.json(user);
  }
);

// Clear cache endpoint
app.delete('/api/cache/:pattern', async (req, res) => {
  const pattern = req.params.pattern;
  const keys = await client.keys(`cache:*${pattern}*`);
  
  if (keys.length > 0) {
    await client.del(keys);
  }
  
  res.json({ message: `Cleared ${keys.length} cache entries` });
});

async function getUsersFromDatabase() {
  // Simulate database delay
  await new Promise(resolve => setTimeout(resolve, 1000));
  return [
    { id: 1, name: 'John Doe', email: 'john@example.com' },
    { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
  ];
}

async function getUserById(id) {
  const users = await getUsersFromDatabase();
  return users.find(user => user.id == id);
}