Welcome to the heart of Redis! At its most fundamental level, Redis is a key-value store, and the most basic value you can store is a String. Understanding how to work with Strings and manage keys is crucial for building any application with Redis.
In this chapter, we’ll cover:
- What Redis Strings are and their capabilities.
- Basic commands for creating, reading, updating, and deleting (CRUD) string keys.
- Advanced string operations like increments, decrements, and appending.
- Key management strategies, including checking existence, renaming, and deleting.
- The critical concept of key expiration (TTL).
What are Redis Strings?
A Redis String is the simplest type of value you can associate with a key. Despite the name “string,” it’s binary-safe, meaning it can store anything from text (like “Hello World!”) to integers, floating-point numbers, or even binary data like JPEG images or serialized objects, up to 512MB in size.
Key characteristics:
- Binary-safe: No special handling for different character encodings.
- Atomic operations: Operations on strings are atomic, ensuring consistency.
- Versatile: Can be used for simple caching, counters, or flags.
Basic String Commands (CRUD)
Let’s explore the fundamental commands to interact with string keys.
1. SET key value (Create/Update)
Sets the value associated with key. If key already exists, its value is overwritten.
Node.js Example:
// redis-strings.js
const Redis = require('ioredis');
const redis = new Redis();
async function setStringExample() {
try {
// Set a new key-value pair
let response = await redis.set('username:1', 'Alice');
console.log(`SET username:1 -> ${response}`); // Output: OK
// Overwrite an existing key
response = await redis.set('username:1', 'Alice Smith');
console.log(`SET username:1 (overwrite) -> ${response}`); // Output: OK
// Set a key that holds an integer (Redis treats it as a string)
response = await redis.set('user:visits:1', '100');
console.log(`SET user:visits:1 -> ${response}`); // Output: OK
} catch (err) {
console.error('Error in setStringExample:', err);
}
}
// setStringExample().then(() => redis.quit());
Python Example:
# redis_strings.py
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
def set_string_example():
try:
# Set a new key-value pair
response = r.set('username:2', 'Bob')
print(f"SET username:2 -> {response}") # Output: True (or 1)
# Overwrite an existing key
response = r.set('username:2', 'Bob Johnson')
print(f"SET username:2 (overwrite) -> {response}") # Output: True (or 1)
# Set a key that holds an integer (Redis treats it as a string)
response = r.set('user:visits:2', '250')
print(f"SET user:visits:2 -> {response}") # Output: True (or 1)
except Exception as e:
print(f"Error in set_string_example: {e}")
# set_string_example()
# r.close()
2. GET key (Read)
Retrieves the value associated with key. If key does not exist, it returns null (Node.js) or None (Python).
Node.js Example:
// ... (previous setup)
async function getStringExample() {
try {
// Retrieve an existing key
let value = await redis.get('username:1');
console.log(`GET username:1 -> ${value}`); // Output: Alice Smith
// Retrieve a non-existent key
value = await redis.get('nonexistentkey');
console.log(`GET nonexistentkey -> ${value}`); // Output: null
// Retrieve a key storing a number
value = await redis.get('user:visits:1');
console.log(`GET user:visits:1 -> ${value}`); // Output: 100
} catch (err) {
console.error('Error in getStringExample:', err);
}
}
// getStringExample().then(() => redis.quit());
Python Example:
# ... (previous setup)
def get_string_example():
try:
# Retrieve an existing key
value = r.get('username:2')
print(f"GET username:2 -> {value.decode('utf-8') if value else None}") # Output: Bob Johnson
# Retrieve a non-existent key
value = r.get('nonexistentkey')
print(f"GET nonexistentkey -> {value.decode('utf-8') if value else None}") # Output: None
# Retrieve a key storing a number
value = r.get('user:visits:2')
print(f"GET user:visits:2 -> {value.decode('utf-8') if value else None}") # Output: 250
except Exception as e:
print(f"Error in get_string_example: {e}")
# get_string_example()
# r.close()
3. DEL key [key ...] (Delete)
Removes the specified keys. Returns the number of keys that were removed.
Node.js Example:
// ... (previous setup)
async function deleteStringExample() {
try {
await redis.set('tempkey1', 'value1');
await redis.set('tempkey2', 'value2');
let deletedCount = await redis.del('tempkey1', 'tempkey2', 'nonexistent');
console.log(`DEL tempkey1 tempkey2 nonexistent -> ${deletedCount}`); // Output: 2
let value = await redis.get('tempkey1');
console.log(`GET tempkey1 after deletion -> ${value}`); // Output: null
} catch (err) {
console.error('Error in deleteStringExample:', err);
}
}
// deleteStringExample().then(() => redis.quit());
Python Example:
# ... (previous setup)
def delete_string_example():
try:
r.set('tempkey3', 'value3')
r.set('tempkey4', 'value4')
deleted_count = r.delete('tempkey3', 'tempkey4', 'nonexistent')
print(f"DEL tempkey3 tempkey4 nonexistent -> {deleted_count}") # Output: 2
value = r.get('tempkey3')
print(f"GET tempkey3 after deletion -> {value.decode('utf-8') if value else None}") # Output: None
except Exception as e:
print(f"Error in delete_string_example: {e}")
# delete_string_example()
# r.close()
Advanced String Operations
Redis offers specialized commands for numerical string values and appending.
1. INCR key, DECR key, INCRBY key increment, DECRBY key decrement (Atomic Counters)
Atomically increments/decrements the number stored at key. If the key does not exist, it’s initialized to 0 before the operation.
Node.js Example:
// ... (previous setup)
async function counterExample() {
try {
// Initialize a counter
await redis.set('page:views', '0');
// Increment
let views = await redis.incr('page:views');
console.log(`INCR page:views -> ${views}`); // Output: 1
views = await redis.incr('page:views');
console.log(`INCR page:views -> ${views}`); // Output: 2
// Increment by a specific amount
views = await redis.incrby('page:views', 5);
console.log(`INCRBY page:views 5 -> ${views}`); // Output: 7
// Decrement
views = await redis.decr('page:views');
console.log(`DECR page:views -> ${views}`); // Output: 6
// Decrement by a specific amount
views = await redis.decrby('page:views', 3);
console.log(`DECRBY page:views 3 -> ${views}`); // Output: 3
// INCR on a non-existent key
views = await redis.incr('unique:ids');
console.log(`INCR unique:ids (new key) -> ${views}`); // Output: 1
} catch (err) {
console.error('Error in counterExample:', err);
} finally {
await redis.del('page:views', 'unique:ids');
}
}
// counterExample().then(() => redis.quit());
Python Example:
# ... (previous setup)
def counter_example():
try:
r.set('page:likes', '0')
# Increment
likes = r.incr('page:likes')
print(f"INCR page:likes -> {likes}") # Output: 1
likes = r.incr('page:likes')
print(f"INCR page:likes -> {likes}") # Output: 2
# Increment by a specific amount
likes = r.incrby('page:likes', 10)
print(f"INCRBY page:likes 10 -> {likes}") # Output: 12
# Decrement
likes = r.decr('page:likes')
print(f"DECR page:likes -> {likes}") # Output: 11
# Decrement by a specific amount
likes = r.decrby('page:likes', 5)
print(f"DECRBY page:likes 5 -> {likes}") # Output: 6
# INCR on a non-existent key
users = r.incr('total:users')
print(f"INCR total:users (new key) -> {users}") # Output: 1
except Exception as e:
print(f"Error in counter_example: {e}")
finally:
r.delete('page:likes', 'total:users')
# counter_example()
# r.close()
2. APPEND key value
Appends value to the value of key. If key does not exist, it is created with an empty string, then value is appended. Returns the new length of the string.
Node.js Example:
// ... (previous setup)
async function appendExample() {
try {
await redis.set('greeting', 'Hello');
let newLength = await redis.append('greeting', ' World');
console.log(`APPEND greeting " World" -> new length: ${newLength}`); // Output: new length: 11
console.log(`GET greeting -> ${await redis.get('greeting')}`); // Output: Hello World
newLength = await redis.append('new_message', 'First part.');
console.log(`APPEND new_message "First part." -> new length: ${newLength}`); // Output: new length: 11
newLength = await redis.append('new_message', ' Second part.');
console.log(`APPEND new_message " Second part." -> new length: ${newLength}`); // Output: new length: 24
console.log(`GET new_message -> ${await redis.get('new_message')}`); // Output: First part. Second part.
} catch (err) {
console.error('Error in appendExample:', err);
} finally {
await redis.del('greeting', 'new_message');
}
}
// appendExample().then(() => redis.quit());
Python Example:
# ... (previous setup)
def append_example():
try:
r.set('message', 'Good')
new_length = r.append('message', ' Morning')
print(f"APPEND message ' Morning' -> new length: {new_length}") # Output: new length: 11
print(f"GET message -> {r.get('message').decode('utf-8')}") # Output: Good Morning
new_length = r.append('log_entry', 'Event 1 recorded.')
print(f"APPEND log_entry 'Event 1 recorded.' -> new length: {new_length}") # Output: new length: 17
new_length = r.append('log_entry', ' Event 2 followed.')
print(f"APPEND log_entry ' Event 2 followed.' -> new length: {new_length}") # Output: new length: 35
print(f"GET log_entry -> {r.get('log_entry').decode('utf-8')}") # Output: Event 1 recorded. Event 2 followed.
except Exception as e:
print(f"Error in append_example: {e}")
finally:
r.delete('message', 'log_entry')
# append_example()
# r.close()
Key Management
Beyond simple CRUD, Redis offers commands to manage keys themselves.
1. EXISTS key [key ...]
Checks if one or more keys exist. Returns the number of keys that exist.
Node.js Example:
// ... (previous setup)
async function existsExample() {
try {
await redis.set('product:101', 'laptop');
let count = await redis.exists('product:101', 'product:102');
console.log(`EXISTS product:101 product:102 -> ${count}`); // Output: 1 (only product:101 exists)
} catch (err) {
console.error('Error in existsExample:', err);
} finally {
await redis.del('product:101');
}
}
// existsExample().then(() => redis.quit());
2. RENAME oldkey newkey
Renames oldkey to newkey. If oldkey does not exist, an error is returned. If newkey already exists, it is overwritten.
Node.js Example:
// ... (previous setup)
async function renameExample() {
try {
await redis.set('old_name', 'some data');
let response = await redis.rename('old_name', 'new_name');
console.log(`RENAME old_name new_name -> ${response}`); // Output: OK
console.log(`GET old_name -> ${await redis.get('old_name')}`); // Output: null
console.log(`GET new_name -> ${await redis.get('new_name')}`); // Output: some data
} catch (err) {
console.error('Error in renameExample:', err);
} finally {
await redis.del('new_name');
}
}
// renameExample().then(() => redis.quit());
3. TYPE key
Returns the data type of the value stored at key.
Node.js Example:
// ... (previous setup)
async function typeExample() {
try {
await redis.set('my_string', 'hello');
let type = await redis.type('my_string');
console.log(`TYPE my_string -> ${type}`); // Output: string
let nonExistentType = await redis.type('non_existent_key');
console.log(`TYPE non_existent_key -> ${nonExistentType}`); // Output: none
} catch (err) {
console.error('Error in typeExample:', err);
} finally {
await redis.del('my_string');
}
}
// typeExample().then(() => redis.quit());
Key Expiration (TTL - Time To Live)
One of Redis’s most powerful features for caching and temporary data is key expiration. You can set a Time To Live (TTL) for a key, after which Redis will automatically delete it.
1. EXPIRE key seconds
Sets a timeout on key. After seconds, the key will be automatically deleted.
2. PEXPIRE key milliseconds
Sets a timeout on key in milliseconds.
3. TTL key
Returns the remaining time to live of a key in seconds. Returns -2 if the key does not exist, and -1 if the key exists but has no associated expire.
4. PTTL key
Returns the remaining time to live of a key in milliseconds.
5. SETEX key seconds value (Set with Expiration)
A convenience command to set a key’s value and its expiration time in one atomic operation.
Node.js Example:
// ... (previous setup)
async function expirationExample() {
try {
// Using SETEX
await redis.setex('ephemeral_data', 5, 'this will disappear'); // Expires in 5 seconds
console.log(`SETEX ephemeral_data (5s TTL) -> Value: ${await redis.get('ephemeral_data')}`);
let ttl = await redis.ttl('ephemeral_data');
console.log(`TTL ephemeral_data -> ${ttl} seconds remaining`);
// Wait a bit
await new Promise(resolve => setTimeout(resolve, 3000));
ttl = await redis.ttl('ephemeral_data');
console.log(`TTL ephemeral_data after 3 seconds -> ${ttl} seconds remaining`);
// Wait for expiration
await new Promise(resolve => setTimeout(resolve, 3000)); // Total 6 seconds
console.log(`GET ephemeral_data after expiration -> ${await redis.get('ephemeral_data')}`); // Output: null
// Setting explicit expire
await redis.set('another_temp_key', 'some temporary value');
await redis.expire('another_temp_key', 10); // Expires in 10 seconds
console.log(`SET and EXPIRE another_temp_key -> TTL: ${await redis.ttl('another_temp_key')}`);
} catch (err) {
console.error('Error in expirationExample:', err);
} finally {
await redis.del('ephemeral_data', 'another_temp_key'); // Just in case
}
}
// expirationExample().then(() => redis.quit());
Python Example:
# ... (previous setup)
import time
def expiration_example():
try:
# Using SETEX
r.setex('session:user:123', 5, 'logged_in') # Expires in 5 seconds
print(f"SETEX session:user:123 (5s TTL) -> Value: {r.get('session:user:123').decode('utf-8')}")
ttl = r.ttl('session:user:123')
print(f"TTL session:user:123 -> {ttl} seconds remaining")
# Wait a bit
time.sleep(3)
ttl = r.ttl('session:user:123')
print(f"TTL session:user:123 after 3 seconds -> {ttl} seconds remaining")
# Wait for expiration
time.sleep(3) # Total 6 seconds
print(f"GET session:user:123 after expiration -> {r.get('session:user:123')}") # Output: None
# Setting explicit expire
r.set('cache:item:456', 'cached content')
r.expire('cache:item:456', 10) # Expires in 10 seconds
print(f"SET and EXPIRE cache:item:456 -> TTL: {r.ttl('cache:item:456')}")
except Exception as e:
print(f"Error in expiration_example: {e}")
finally:
r.delete('session:user:123', 'cache:item:456') # Just in case
# expiration_example()
# r.close()
Combining Everything
Let’s put some of these concepts together in a single Node.js script.
// full_string_operations.js
const Redis = require('ioredis');
const redis = new Redis(); // Connects to localhost:6379
async function runAllStringExamples() {
console.log('--- Running String Examples ---');
// SET, GET, EXISTS
await redis.set('app:version', '1.0.0');
console.log(`Current App Version: ${await redis.get('app:version')}`);
console.log(`Does 'app:version' exist? ${await redis.exists('app:version') ? 'Yes' : 'No'}`);
// INCR/DECR - User daily hits counter
const userId = 'user:123';
const dailyHitsKey = `daily:hits:${userId}`;
await redis.set(dailyHitsKey, '0'); // Initialize
await redis.expire(dailyHitsKey, 86400); // Expires in 24 hours
console.log(`\nUser ${userId} hits: ${await redis.get(dailyHitsKey)}`);
await redis.incr(dailyHitsKey);
await redis.incr(dailyHitsKey);
console.log(`User ${userId} hits after INCR: ${await redis.get(dailyHitsKey)}`);
await redis.incrby(dailyHitsKey, 10);
console.log(`User ${userId} hits after INCRBY 10: ${await redis.get(dailyHitsKey)}`);
console.log(`Time left for daily hits counter: ${await redis.ttl(dailyHitsKey)}s`);
// APPEND - Log aggregation
const logKey = 'application:logs';
await redis.set(logKey, 'Startup complete. ');
await redis.append(logKey, 'Processing user requests. ');
await redis.append(logKey, 'Database connection established.');
console.log(`\nApplication Log: ${await redis.get(logKey)}`);
// RENAME and DEL
await redis.set('oldKey', 'some sensitive data');
console.log(`\nBefore rename: GET oldKey -> ${await redis.get('oldKey')}`);
await redis.rename('oldKey', 'newKey');
console.log(`After rename: GET oldKey -> ${await redis.get('oldKey')}`);
console.log(`After rename: GET newKey -> ${await redis.get('newKey')}`);
await redis.del('newKey');
console.log(`After DEL newKey: GET newKey -> ${await redis.get('newKey')}`);
console.log('--- String Examples Complete ---');
await redis.quit();
}
// Call the main function to execute all examples
runAllStringExamples();
Exercises / Mini-Challenges
User Last Seen:
- Create a Redis key for a user (e.g.,
user:status:456) and set its value to the current timestamp (you can useDate.now()in Node.js ortime.time()in Python for milliseconds since epoch). - Set this key to expire after 60 seconds.
- Retrieve the value and print the remaining TTL.
- Wait for 30 seconds, then retrieve the TTL again.
- Challenge: Modify the logic so that every time a user is “seen,” their timestamp is updated, and their expiration is reset to 60 seconds. (Hint: look into
SET key value EXorEXPIREafterSET).
- Create a Redis key for a user (e.g.,
Website Visitor Counter with Daily Reset:
- Implement a global visitor counter for a website (
website:visitors). - Every time a “visitor” arrives, increment this counter.
- The counter should automatically reset to 0 every 24 hours.
- Challenge: How would you ensure the counter starts at 0 if it doesn’t exist, and still sets the expiration? (Hint:
SETNXorSET key value EX NXmight be useful, or a combination ofSETandEXPIRE).
- Implement a global visitor counter for a website (
Basic Message Logging:
- Create a key
app:event_log. - Whenever an “event” occurs (e.g., “User logged in”, “Item added to cart”), append a timestamped message to this key.
- Retrieve the full log and print it.
- Challenge: How would you ensure the log doesn’t grow indefinitely, but only keeps the last 1000 characters for example? (Hint: Redis strings don’t have a direct “trim” for middle, but you could
GETSETand truncate client-side, or think about a different data type for logs if this becomes too complex for strings - a foreshadowing!).
- Create a key
These exercises will help you solidify your understanding of Redis Strings and key management, which are foundational for more complex data types. In the next chapter, we’ll move on to Hashes, a data type perfect for representing objects.