What are Hashes?
A Hash is like a JavaScript object or dictionary — a collection of field-value pairs. Perfect for storing related data together, like a user profile or product details.
Think of it as an object inside Redis.
Basic Commands
HSET user:1 name "Alice" age 25 city "NYC"
HGET user:1 name # Get one field: "Alice"
HGETALL user:1 # Get all fields and values
HDEL user:1 city # Delete city field
HEXISTS user:1 name # Does name field exist? Yes
HKEYS user:1 # All field names: ["name", "age", "city"]
HVALS user:1 # All values: ["Alice", 25, "NYC"]
HLEN user:1 # Number of fields: 3
Multiple Fields at Once
# Set multiple fields
HMSET product:101 name "Laptop" price 999 stock 5
# or
HSET product:101 name "Laptop" price 999 stock 5
# Get multiple fields
HMGET product:101 name price
# Returns: ["Laptop", 999]
Updating and Incrementing
HSET user:1 name "Alice" age 25
# Update a field
HSET user:1 age 26
# Increment a field
HINCRBY user:1 age 1 # Now age is 27
HINCRBYFLOAT product:1 price 9.99 # Add decimal to price
Real-world: User Profiles
HSET user:1 \
username "alice" \
email "alice@example.com" \
first_name "Alice" \
last_name "Smith" \
joined "2024-01-15" \
avatar "https://example.com/alice.jpg" \
verified true \
bio "Developer, coffee enthusiast"
# Retrieve full profile
HGETALL user:1
# Get just email for verification
HGET user:1 email
# Update avatar
HSET user:1 avatar "https://example.com/alice-new.jpg"
# Check if verified
HEXISTS user:1 verified
Node.js (ioredis):
import Redis from 'ioredis';
const redis = new Redis();
// Create user profile
async function createUserProfile(userId, profile) {
await redis.hset(`user:${userId}`, {
username: profile.username,
email: profile.email,
first_name: profile.firstName,
last_name: profile.lastName,
joined: new Date().toISOString(),
avatar: profile.avatar || '',
verified: 'false',
bio: profile.bio || ''
});
}
// Get full profile
async function getUserProfile(userId) {
const profile = await redis.hgetall(`user:${userId}`);
if (!Object.keys(profile).length) return null;
return profile;
}
// Get specific fields
async function getUserEmail(userId) {
return await redis.hget(`user:${userId}`, 'email');
}
// Update specific fields
async function updateUserProfile(userId, updates) {
await redis.hset(`user:${userId}`, updates);
}
// Usage
await createUserProfile(1, { username: 'alice', email: 'alice@example.com', firstName: 'Alice', lastName: 'Smith' });
const profile = await getUserProfile(1);
await updateUserProfile(1, { avatar: 'https://example.com/alice-new.jpg', bio: 'Updated bio' });
Python (redis-py):
import redis
from datetime import datetime
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
def create_user_profile(user_id: int, profile: dict):
r.hset(f"user:{user_id}", mapping={
'username': profile['username'],
'email': profile['email'],
'first_name': profile.get('first_name', ''),
'last_name': profile.get('last_name', ''),
'joined': datetime.now().isoformat(),
'avatar': profile.get('avatar', ''),
'verified': 'false',
'bio': profile.get('bio', '')
})
def get_user_profile(user_id: int) -> dict | None:
profile = r.hgetall(f"user:{user_id}")
return profile if profile else None
def update_user_profile(user_id: int, updates: dict):
r.hset(f"user:{user_id}", mapping=updates)
# Usage
create_user_profile(1, {'username': 'alice', 'email': 'alice@example.com'})
profile = get_user_profile(1)
update_user_profile(1, {'avatar': 'https://example.com/alice.jpg'})
Real-world: Product Inventory
HSET product:1001 \
name "MacBook Pro" \
price 1999.99 \
stock 5 \
category "electronics" \
sku "MB-2024-001" \
description "14-inch laptop" \
rating 4.8
# Display product
name = HGET product:1001 name # "MacBook Pro"
price = HGET product:1001 price # 1999.99
available = HGET product:1001 stock > 0
# Someone buys one
HINCRBY product:1001 stock -1 # Decrease stock by 1
# Check new price after sale
HINCRBYFLOAT product:1001 price -99.99 # Apply discount
Node.js (ioredis):
// Create product
async function createProduct(productId, product) {
await redis.hset(`product:${productId}`, {
name: product.name,
price: product.price.toString(),
stock: product.stock.toString(),
category: product.category,
sku: product.sku,
description: product.description,
rating: product.rating?.toString() || '0'
});
}
// Get product for display
async function getProduct(productId) {
const product = await redis.hgetall(`product:${productId}`);
if (!Object.keys(product).length) return null;
return {
...product,
price: parseFloat(product.price),
stock: parseInt(product.stock),
rating: parseFloat(product.rating)
};
}
// Purchase product (atomic stock decrease)
async function purchaseProduct(productId, quantity = 1) {
const stock = await redis.hget(`product:${productId}`, 'stock');
if (parseInt(stock) < quantity) {
throw new Error('Insufficient stock');
}
const newStock = await redis.hincrby(`product:${productId}`, 'stock', -quantity);
return { success: true, remainingStock: newStock };
}
// Apply discount
async function applyDiscount(productId, discountAmount) {
const newPrice = await redis.hincrbyfloat(`product:${productId}`, 'price', -discountAmount);
return { newPrice: parseFloat(newPrice) };
}
Python (redis-py):
def create_product(product_id: int, product: dict):
r.hset(f"product:{product_id}", mapping={
'name': product['name'],
'price': str(product['price']),
'stock': str(product['stock']),
'category': product['category'],
'sku': product['sku'],
'description': product.get('description', ''),
'rating': str(product.get('rating', 0))
})
def get_product(product_id: int) -> dict | None:
product = r.hgetall(f"product:{product_id}")
if not product:
return None
return {
**product,
'price': float(product['price']),
'stock': int(product['stock']),
'rating': float(product['rating'])
}
def purchase_product(product_id: int, quantity: int = 1) -> dict:
stock = r.hget(f"product:{product_id}", 'stock')
if int(stock) < quantity:
raise ValueError("Insufficient stock")
new_stock = r.hincrby(f"product:{product_id}", 'stock', -quantity)
return {'remaining_stock': new_stock}
# Usage
create_product(1001, {'name': 'MacBook Pro', 'price': 1999.99, 'stock': 5, 'category': 'electronics', 'sku': 'MB-2024'})
product = get_product(1001)
purchase_product(1001, 1)
Real-world: Game Leaderboard Entry
HSET leaderboard:user:1 \
username "alice" \
score 2500 \
level 15 \
wins 42 \
losses 8 \
last_played "2026-02-09"
# Get player info
HGETALL leaderboard:user:1
# Update score after win
HINCRBY leaderboard:user:1 score 150
HINCRBY leaderboard:user:1 wins 1
Real-world: Session Data
HSET session:abc123def456 \
user_id "user:1" \
username "alice" \
login_time "2026-02-09T10:00:00Z" \
last_activity "2026-02-09T10:15:00Z" \
ip_address "192.168.1.1" \
user_agent "Chrome/120"
# Check session validity
HEXISTS session:abc123def456 user_id # Is valid
# Update last activity
HSET session:abc123def456 last_activity "2026-02-09T10:20:00Z"
# Get user from session
HGET session:abc123def456 user_id
Node.js (ioredis):
import crypto from 'crypto';
// Create session
async function createSession(userId, username, req) {
const sessionId = crypto.randomBytes(32).toString('hex');
const now = new Date().toISOString();
await redis.hset(`session:${sessionId}`, {
user_id: userId,
username: username,
login_time: now,
last_activity: now,
ip_address: req.ip,
user_agent: req.headers['user-agent']
});
await redis.expire(`session:${sessionId}`, 86400); // 24 hours
return sessionId;
}
// Validate and refresh session
async function validateSession(sessionId) {
const exists = await redis.hexists(`session:${sessionId}`, 'user_id');
if (!exists) return null;
// Update last activity and refresh TTL
await redis.hset(`session:${sessionId}`, 'last_activity', new Date().toISOString());
await redis.expire(`session:${sessionId}`, 86400);
return await redis.hgetall(`session:${sessionId}`);
}
// Destroy session
async function destroySession(sessionId) {
await redis.del(`session:${sessionId}`);
}
Python (redis-py):
import secrets
from datetime import datetime
def create_session(user_id: str, username: str, ip_address: str, user_agent: str = '') -> str:
session_id = secrets.token_hex(32)
now = datetime.now().isoformat()
r.hset(f"session:{session_id}", mapping={
'user_id': user_id,
'username': username,
'login_time': now,
'last_activity': now,
'ip_address': ip_address,
'user_agent': user_agent
})
r.expire(f"session:{session_id}", 86400) # 24 hours
return session_id
def validate_session(session_id: str) -> dict | None:
if not r.hexists(f"session:{session_id}", 'user_id'):
return None
# Update last activity and refresh TTL
r.hset(f"session:{session_id}", 'last_activity', datetime.now().isoformat())
r.expire(f"session:{session_id}", 86400)
return r.hgetall(f"session:{session_id}")
def destroy_session(session_id: str):
r.delete(f"session:{session_id}")
# Usage
session_id = create_session("user:1", "alice", "192.168.1.1", "Chrome/120")
session = validate_session(session_id)
destroy_session(session_id)
Real-world: Configuration
HSET config:app \
name "MyApp" \
version "1.2.3" \
max_connections 1000 \
timeout 30 \
debug false \
theme "dark"
# Get config
HGET config:app theme # "dark"
# Update config
HSET config:app theme "light"
Efficiency: Hash vs Multiple Strings
Using Hashes (better):
HSET user:1 name "Alice" age 25 email "alice@example.com"
# One key, efficient storage
MEMORY USAGE user:1 # Less memory
Using Multiple Strings (worse):
SET user:1:name "Alice"
SET user:1:age 25
SET user:1:email "alice@example.com"
# Three keys, more overhead
MEMORY USAGE user:1:name + MEMORY USAGE user:1:age + ...
Hashes use less memory and fewer operations.
Common Patterns
User Profile Retrieval
# Store full profile in one hash
HSET profile:user:1 \
name "Alice" \
email "alice@example.com" \
avatar "url" \
bio "bio"
# Get profile (one operation)
profile = HGETALL profile:user:1
# Update field (one operation)
HSET profile:user:1 bio "New bio"
Shopping Cart
# Store items in user's cart
HSET cart:user:1 \
product:101 1 \ # quantity of product:101
product:202 3 \ # quantity of product:202
product:303 2
# Get cart
HGETALL cart:user:1
# Add item or update quantity
HINCRBY cart:user:1 product:101 2 # Add 2 more
# Remove item
HDEL cart:user:1 product:303
Node.js (ioredis):
// Add item to cart
async function addToCart(userId, productId, quantity = 1) {
await redis.hincrby(`cart:user:${userId}`, `product:${productId}`, quantity);
await redis.expire(`cart:user:${userId}`, 86400 * 7); // Cart expires in 7 days
}
// Get cart contents
async function getCart(userId) {
const cart = await redis.hgetall(`cart:user:${userId}`);
return Object.entries(cart).map(([key, quantity]) => ({
productId: key.replace('product:', ''),
quantity: parseInt(quantity)
}));
}
// Update item quantity
async function updateCartItem(userId, productId, quantity) {
if (quantity <= 0) {
await redis.hdel(`cart:user:${userId}`, `product:${productId}`);
} else {
await redis.hset(`cart:user:${userId}`, `product:${productId}`, quantity);
}
}
// Remove item from cart
async function removeFromCart(userId, productId) {
await redis.hdel(`cart:user:${userId}`, `product:${productId}`);
}
// Clear cart
async function clearCart(userId) {
await redis.del(`cart:user:${userId}`);
}
Python (redis-py):
def add_to_cart(user_id: int, product_id: int, quantity: int = 1):
r.hincrby(f"cart:user:{user_id}", f"product:{product_id}", quantity)
r.expire(f"cart:user:{user_id}", 86400 * 7) # 7 days
def get_cart(user_id: int) -> list:
cart = r.hgetall(f"cart:user:{user_id}")
return [
{'product_id': key.replace('product:', ''), 'quantity': int(qty)}
for key, qty in cart.items()
]
def update_cart_item(user_id: int, product_id: int, quantity: int):
if quantity <= 0:
r.hdel(f"cart:user:{user_id}", f"product:{product_id}")
else:
r.hset(f"cart:user:{user_id}", f"product:{product_id}", quantity)
def remove_from_cart(user_id: int, product_id: int):
r.hdel(f"cart:user:{user_id}", f"product:{product_id}")
def clear_cart(user_id: int):
r.delete(f"cart:user:{user_id}")
# Usage
add_to_cart(1, 101, 2)
add_to_cart(1, 202, 1)
cart = get_cart(1)
update_cart_item(1, 101, 5)
remove_from_cart(1, 202)
Caching JSON Data
# Cache API response as hash instead of JSON string
HSET api:user:response:1 \
status "active" \
name "Alice" \
verified true \
tier "premium"
# Later access specific field without parsing JSON
HGET api:user:response:1 tier
Rate Limiting by Endpoint
# Track rate limit for each endpoint per user
HSET ratelimit:user:1 \
api:posts:create 5 \ # 5 uses
api:messages:send 10 \ # 10 uses
api:search:query 100
# Check if user can use endpoint
count = HGET ratelimit:user:1 "api:posts:create"
if count >= LIMIT:
REJECT("Rate limit exceeded")
# Decrement on use
HINCRBY ratelimit:user:1 "api:posts:create" -1