DEV Community

Otto
Otto

Posted on

Redis in 2026: Caching, Sessions, and Real-Time Features You Actually Need

Redis in 2026: Caching, Sessions, and Real-Time Features You Actually Need

Every serious web app eventually hits a wall: the database is too slow, sessions don't scale, or real-time features feel impossible.

Redis solves all three.

This guide covers practical Redis patterns you'll actually use in production in 2026.


What Is Redis?

Redis is an in-memory data structure store — it lives in RAM, so reads/writes are microseconds-fast. It's used as:

  • Cache — store expensive query results
  • Session store — stateless HTTP sessions
  • Message broker — pub/sub for real-time features
  • Rate limiter — protect APIs from abuse
  • Job queue — background task processing

In 2026, Redis is installed on virtually every production server.


Installation

# Docker (recommended for dev)
docker run -d -p 6379:6379 redis:alpine

# Node.js client
npm install ioredis

# Python client
pip install redis
Enter fullscreen mode Exit fullscreen mode

Pattern 1: Caching Database Queries

This single pattern can make your app feel 10x faster.

// cache.js
const Redis = require('ioredis');
const redis = new Redis({ host: 'localhost', port: 6379 });

async function getCachedUser(userId) {
  const cacheKey = `user:${userId}`;

  // Check cache first
  const cached = await redis.get(cacheKey);
  if (cached) {
    return JSON.parse(cached);
  }

  // Not cached — fetch from database
  const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]);

  // Store in cache for 5 minutes
  await redis.setex(cacheKey, 300, JSON.stringify(user));

  return user;
}

// Invalidate cache when user updates
async function updateUser(userId, data) {
  await db.query('UPDATE users SET ? WHERE id = ?', [data, userId]);
  await redis.del(`user:${userId}`); // Clear stale cache
}
Enter fullscreen mode Exit fullscreen mode

Before Redis: 150ms per request (DB query)
After Redis: 2ms per request (cache hit) — 98.6% faster


Pattern 2: Session Management

// Express session with Redis store
const session = require('express-session');
const RedisStore = require('connect-redis').default;

app.use(session({
  store: new RedisStore({ client: redis }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
  },
}));

// Now sessions scale horizontally — any server can serve any session
Enter fullscreen mode Exit fullscreen mode

Pattern 3: Rate Limiting

Protect your API without a complex library:

async function checkRateLimit(ip, maxRequests = 100, windowSeconds = 60) {
  const key = `rate_limit:${ip}`;
  const current = await redis.incr(key);

  if (current === 1) {
    // First request — set expiry
    await redis.expire(key, windowSeconds);
  }

  return {
    allowed: current <= maxRequests,
    remaining: Math.max(0, maxRequests - current),
    total: maxRequests,
  };
}

// Middleware
app.use(async (req, res, next) => {
  const { allowed, remaining } = await checkRateLimit(req.ip);

  res.set('X-RateLimit-Remaining', remaining);

  if (!allowed) {
    return res.status(429).json({ error: 'Too many requests' });
  }

  next();
});
Enter fullscreen mode Exit fullscreen mode

Pattern 4: Pub/Sub for Real-Time Features

// Real-time notifications
const publisher = new Redis();
const subscriber = new Redis();

// Publish a message
async function notifyUser(userId, message) {
  await publisher.publish(`user:${userId}:notifications`, JSON.stringify({
    type: 'notification',
    message,
    timestamp: Date.now(),
  }));
}

// Subscribe to messages
subscriber.subscribe('user:123:notifications');
subscriber.on('message', (channel, message) => {
  const data = JSON.parse(message);
  // Send to WebSocket client
  sendToWebSocket(data);
});

// Use case: chat apps, live dashboards, collaborative tools
Enter fullscreen mode Exit fullscreen mode

Pattern 5: Leaderboards with Sorted Sets

// Game/app leaderboard
async function updateScore(userId, score) {
  await redis.zadd('leaderboard', score, userId);
}

async function getTopPlayers(count = 10) {
  // Get top N players with scores
  const results = await redis.zrevrangebyscore(
    'leaderboard', '+inf', '-inf',
    'WITHSCORES', 'LIMIT', 0, count
  );

  const players = [];
  for (let i = 0; i < results.length; i += 2) {
    players.push({
      userId: results[i],
      score: parseInt(results[i + 1]),
      rank: players.length + 1,
    });
  }

  return players;
}

async function getUserRank(userId) {
  const rank = await redis.zrevrank('leaderboard', userId);
  return rank !== null ? rank + 1 : null; // 1-indexed
}
Enter fullscreen mode Exit fullscreen mode

Python Version

import redis
import json
from datetime import timedelta

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

# Cache function decorator
def cached(ttl_seconds=300):
    def decorator(func):
        def wrapper(*args, **kwargs):
            cache_key = f"{func.__name__}:{args}:{kwargs}"
            cached_value = r.get(cache_key)
            if cached_value:
                return json.loads(cached_value)

            result = func(*args, **kwargs)
            r.setex(cache_key, ttl_seconds, json.dumps(result))
            return result
        return wrapper
    return decorator

@cached(ttl_seconds=60)
def get_user(user_id):
    # This expensive call is only made once per minute
    return db.query(f"SELECT * FROM users WHERE id = {user_id}")
Enter fullscreen mode Exit fullscreen mode

Redis Data Types Cheat Sheet

Type Use Case Key Commands
String Cache, counters, flags GET, SET, INCR, EXPIRE
Hash User profiles, configs HGET, HSET, HMGET
List Queues, activity feeds LPUSH, RPOP, LRANGE
Set Tags, unique visitors SADD, SMEMBERS, SINTER
Sorted Set Leaderboards, priority queues ZADD, ZRANGE, ZREVRANK
Stream Event logs, message queues XADD, XREAD, XGROUP

Production Tips

Always set TTLs — memory is finite, stale data is dangerous

Use key namespacinguser:123:profile, not just 123

Enable persistence — RDB snapshots or AOF for durability

Monitor memoryredis-cli INFO memory

Use connection pooling — don't create a new connection per request

⚠️ Don't store sensitive data unencrypted — Redis is typically not encrypted at rest


When Not to Use Redis

  • Long-term data storage → Use PostgreSQL/MongoDB
  • Complex queries → Your relational DB handles this better
  • Large files/blobs → Use S3 or filesystem

Redis shines as a complement to, not replacement for, your primary database.


With these five patterns, you'll solve 90% of the performance and real-time problems you'll face building production apps in 2026.

Start with caching — it's the highest ROI change you can make to a slow app.


Building projects and tools for developers and freelancers. Resources at guittet.gumroad.com — including the Freelancer OS Notion template and AI prompt packs.

Top comments (0)