Node.js
Comprehensive guide for using Redis with Node.js in Sealos - from basics to production deployment
This comprehensive guide covers everything you need to know about using Redis with Node.js in Sealos DevBox, from basic operations to production-ready implementations.
Prerequisites
- A Sealos DevBox project with Node.js environment
- A Redis database created using the Database app in Sealos
- Basic understanding of JavaScript and Node.js
Project Setup & Structure
Let's start by setting up a complete project structure that we'll build upon throughout this guide.
Initialize Your Project
In your Cursor terminal, install the necessary packages:
npm install redis dotenv @redis/json @redis/search @redis/time-series msgpack5Project Structure
Create the following directory structure:
# Create directories
mkdir src examples tests
# Create main files
touch .env package.json
touch src/redisClient.js
touch examples/basic-operations.js
touch tests/connection-test.jsYour project should look like this:
Environment Configuration
Set up environment variables
Create your .env file with your Sealos Redis credentials:
# Basic Redis connection
REDIS_HOST=your_redis_host
REDIS_PORT=6379
REDIS_PASSWORD=your_redis_password
REDIS_DB=0
# Advanced settings (we'll use these later)
REDIS_TLS=false
REDIS_USERNAME=default
REDIS_CONNECT_TIMEOUT=10000
REDIS_COMMAND_TIMEOUT=5000Create the main Redis client
Create src/redisClient.js - this will be our single source of truth for Redis connections:
const { createClient } = require('redis');
require('dotenv').config();
class RedisManager {
constructor() {
this.client = null;
this.isConnected = false;
}
createClient() {
const config = {
url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`,
password: process.env.REDIS_PASSWORD,
database: parseInt(process.env.REDIS_DB) || 0,
socket: {
connectTimeout: parseInt(process.env.REDIS_CONNECT_TIMEOUT) || 60000,
commandTimeout: parseInt(process.env.REDIS_COMMAND_TIMEOUT) || 5000,
reconnectStrategy: (retries) => {
if (retries > 20) {
console.error('Too many reconnection attempts, giving up');
return false;
}
// Exponential backoff with jitter (latest best practice)
const jitter = Math.floor(Math.random() * 200);
const delay = Math.min(Math.pow(2, retries) * 50, 2000);
console.log(`Reconnecting in ${delay + jitter}ms...`);
return delay + jitter;
},
keepAlive: 30000,
noDelay: true
}
};
// Add TLS if enabled
if (process.env.REDIS_TLS === 'true') {
config.socket.tls = true;
config.socket.rejectUnauthorized = process.env.NODE_ENV === 'production';
}
this.client = createClient(config);
this.setupEventHandlers();
return this.client;
}
setupEventHandlers() {
this.client.on('connect', () => {
console.log('โ
Redis client connected');
});
this.client.on('ready', () => {
console.log('โ
Redis client ready');
this.isConnected = true;
});
this.client.on('error', (err) => {
console.error('โ Redis client error:', err.message);
this.isConnected = false;
});
this.client.on('end', () => {
console.log('๐ Redis client disconnected');
this.isConnected = false;
});
this.client.on('reconnecting', () => {
console.log('๐ Redis client reconnecting...');
this.isConnected = false;
});
}
async connect() {
if (!this.client) {
this.createClient();
}
if (!this.isConnected) {
await this.client.connect();
}
return this.client;
}
async disconnect() {
if (this.client && this.isConnected) {
// Use destroy() instead of quit() for latest node-redis
this.client.destroy();
}
}
getClient() {
if (!this.isConnected) {
throw new Error('Redis client not connected. Call connect() first.');
}
return this.client;
}
async ping() {
try {
const result = await this.client.ping();
return result === 'PONG';
} catch (error) {
console.error('Redis ping failed:', error);
return false;
}
}
}
// Export singleton instance
const redisManager = new RedisManager();
module.exports = redisManager;Test your connection
Create tests/connection-test.js:
const redisManager = require('../src/redisClient');
async function testConnection() {
try {
console.log('๐ Testing Redis connection...');
// Connect to Redis
await redisManager.connect();
// Test ping
const pingResult = await redisManager.ping();
console.log('๐ก Ping result:', pingResult ? 'SUCCESS' : 'FAILED');
// Test basic operations
const client = redisManager.getClient();
await client.set('test:connection', 'Hello from Sealos!');
const value = await client.get('test:connection');
console.log('๐พ Test value:', value);
// Clean up test key
await client.del('test:connection');
console.log('โ
Connection test completed successfully!');
} catch (error) {
console.error('โ Connection test failed:', error.message);
} finally {
await redisManager.disconnect();
}
}
// Run the test
testConnection();Run your first test
node tests/connection-test.jsYou should see output like:
๐ Testing Redis connection...
โ
Redis client connected
โ
Redis client ready
๐ก Ping result: SUCCESS
๐พ Test value: Hello from Sealos!
โ
Connection test completed successfully!
๐ Redis client disconnectedBasic Operations & Data Types
Now let's explore Redis data types with practical examples. Create examples/basic-operations.js:
const redisManager = require('../src/redisClient');
async function demonstrateBasicOperations() {
try {
await redisManager.connect();
const client = redisManager.getClient();
console.log('๐ Starting basic operations demo...\n');
// 1. Strings
console.log('๐ STRING OPERATIONS:');
await client.set('user:name', 'John Doe');
await client.setEx('session:abc123', 3600, 'active'); // With TTL
const name = await client.get('user:name');
const session = await client.get('session:abc123');
console.log(`Name: ${name}`);
console.log(`Session: ${session}`);
// Increment counter
await client.incr('page:views');
await client.incrBy('page:views', 5);
const views = await client.get('page:views');
console.log(`Page views: ${views}\n`);
// 2. Hashes
console.log('๐๏ธ HASH OPERATIONS:');
await client.hSet('user:1001', {
name: 'Alice Smith',
email: 'alice@example.com',
age: '28',
city: 'New York'
});
const user = await client.hGetAll('user:1001');
const userAge = await client.hGet('user:1001', 'age');
console.log('User data:', user);
console.log(`User age: ${userAge}\n`);
// 3. Lists
console.log('๐ LIST OPERATIONS:');
await client.lPush('tasks', 'Task 1', 'Task 2', 'Task 3');
await client.rPush('tasks', 'Task 4'); // Add to end
const tasks = await client.lRange('tasks', 0, -1);
const firstTask = await client.lPop('tasks');
console.log('All tasks:', tasks);
console.log(`Completed task: ${firstTask}\n`);
// 4. Sets
console.log('๐ฏ SET OPERATIONS:');
await client.sAdd('tags:user:1001', 'developer', 'nodejs', 'redis');
await client.sAdd('tags:user:1002', 'designer', 'css', 'nodejs');
const userTags = await client.sMembers('tags:user:1001');
const commonTags = await client.sInter('tags:user:1001', 'tags:user:1002');
console.log('User 1001 tags:', userTags);
console.log('Common tags:', commonTags);
console.log('\nโ
Basic operations demo completed!');
} catch (error) {
console.error('โ Error in basic operations:', error.message);
} finally {
await redisManager.disconnect();
}
}
// Run the demo
demonstrateBasicOperations();Test Basic Operations
node examples/basic-operations.jsExpected output:
๐ Starting basic operations demo...
๐ STRING OPERATIONS:
Name: John Doe
Session: active
Page views: 6
๐๏ธ HASH OPERATIONS:
User data: { name: 'Alice Smith', email: 'alice@example.com', age: '28', city: 'New York' }
User age: 28
๐ LIST OPERATIONS:
All tasks: [ 'Task 3', 'Task 2', 'Task 1', 'Task 4' ]
Completed task: Task 3
๐ฏ SET OPERATIONS:
User 1001 tags: [ 'nodejs', 'redis', 'developer' ]
Common tags: [ 'nodejs' ]
โ
Basic operations demo completed!Advanced Data Structures & Real-World Use Cases
Now let's build practical applications using Redis's advanced data structures. We'll create several examples that demonstrate real-world scenarios.
Working with Sorted Sets - Leaderboard System
Create examples/leaderboard.js:
const redisManager = require('../src/redisClient');
class Leaderboard {
constructor(name) {
this.leaderboardKey = `leaderboard:${name}`;
}
async updateScore(userId, score, userData = {}) {
const client = redisManager.getClient();
// Update score in sorted set
await client.zAdd(this.leaderboardKey, { score, value: userId });
// Store additional user data if provided
if (Object.keys(userData).length > 0) {
await client.hSet(`user:${userId}`, userData);
}
console.log(`โ
Updated score for user ${userId}: ${score}`);
}
async getTopPlayers(count = 10) {
const client = redisManager.getClient();
const players = await client.zRevRange(this.leaderboardKey, 0, count - 1, {
withScores: true
});
// Enrich with user data
const enrichedPlayers = [];
for (let i = 0; i < players.length; i++) {
const { value: userId, score } = players[i];
const userData = await client.hGetAll(`user:${userId}`);
enrichedPlayers.push({
rank: i + 1,
userId,
score,
name: userData.name || `User ${userId}`,
...userData
});
}
return enrichedPlayers;
}
async getUserRank(userId) {
const client = redisManager.getClient();
const rank = await client.zRevRank(this.leaderboardKey, userId);
const score = await client.zScore(this.leaderboardKey, userId);
return rank !== null ? { rank: rank + 1, score } : null;
}
}
async function demonstrateLeaderboard() {
try {
await redisManager.connect();
console.log('๐ Starting leaderboard demo...\n');
const gameLeaderboard = new Leaderboard('game_scores');
// Add some players with scores
await gameLeaderboard.updateScore('player1', 1500, { name: 'Alice', level: 25 });
await gameLeaderboard.updateScore('player2', 2300, { name: 'Bob', level: 32 });
await gameLeaderboard.updateScore('player3', 1800, { name: 'Charlie', level: 28 });
await gameLeaderboard.updateScore('player4', 2100, { name: 'Diana', level: 30 });
await gameLeaderboard.updateScore('player5', 1200, { name: 'Eve', level: 22 });
console.log('\n๐ฅ Top 3 Players:');
const topPlayers = await gameLeaderboard.getTopPlayers(3);
topPlayers.forEach(player => {
console.log(`${player.rank}. ${player.name} - Score: ${player.score} (Level ${player.level})`);
});
// Check specific player rank
const aliceRank = await gameLeaderboard.getUserRank('player1');
console.log(`\n๐ Alice's rank: #${aliceRank.rank} with score ${aliceRank.score}`);
console.log('\nโ
Leaderboard demo completed!');
} catch (error) {
console.error('โ Error in leaderboard demo:', error.message);
} finally {
await redisManager.disconnect();
}
}
// Export for use in other examples
module.exports = Leaderboard;
// Run demo if called directly
if (require.main === module) {
demonstrateLeaderboard();
}Working with Lists - Task Queue System
Create examples/task-queue.js:
const redisManager = require('../src/redisClient');
class TaskQueue {
constructor(queueName = 'default_queue') {
this.queueName = queueName;
this.processingQueue = `${queueName}:processing`;
}
async addTask(taskData, priority = 'normal') {
const client = redisManager.getClient();
const task = {
id: `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
data: taskData,
createdAt: new Date().toISOString(),
priority,
attempts: 0
};
const queueKey = priority === 'high' ? `${this.queueName}:high` : this.queueName;
await client.lPush(queueKey, JSON.stringify(task));
console.log(`๐ Added ${priority} priority task: ${task.id}`);
return task.id;
}
async processTask(timeout = 5) {
const client = redisManager.getClient();
// Check high priority queue first
let result = await client.brPop(`${this.queueName}:high`, 0.1);
if (!result) {
// Then check normal priority queue
result = await client.brPop(this.queueName, timeout);
}
if (result) {
const task = JSON.parse(result.element);
// Move to processing queue for reliability
await client.lPush(this.processingQueue, JSON.stringify(task));
console.log(`โก Processing task: ${task.id}`);
return task;
}
return null;
}
async completeTask(taskId) {
const client = redisManager.getClient();
// Remove from processing queue
const processingTasks = await client.lRange(this.processingQueue, 0, -1);
for (let i = 0; i < processingTasks.length; i++) {
const task = JSON.parse(processingTasks[i]);
if (task.id === taskId) {
await client.lRem(this.processingQueue, 1, processingTasks[i]);
console.log(`โ
Completed task: ${taskId}`);
break;
}
}
}
async getQueueStats() {
const client = redisManager.getClient();
return {
pending: await client.lLen(this.queueName),
highPriority: await client.lLen(`${this.queueName}:high`),
processing: await client.lLen(this.processingQueue)
};
}
}
async function demonstrateTaskQueue() {
try {
await redisManager.connect();
console.log('๐ Starting task queue demo...\n');
const emailQueue = new TaskQueue('email_queue');
// Add various tasks
await emailQueue.addTask({
type: 'welcome_email',
recipient: 'user@example.com',
template: 'welcome'
});
await emailQueue.addTask({
type: 'password_reset',
recipient: 'admin@example.com',
token: 'abc123'
}, 'high');
await emailQueue.addTask({
type: 'newsletter',
recipients: ['user1@example.com', 'user2@example.com']
});
// Check queue stats
let stats = await emailQueue.getQueueStats();
console.log('๐ Queue stats:', stats);
// Process some tasks
console.log('\n๐ Processing tasks...');
for (let i = 0; i < 3; i++) {
const task = await emailQueue.processTask();
if (task) {
// Simulate task processing
await new Promise(resolve => setTimeout(resolve, 1000));
await emailQueue.completeTask(task.id);
}
}
// Final stats
stats = await emailQueue.getQueueStats();
console.log('\n๐ Final queue stats:', stats);
console.log('\nโ
Task queue demo completed!');
} catch (error) {
console.error('โ Error in task queue demo:', error.message);
} finally {
await redisManager.disconnect();
}
}
// Export for use in other examples
module.exports = TaskQueue;
// Run demo if called directly
if (require.main === module) {
demonstrateTaskQueue();
}Test Advanced Data Structures
Run the leaderboard example:
node examples/leaderboard.jsRun the task queue example:
node examples/task-queue.jsExpected output for leaderboard:
๐ Starting leaderboard demo...
โ
Updated score for user player1: 1500
โ
Updated score for user player2: 2300
โ
Updated score for user player3: 1800
โ
Updated score for user player4: 2100
โ
Updated score for user player5: 1200
๐ฅ Top 3 Players:
1. Bob - Score: 2300 (Level 32)
2. Diana - Score: 2100 (Level 30)
3. Charlie - Score: 1800 (Level 28)
๐ Alice's rank: #4 with score 1500
โ
Leaderboard demo completed!Advanced Caching Strategies
Let's implement different caching patterns that you can use in production applications.
Cache-Aside Pattern (Lazy Loading)
Create examples/cache-patterns.js:
const redisManager = require('../src/redisClient');
class CacheAsideService {
constructor(ttl = 3600) {
this.ttl = ttl; // Time to live in seconds
}
async get(key, fetchFunction) {
try {
const client = redisManager.getClient();
// Try to get from cache first
const cached = await client.get(key);
if (cached) {
console.log(`๐ฏ Cache HIT for key: ${key}`);
return JSON.parse(cached);
}
console.log(`โ Cache MISS for key: ${key}`);
// Cache miss - fetch from source
const data = await fetchFunction();
// Store in cache with TTL
await client.setEx(key, this.ttl, JSON.stringify(data));
console.log(`๐พ Cached data for key: ${key}`);
return data;
} catch (error) {
console.error('Cache-aside error:', error);
// Fallback to direct fetch if cache fails
return await fetchFunction();
}
}
async invalidate(key) {
const client = redisManager.getClient();
await client.del(key);
console.log(`๐๏ธ Invalidated cache for key: ${key}`);
}
async invalidatePattern(pattern) {
const client = redisManager.getClient();
const keys = await client.keys(pattern);
if (keys.length > 0) {
await client.del(keys);
console.log(`๐๏ธ Invalidated ${keys.length} keys matching pattern: ${pattern}`);
}
}
}
// Simulate database operations
const mockDatabase = {
async getUserById(userId) {
console.log(`๐ Fetching user ${userId} from database...`);
// Simulate database delay
await new Promise(resolve => setTimeout(resolve, 500));
return {
id: userId,
name: `User ${userId}`,
email: `user${userId}@example.com`,
createdAt: new Date().toISOString()
};
},
async getProductById(productId) {
console.log(`๐ Fetching product ${productId} from database...`);
await new Promise(resolve => setTimeout(resolve, 300));
return {
id: productId,
name: `Product ${productId}`,
price: Math.floor(Math.random() * 1000) + 10,
category: 'Electronics'
};
}
};
async function demonstrateCacheAside() {
try {
await redisManager.connect();
console.log('๐ Starting Cache-Aside pattern demo...\n');
const cache = new CacheAsideService(1800); // 30 minutes TTL
// Function to get user with caching
async function getUserProfile(userId) {
return await cache.get(`user:${userId}`, async () => {
return await mockDatabase.getUserById(userId);
});
}
// First call - cache miss
console.log('๐ First call to getUserProfile(1001):');
let user = await getUserProfile('1001');
console.log('๐ค User data:', user);
console.log('\n๐ Second call to getUserProfile(1001):');
// Second call - cache hit
user = await getUserProfile('1001');
console.log('๐ค User data:', user);
// Invalidate and try again
console.log('\n๐๏ธ Invalidating user cache...');
await cache.invalidate('user:1001');
console.log('\n๐ Third call after invalidation:');
user = await getUserProfile('1001');
console.log('๐ค User data:', user);
console.log('\nโ
Cache-Aside demo completed!');
} catch (error) {
console.error('โ Error in cache-aside demo:', error.message);
} finally {
await redisManager.disconnect();
}
}
// Export for use in other examples
module.exports = { CacheAsideService, mockDatabase };
// Run demo if called directly
if (require.main === module) {
demonstrateCacheAside();
}Write-Through Pattern
Add this to examples/cache-patterns.js (append to the file):
class WriteThroughCache {
constructor(ttl = 3600) {
this.ttl = ttl;
}
async set(key, data, saveFunction) {
try {
const client = redisManager.getClient();
// Write to database first
await saveFunction(data);
console.log(`๐พ Saved data to database for key: ${key}`);
// Then update cache
await client.setEx(key, this.ttl, JSON.stringify(data));
console.log(`๐ฏ Updated cache for key: ${key}`);
return data;
} catch (error) {
console.error('Write-through error:', error);
throw error; // Re-throw to maintain transaction integrity
}
}
async get(key) {
const client = redisManager.getClient();
const cached = await client.get(key);
return cached ? JSON.parse(cached) : null;
}
async update(key, updates, updateFunction) {
try {
const client = redisManager.getClient();
// Update database first
const updatedData = await updateFunction(updates);
console.log(`๐ Updated database for key: ${key}`);
// Update cache
await client.setEx(key, this.ttl, JSON.stringify(updatedData));
console.log(`๐ฏ Updated cache for key: ${key}`);
return updatedData;
} catch (error) {
console.error('Write-through update error:', error);
throw error;
}
}
}
async function demonstrateWriteThrough() {
try {
await redisManager.connect();
console.log('๐ Starting Write-Through pattern demo...\n');
const writeCache = new WriteThroughCache(3600);
// Create new user
const newUser = {
id: '2001',
name: 'John Smith',
email: 'john.smith@example.com',
role: 'admin'
};
console.log('๐ Creating new user with write-through:');
await writeCache.set('user:2001', newUser, async (userData) => {
// Simulate database save
console.log('๐พ Saving to database:', userData.name);
await new Promise(resolve => setTimeout(resolve, 200));
});
// Read from cache
console.log('\n๐ Reading user from cache:');
const cachedUser = await writeCache.get('user:2001');
console.log('๐ค Cached user:', cachedUser);
// Update user
console.log('\n๐ Updating user with write-through:');
await writeCache.update('user:2001', { role: 'super_admin' }, async (updates) => {
// Simulate database update
console.log('๐ Updating database with:', updates);
await new Promise(resolve => setTimeout(resolve, 200));
return { ...cachedUser, ...updates };
});
// Read updated data
const updatedUser = await writeCache.get('user:2001');
console.log('๐ค Updated user:', updatedUser);
console.log('\nโ
Write-Through demo completed!');
} catch (error) {
console.error('โ Error in write-through demo:', error.message);
} finally {
await redisManager.disconnect();
}
}
// Add to exports
module.exports = { CacheAsideService, WriteThroughCache, mockDatabase };Test Caching Patterns
node examples/cache-patterns.jsExpected output:
๐ Starting Cache-Aside pattern demo...
๐ First call to getUserProfile(1001):
โ Cache MISS for key: user:1001
๐ Fetching user 1001 from database...
๐พ Cached data for key: user:1001
๐ค User data: { id: '1001', name: 'User 1001', email: 'user1001@example.com', createdAt: '...' }
๐ Second call to getUserProfile(1001):
๐ฏ Cache HIT for key: user:1001
๐ค User data: { id: '1001', name: 'User 1001', email: 'user1001@example.com', createdAt: '...' }
๐๏ธ Invalidating user cache...
๐๏ธ Invalidated cache for key: user:1001
๐ Third call after invalidation:
โ Cache MISS for key: user:1001
๐ Fetching user 1001 from database...
๐พ Cached data for key: user:1001
โ
Cache-Aside demo completed!Redis Stack Integration
Redis Stack provides powerful modules for JSON, Search, and TimeSeries. Let's explore how to use them with our Node.js application.
Working with RedisJSON
Create examples/redis-json.js:
const redisManager = require('../src/redisClient');
class RedisJSONService {
async setDocument(key, document) {
const client = redisManager.getClient();
// Use the native JSON.SET command (latest node-redis supports JSON module)
await client.json.set(key, '$', document);
console.log(`๐ Stored JSON document: ${key}`);
}
async getDocument(key) {
const client = redisManager.getClient();
return await client.json.get(key);
}
async updateField(key, path, value) {
const client = redisManager.getClient();
await client.json.set(key, path, value);
console.log(`๐ Updated field ${path} in document: ${key}`);
}
async getField(key, path) {
const client = redisManager.getClient();
return await client.json.get(key, { path });
}
async appendToArray(key, path, ...values) {
const client = redisManager.getClient();
return await client.json.arrAppend(key, path, ...values);
}
async incrementNumber(key, path, increment = 1) {
const client = redisManager.getClient();
return await client.json.numIncrBy(key, path, increment);
}
}
async function demonstrateRedisJSON() {
try {
await redisManager.connect();
console.log('๐ Starting RedisJSON demo...\n');
const jsonService = new RedisJSONService();
// Store user profile as JSON document
const userProfile = {
id: 1001,
name: 'John Doe',
email: 'john@example.com',
preferences: {
theme: 'dark',
notifications: true,
language: 'en'
},
tags: ['premium', 'early-adopter'],
level: 25,
metadata: {
createdAt: new Date().toISOString(),
lastLogin: new Date().toISOString()
}
};
await jsonService.setDocument('user:1001', userProfile);
// Get entire document
console.log('๐ Retrieved user profile:');
const retrievedProfile = await jsonService.getDocument('user:1001');
console.log(JSON.stringify(retrievedProfile, null, 2));
// Update nested field
console.log('\n๐ Updating theme preference...');
await jsonService.updateField('user:1001', '$.preferences.theme', 'light');
// Get specific field
const theme = await jsonService.getField('user:1001', '$.preferences.theme');
console.log(`๐จ Current theme: ${theme}`);
// Add new tag to array
console.log('\n๐ท๏ธ Adding new tag...');
await jsonService.appendToArray('user:1001', '$.tags', 'beta-tester');
// Get updated tags
const tags = await jsonService.getField('user:1001', '$.tags');
console.log('๐ท๏ธ Updated tags:', tags);
// Increment a numeric field
console.log('\n๐ข Incrementing user level...');
await jsonService.incrementNumber('user:1001', '$.level', 1);
const level = await jsonService.getField('user:1001', '$.level');
console.log('๐ New level:', level);
console.log('\nโ
RedisJSON demo completed!');
} catch (error) {
console.error('โ Error in RedisJSON demo:', error.message);
} finally {
await redisManager.disconnect();
}
}
// Export for use in other examples
module.exports = RedisJSONService;
// Run demo if called directly
if (require.main === module) {
demonstrateRedisJSON();
}Working with RediSearch
Create examples/redis-search.js:
const redisManager = require('../src/redisClient');
const { SchemaFieldTypes } = require('@redis/search');
class RedisSearchService {
async createIndex(indexName, schema, options = {}) {
const client = redisManager.getClient();
try {
// Use the native ft.create method (latest node-redis supports Search module)
await client.ft.create(indexName, schema, {
ON: 'JSON',
PREFIX: 'product:',
...options
});
console.log(`๐ Created search index: ${indexName}`);
} catch (error) {
if (!error.message.includes('Index already exists')) {
throw error;
}
console.log(`๐ Index ${indexName} already exists`);
}
}
async indexDocument(key, document) {
const client = redisManager.getClient();
await client.json.set(key, '$', document);
console.log(`๐ Indexed document: ${key}`);
}
async search(indexName, query, options = {}) {
const client = redisManager.getClient();
return await client.ft.search(indexName, query, options);
}
async aggregate(indexName, query, options = {}) {
const client = redisManager.getClient();
return await client.ft.aggregate(indexName, query, options);
}
}
async function demonstrateRediSearch() {
try {
await redisManager.connect();
console.log('๐ Starting RediSearch demo...\n');
const searchService = new RedisSearchService();
// Create product search index using latest schema format
await searchService.createIndex('idx:products', {
'$.name': {
type: SchemaFieldTypes.TEXT,
AS: 'name',
SORTABLE: true
},
'$.description': {
type: SchemaFieldTypes.TEXT,
AS: 'description'
},
'$.price': {
type: SchemaFieldTypes.NUMERIC,
AS: 'price'
},
'$.category': {
type: SchemaFieldTypes.TAG,
AS: 'category'
},
'$.rating': {
type: SchemaFieldTypes.NUMERIC,
AS: 'rating'
}
});
// Index some products
const products = [
{
id: 1,
name: 'Wireless Headphones',
description: 'High-quality wireless headphones with noise cancellation',
price: 199.99,
category: 'electronics',
rating: 4.5
},
{
id: 2,
name: 'Smart Watch',
description: 'Feature-rich smartwatch with health monitoring',
price: 299.99,
category: 'electronics',
rating: 4.2
},
{
id: 3,
name: 'Coffee Maker',
description: 'Automatic coffee maker with programmable timer',
price: 89.99,
category: 'appliances',
rating: 4.0
}
];
for (const product of products) {
await searchService.indexDocument(`product:${product.id}`, product);
}
console.log('\n๐ Searching for "wireless":');
let results = await searchService.search('idx:products', 'wireless');
console.log(`Found ${results.total} results:`);
results.documents.forEach((doc) => {
console.log(`- ${doc.value.name}: $${doc.value.price}`);
});
console.log('\n๐ Searching electronics under $250:');
results = await searchService.search('idx:products', '@category:{electronics} @price:[0 250]');
console.log(`Found ${results.total} results:`);
results.documents.forEach((doc) => {
console.log(`- ${doc.value.name}: $${doc.value.price} (${doc.value.category})`);
});
console.log('\nโ
RediSearch demo completed!');
} catch (error) {
console.error('โ Error in RediSearch demo:', error.message);
} finally {
await redisManager.disconnect();
}
}
// Export for use in other examples
module.exports = RedisSearchService;
// Run demo if called directly
if (require.main === module) {
demonstrateRediSearch();
}Test Redis Stack Features
Run the RedisJSON example:
node examples/redis-json.jsRun the RediSearch example:
node examples/redis-search.jsNote: Redis Stack features require a Redis Stack installation. If you're using standard Redis, these examples will show how the commands work, but you'll need Redis Stack for full functionality.
Working with Connection Pooling (Latest v5+ Features)
For high-performance applications, use the latest connection pooling features:
const { createClientPool } = require('redis');
require('dotenv').config();
async function demonstrateConnectionPool() {
try {
console.log('๐ Starting connection pool demo...\n');
// Create a connection pool (v5+ feature)
const pool = await createClientPool({
url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`,
password: process.env.REDIS_PASSWORD,
database: parseInt(process.env.REDIS_DB) || 0
})
.on('error', err => console.error('Redis Client Pool Error', err))
.connect();
console.log('โ
Connection pool created and connected');
// Execute commands directly on the pool
await pool.ping();
console.log('๐ก Pool ping successful');
// Use pool for multiple operations
const operations = [];
for (let i = 0; i < 10; i++) {
operations.push(pool.set(`pool:test:${i}`, `value-${i}`));
}
await Promise.all(operations);
console.log('๐พ Stored 10 keys using pool');
// Read back the values
const values = [];
for (let i = 0; i < 10; i++) {
values.push(await pool.get(`pool:test:${i}`));
}
console.log('๐ Retrieved values:', values.slice(0, 3), '...');
// Clean up
for (let i = 0; i < 10; i++) {
await pool.del(`pool:test:${i}`);
}
await pool.destroy();
console.log('๐งน Pool destroyed');
console.log('\nโ
Connection pool demo completed!');
} catch (error) {
console.error('โ Error in connection pool demo:', error.message);
}
}
// Export for use in other examples
module.exports = { demonstrateConnectionPool };
// Run demo if called directly
if (require.main === module) {
demonstrateConnectionPool();
}Test Connection Pool
node examples/connection-pool.jsComprehensive Troubleshooting Guide
Common Connection Issues
Issue: "ECONNREFUSED" Error
Symptoms:
Error: connect ECONNREFUSED 127.0.0.1:6379Solutions:
-
Check Redis server status:
# In Sealos terminal redis-cli ping -
Verify connection parameters:
// Debug connection const client = redis.createClient({ url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`, password: process.env.REDIS_PASSWORD, socket: { connectTimeout: 60000, lazyConnect: true } }); client.on('error', (err) => { console.error('Detailed error:', err); }); -
Check environment variables:
console.log('Redis config:', { host: process.env.REDIS_HOST, port: process.env.REDIS_PORT, hasPassword: !!process.env.REDIS_PASSWORD });
Issue: "WRONGPASS" Authentication Error
Symptoms:
ReplyError: WRONGPASS invalid username-password pairSolutions:
-
Verify password in environment:
echo $REDIS_PASSWORD -
Test authentication manually:
redis-cli -h your_host -p 6379 -a your_password ping -
Handle authentication in code:
const client = redis.createClient({ url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`, password: process.env.REDIS_PASSWORD, // For Redis with ACL users username: process.env.REDIS_USERNAME || 'default' });
Issue: "Socket closed unexpectedly"
Symptoms:
Error: Socket closed unexpectedlySolutions:
-
Implement robust reconnection:
const client = redis.createClient({ socket: { reconnectStrategy: (retries) => { if (retries > 20) return false; return Math.min(retries * 50, 500); }, connectTimeout: 60000, lazyConnect: true } }); -
Handle connection events:
client.on('end', () => { console.log('Connection ended, attempting to reconnect...'); }); client.on('reconnecting', () => { console.log('Reconnecting to Redis...'); });
Performance Issues
Issue: Slow Response Times
Diagnosis:
// Monitor command execution time
const originalSend = client.sendCommand;
client.sendCommand = function(...args) {
const start = Date.now();
const result = originalSend.apply(this, args);
result.then(() => {
const duration = Date.now() - start;
if (duration > 100) { // Log slow commands
console.warn(`Slow Redis command: ${args[0]} took ${duration}ms`);
}
});
return result;
};Solutions:
-
Use pipelining for bulk operations:
// Instead of multiple individual commands const pipeline = client.multi(); for (let i = 0; i < 1000; i++) { pipeline.set(`key:${i}`, `value:${i}`); } await pipeline.exec(); -
Optimize data structures:
// Use appropriate data types // For counters: use INCR instead of GET/SET await client.incr('page_views'); // For bulk data: use HMSET instead of multiple SET await client.hSet('user:1001', { name: 'John', email: 'john@example.com', age: '30' });
Issue: Memory Usage Problems
Diagnosis:
async function diagnoseMemory() {
const info = await client.info('memory');
const stats = await client.info('stats');
console.log('Memory info:', info);
console.log('Stats:', stats);
// Check for memory leaks
const keyCount = await client.dbSize();
console.log('Total keys:', keyCount);
}Solutions:
-
Set appropriate TTL:
// Always set expiration for cache data await client.setEx('cache:user:1001', 3600, userData); // Use EXPIRE for existing keys await client.expire('session:abc123', 1800); -
Clean up unused keys:
async function cleanupOldKeys() { const cursor = '0'; let scanCursor = cursor; do { const result = await client.scan(scanCursor, { MATCH: 'temp:*', COUNT: 100 }); if (result.keys.length > 0) { await client.del(result.keys); } scanCursor = result.cursor; } while (scanCursor !== '0'); }
Data Consistency Issues
Issue: Race Conditions
Problem: Multiple clients modifying the same data simultaneously.
Solution - Optimistic Locking:
async function updateUserBalance(userId, amount) {
const key = `user:${userId}:balance`;
while (true) {
// Watch the key for changes
await client.watch(key);
const currentBalance = parseFloat(await client.get(key) || '0');
const newBalance = currentBalance + amount;
// Start transaction
const multi = client.multi();
multi.set(key, newBalance.toString());
try {
const results = await multi.exec();
if (results) {
// Transaction succeeded
return newBalance;
}
// Transaction failed due to key modification, retry
} catch (error) {
throw error;
}
}
}Solution - Lua Scripts for Atomicity:
const updateBalanceScript = `
local key = KEYS[1]
local amount = tonumber(ARGV[1])
local current = tonumber(redis.call('GET', key) or 0)
local new_balance = current + amount
if new_balance >= 0 then
redis.call('SET', key, new_balance)
return new_balance
else
return -1
end
`;
async function updateBalanceAtomic(userId, amount) {
const result = await client.eval(
updateBalanceScript,
1,
`user:${userId}:balance`,
amount.toString()
);
if (result === -1) {
throw new Error('Insufficient balance');
}
return result;
}Best Practices Summary
Development Best Practices
- Always use environment variables for configuration
- Implement proper error handling with specific error types
- Use connection pooling for high-traffic applications
- Set appropriate TTL for all cached data
- Use pipelining for bulk operations
- Implement health checks for production deployments
Security Best Practices
- Enable TLS/SSL in production
- Use strong passwords and consider ACL users
- Restrict network access to Redis instances
- Regularly update Redis and client libraries
- Monitor for suspicious activity
Performance Best Practices
- Choose appropriate data structures for your use case
- Use Lua scripts for complex atomic operations
- Monitor memory usage and implement cleanup strategies
- Optimize serialization for large objects
- Use Redis Stack modules when appropriate
Production Best Practices
- Implement comprehensive monitoring
- Set up proper logging and alerting
- Use container orchestration for scalability
- Plan for disaster recovery
- Regular performance testing and optimization
Complete Application Example
Let's create a comprehensive example that combines multiple Redis features. Create examples/complete-app.js:
const redisManager = require('../src/redisClient');
const TaskQueue = require('./task-queue');
const Leaderboard = require('./leaderboard');
const { CacheAsideService } = require('./cache-patterns');
class RedisApplication {
constructor() {
this.taskQueue = new TaskQueue('app_tasks');
this.leaderboard = new Leaderboard('user_scores');
this.cache = new CacheAsideService(3600);
}
async initialize() {
await redisManager.connect();
console.log('๐ Redis Application initialized');
}
async simulateUserActivity() {
console.log('\n๐ฅ Simulating user activity...');
// Add some users to leaderboard
const users = [
{ id: 'user1', name: 'Alice', score: 1500 },
{ id: 'user2', name: 'Bob', score: 2300 },
{ id: 'user3', name: 'Charlie', score: 1800 },
{ id: 'user4', name: 'Diana', score: 2100 }
];
for (const user of users) {
await this.leaderboard.updateScore(user.id, user.score, { name: user.name });
// Add task to send welcome email
await this.taskQueue.addTask({
type: 'welcome_email',
userId: user.id,
email: `${user.name.toLowerCase()}@example.com`
});
}
}
async processBackgroundTasks() {
console.log('\nโก Processing background tasks...');
for (let i = 0; i < 4; i++) {
const task = await this.taskQueue.processTask(1);
if (task) {
console.log(`๐ง Sending ${task.data.type} to ${task.data.email}`);
// Simulate email sending
await new Promise(resolve => setTimeout(resolve, 500));
await this.taskQueue.completeTask(task.id);
}
}
}
async showLeaderboard() {
console.log('\n๐ Current Leaderboard:');
const topPlayers = await this.leaderboard.getTopPlayers(5);
topPlayers.forEach(player => {
console.log(`${player.rank}. ${player.name} - ${player.score} points`);
});
}
async demonstrateCache() {
console.log('\n๐พ Cache demonstration:');
// Simulate expensive operation
const expensiveOperation = async (id) => {
console.log(`๐ Performing expensive calculation for ${id}...`);
await new Promise(resolve => setTimeout(resolve, 1000));
return { id, result: Math.random() * 1000, timestamp: new Date().toISOString() };
};
// First call - cache miss
console.log('First call (cache miss):');
const result1 = await this.cache.get('calculation:1', () => expensiveOperation('calc1'));
console.log('Result:', result1);
// Second call - cache hit
console.log('\nSecond call (cache hit):');
const result2 = await this.cache.get('calculation:1', () => expensiveOperation('calc1'));
console.log('Result:', result2);
}
async getApplicationStats() {
const client = redisManager.getClient();
const stats = {
queueStats: await this.taskQueue.getQueueStats(),
totalKeys: await client.dbSize(),
memoryUsage: await client.info('memory'),
topPlayer: await this.leaderboard.getTopPlayers(1)
};
return stats;
}
async cleanup() {
await redisManager.disconnect();
console.log('๐งน Application cleanup completed');
}
}
async function runCompleteExample() {
const app = new RedisApplication();
try {
await app.initialize();
await app.simulateUserActivity();
await app.processBackgroundTasks();
await app.showLeaderboard();
await app.demonstrateCache();
console.log('\n๐ Application Statistics:');
const stats = await app.getApplicationStats();
console.log('Queue stats:', stats.queueStats);
console.log('Total Redis keys:', stats.totalKeys);
console.log('Top player:', stats.topPlayer[0]);
console.log('\nโ
Complete application example finished!');
} catch (error) {
console.error('โ Application error:', error.message);
} finally {
await app.cleanup();
}
}
// Run the complete example
if (require.main === module) {
runCompleteExample();
}
module.exports = RedisApplication;Run All Examples
Now you can test all the examples we've built:
# Test connection
node tests/connection-test.js
# Basic operations
node examples/basic-operations.js
# Advanced data structures
node examples/leaderboard.js
node examples/task-queue.js
# Caching patterns
node examples/cache-patterns.js
# Redis Stack features (if available)
node examples/redis-json.js
node examples/redis-search.js
# Connection pooling (v5+ features)
node examples/connection-pool.js
# Complete application
node examples/complete-app.jsProject Structure Summary
Your final project structure should look like this:
Next Steps
Now that you have a solid foundation with Redis and Node.js, consider exploring:
- Production Deployment: Implement monitoring, logging, and error handling
- Performance Optimization: Use pipelining, connection pooling, and memory optimization
- Security: Enable TLS, implement proper authentication, and secure your Redis instance
- Scaling: Explore Redis Cluster, Sentinel, and horizontal scaling strategies
- Integration: Connect with your favorite Node.js frameworks (Express, NestJS, Fastify)
For more detailed information and updates, refer to the official node-redis documentation and Redis Stack documentation.
Explore with AI
Get AI insights on this article
Share this article
Last updated on