What are Strings?
A String is the simplest Redis data type, just bytes. It can hold text, numbers, binary data, JSON, or anything else. Every value in Redis is ultimately stored as a string, but this is the basic building block.
Basic Commands
SET key "value" # Store a string
GET key # Get the string
APPEND key " more" # Add to the end
STRLEN key # How long is it?
GETRANGE key 0 4 # Get part of it (chars 0-4)
SETRANGE key 0 "new" # Replace part of it
Working with Numbers
Strings can hold numbers, and Redis gives you special commands to work with them:
SET counter 10
INCR counter # Increment by 1 → 11
DECR counter # Decrement by 1 → 10
INCRBY counter 5 # Add 5 → 15
DECRBY counter 3 # Subtract 3 → 12
INCRBYFLOAT price 0.99 # Add decimal → 12.99
Use Cases
Session Tokens
Store user session data:
SET session:user:1 "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
SET session:user:2 "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
# Check if session exists
GET session:user:1
Node.js (ioredis):
import Redis from 'ioredis';
const redis = new Redis();
// Store session
await redis.set(`session:user:${userId}`, token, 'EX', 86400); // 24 hours
// Get session
const session = await redis.get(`session:user:${userId}`);
if (!session) {
throw new Error('Session expired');
}
Python (redis-py):
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
# Store session
def store_session(user_id: int, token: str):
r.set(f"session:user:{user_id}", token, ex=86400) # 24 hours
# Get session
def get_session(user_id: int) -> str | None:
session = r.get(f"session:user:{user_id}")
return session # Returns None if expired/not found
# Usage
store_session(1, "eyJhbGciOiJIUzI1NiIs...")
token = get_session(1)
if not token:
print("Session expired")
Page View Counters
Track how many times something is viewed:
SET page:views 0
INCR page:views # First visit: 1
INCR page:views # Second visit: 2
INCRBY page:views 100 # Bulk add 100 views
# Get total views
GET page:views # 102
Node.js:
// Increment page view
await redis.incr(`page:${pageId}:views`);
// Get view count
const views = await redis.get(`page:${pageId}:views`);
console.log(`Page has ${views} views`);
Python:
# Increment page view
def increment_page_view(page_id: int) -> int:
return r.incr(f"page:{page_id}:views")
# Get view count
def get_page_views(page_id: int) -> int:
views = r.get(f"page:{page_id}:views")
return int(views or 0)
# Usage
increment_page_view(1)
print(f"Page has {get_page_views(1)} views")
Rate Limiting
Limit API requests per user:
SET api:requests:user:1 0
INCR api:requests:user:1 # First request
INCR api:requests:user:1 # Second request
# Get current count
count = GET api:requests:user:1
if count > 100:
REJECT("Rate limit exceeded")
Node.js:
async function checkRateLimit(userId, limit = 100) {
const key = `ratelimit:${userId}:${Math.floor(Date.now() / 60000)}`; // Per minute
const count = await redis.incr(key);
if (count === 1) {
await redis.expire(key, 60); // Expire after 60 seconds
}
if (count > limit) {
throw new Error('Rate limit exceeded');
}
return count;
}
Python:
import time
def check_rate_limit(user_id: str, limit: int = 100) -> tuple[bool, int]:
"""Returns (is_allowed, current_count)"""
key = f"ratelimit:{user_id}:{int(time.time() // 60)}" # Per minute
count = r.incr(key)
if count == 1:
r.expire(key, 60)
return count <= limit, count
# Usage
is_allowed, count = check_rate_limit("user:123")
if not is_allowed:
print(f"Rate limit exceeded: {count}/100")
Storing JSON Objects
Cache entire API responses as JSON:
SET user:profile:1 '{"id":1,"name":"Alice","email":"alice@example.com","age":25}'
SET product:1 '{"name":"Laptop","price":999,"stock":5,"category":"electronics"}'
# Later retrieve
GET user:profile:1
GET product:1
Node.js:
// Store JSON
const user = { id: 1, name: 'Alice', email: 'alice@example.com' };
await redis.set(`user:${user.id}`, JSON.stringify(user), 'EX', 3600);
// Retrieve and parse
const cached = await redis.get(`user:${userId}`);
if (cached) {
return JSON.parse(cached);
}
Python:
import json
# Store JSON
def cache_user(user: dict, ttl: int = 3600):
r.set(f"user:{user['id']}", json.dumps(user), ex=ttl)
# Retrieve and parse
def get_cached_user(user_id: int) -> dict | None:
cached = r.get(f"user:{user_id}")
return json.loads(cached) if cached else None
# Usage
user = {"id": 1, "name": "Alice", "email": "alice@example.com"}
cache_user(user)
retrieved = get_cached_user(1)
HTML Page Caching
Cache rendered HTML pages:
SET page:home "<html><body>...</body></html>"
SET page:about "<html><body>...</body></html>"
# Get cached page
GET page:home
Node.js (HTML Caching Middleware):
// Middleware to cache rendered pages
async function htmlCacheMiddleware(req, res, next) {
const cacheKey = `page:${req.path}`;
// Check cache first
const cachedHtml = await redis.get(cacheKey);
if (cachedHtml) {
res.setHeader('X-Cache', 'HIT');
return res.send(cachedHtml);
}
// Store original send
const originalSend = res.send.bind(res);
res.send = async (html) => {
// Cache for 10 minutes
await redis.set(cacheKey, html, 'EX', 600);
res.setHeader('X-Cache', 'MISS');
return originalSend(html);
};
next();
}
Python:
def get_cached_page(page_name: str) -> tuple[str | None, bool]:
"""Returns (html_content, is_cache_hit)"""
cache_key = f"page:{page_name}"
cached_html = r.get(cache_key)
if cached_html:
return cached_html, True
return None, False
def cache_page(page_name: str, html: str, ttl: int = 600):
"""Cache HTML for 10 minutes by default"""
r.set(f"page:{page_name}", html, ex=ttl)
# Usage
html, hit = get_cached_page("home")
if not hit:
html = render_page("home") # Your render function
cache_page("home", html)
User Preferences
Store configuration or settings:
SET theme:user:1 "dark"
SET language:user:1 "en"
SET timezone:user:1 "UTC"
# Update a setting
SET theme:user:1 "light"
Node.js (User Preferences):
async function getUserPreferences(userId) {
const [theme, language, timezone] = await Promise.all([
redis.get(`theme:user:${userId}`),
redis.get(`language:user:${userId}`),
redis.get(`timezone:user:${userId}`)
]);
return {
theme: theme || 'light',
language: language || 'en',
timezone: timezone || 'UTC'
};
}
async function updateUserPreference(userId, key, value) {
await redis.set(`${key}:user:${userId}`, value);
}
// Usage
await updateUserPreference(1, 'theme', 'dark');
const prefs = await getUserPreferences(1);
Python:
def get_user_preferences(user_id: int) -> dict:
theme = r.get(f"theme:user:{user_id}")
language = r.get(f"language:user:{user_id}")
timezone = r.get(f"timezone:user:{user_id}")
return {
"theme": theme or "light",
"language": language or "en",
"timezone": timezone or "UTC"
}
def update_user_preference(user_id: int, key: str, value: str):
r.set(f"{key}:user:{user_id}", value)
# Usage
update_user_preference(1, "theme", "dark")
prefs = get_user_preferences(1)
print(prefs) # {'theme': 'dark', 'language': 'en', 'timezone': 'UTC'}
Strings with Expiration
All strings can expire automatically (TTL):
SET temp_token "abc123" EX 3600 # Expires in 1 hour
SET cache_key "data" PX 5000 # Expires in 5000 milliseconds
SET session:user:1 "data" EX 86400 # Expires in 1 day
# Check how much time is left
TTL session:user:1 # Seconds remaining
PTTL session:user:1 # Milliseconds remaining
Perfect for:
- Session tokens that expire
- OTP codes (valid for 5 minutes)
- Cache that needs refreshing
- Temporary locks
Get and Set in One Operation
Sometimes you need to set a new value but keep the old one:
SET counter 10
GETSET counter 20 # Returns old value (10), sets to 20
GET counter # Now it's 20
Or set only if the key doesn’t exist:
SETNX key "value" # Set if Not eXists
# If key exists, nothing happens
Batch Operations
Work with multiple strings at once:
MSET key1 "value1" key2 "value2" key3 "value3"
MGET key1 key2 key3 # Returns all three values
Common Patterns
Counter Pattern
Track anything that increments:
# Page views
INCR page:1:views
# API call count
INCR api:calls:today
# Post likes
INCR post:123:likes
# Session active count
INCR users:online
Node.js (Counter Examples):
// Like a post
async function likePost(postId, userId) {
const likesKey = `post:${postId}:likes`;
const userLikedKey = `post:${postId}:liked:${userId}`;
// Check if user already liked
const alreadyLiked = await redis.exists(userLikedKey);
if (alreadyLiked) {
return { success: false, message: 'Already liked' };
}
// Increment likes and mark user as liked
const [newCount] = await redis.multi()
.incr(likesKey)
.set(userLikedKey, '1')
.exec();
return { success: true, likes: newCount };
}
// Track daily API calls
async function trackApiCall(endpoint) {
const today = new Date().toISOString().split('T')[0];
const key = `api:calls:${endpoint}:${today}`;
const count = await redis.incr(key);
await redis.expire(key, 86400 * 7); // Keep for 7 days
return count;
}
Python:
from datetime import date
def like_post(post_id: int, user_id: int) -> dict:
likes_key = f"post:{post_id}:likes"
user_liked_key = f"post:{post_id}:liked:{user_id}"
# Check if user already liked
if r.exists(user_liked_key):
return {"success": False, "message": "Already liked"}
# Increment likes and mark user as liked (atomic pipeline)
pipe = r.pipeline()
pipe.incr(likes_key)
pipe.set(user_liked_key, "1")
results = pipe.execute()
return {"success": True, "likes": results[0]}
def track_api_call(endpoint: str) -> int:
today = date.today().isoformat()
key = f"api:calls:{endpoint}:{today}"
count = r.incr(key)
r.expire(key, 86400 * 7) # Keep for 7 days
return count
# Usage
result = like_post(123, 1)
count = track_api_call("/api/users")
Cache Pattern
Cache expensive operations:
# First request - not in cache
user = GET user:1:profile
if user is empty:
user = database.query("SELECT * FROM users WHERE id=1")
SET user:1:profile user
SET user:1:profile EX 3600 # Expire in 1 hour
# Second request - instant from cache
user = GET user:1:profile # Returns instantly
Node.js (Cache-Aside Pattern):
async function getUserById(userId) {
const cacheKey = `user:${userId}`;
// Try cache first
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// Cache miss - fetch from database
const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
// Store in cache for 1 hour
await redis.set(cacheKey, JSON.stringify(user), 'EX', 3600);
return user;
}
Python:
def get_user_by_id(user_id: int, db_fetch_func) -> dict | None:
cache_key = f"user:{user_id}"
# Try cache first
cached = r.get(cache_key)
if cached:
return json.loads(cached)
# Cache miss - fetch from database
user = db_fetch_func(user_id) # Your database function
if user:
# Store in cache for 1 hour
r.set(cache_key, json.dumps(user), ex=3600)
return user
# Usage
user = get_user_by_id(1, lambda uid: db.query(f"SELECT * FROM users WHERE id={uid}"))
Distributed Lock Pattern
Simple locks across multiple servers:
# Try to acquire lock
SET lock:resource:1 "locked" NX EX 10 # Only if not exists, expire in 10s
# NX = only set if key doesn't exist
# Lock acquired, do work...
# Release lock
DEL lock:resource:1
Node.js (Distributed Lock):
async function acquireLock(resourceId, ttl = 10) {
const lockKey = `lock:resource:${resourceId}`;
const lockId = crypto.randomUUID(); // Unique lock identifier
// Try to acquire lock with NX (only if not exists)
const acquired = await redis.set(lockKey, lockId, 'NX', 'EX', ttl);
if (acquired) {
return { success: true, lockId };
}
return { success: false };
}
async function releaseLock(resourceId, lockId) {
const lockKey = `lock:resource:${resourceId}`;
// Only release if we own the lock
const currentLock = await redis.get(lockKey);
if (currentLock === lockId) {
await redis.del(lockKey);
return true;
}
return false;
}
// Usage
const { success, lockId } = await acquireLock('order:123');
if (success) {
try {
// Do critical work...
await processOrder(123);
} finally {
await releaseLock('order:123', lockId);
}
}
Python:
import uuid
def acquire_lock(resource_id: str, ttl: int = 10) -> tuple[bool, str]:
"""Try to acquire a distributed lock. Returns (success, lock_id)"""
lock_key = f"lock:resource:{resource_id}"
lock_id = str(uuid.uuid4()) # Unique lock identifier
# Try to acquire lock with NX (only if not exists)
acquired = r.set(lock_key, lock_id, nx=True, ex=ttl)
if acquired:
return True, lock_id
return False, ""
def release_lock(resource_id: str, lock_id: str) -> bool:
"""Release lock only if we own it"""
lock_key = f"lock:resource:{resource_id}"
# Only release if we own the lock
current_lock = r.get(lock_key)
if current_lock and current_lock == lock_id:
r.delete(lock_key)
return True
return False
# Usage
success, lock_id = acquire_lock("order:123")
if success:
try:
# Do critical work...
process_order(123)
finally:
release_lock("order:123", lock_id)
else:
print("Resource is locked by another process")