This is a submission for the Redis AI Challenge: Real-Time AI Innovators.
What I Built
I created a Real-Time Donation Leaderboard that combines the power of Redis 8 with OpenAI to deliver instant donation tracking, live leaderboards, and AI-powered predictions. This full-stack TypeScript application demonstrates how Redis can serve as the backbone for real-time applications while enabling intelligent insights through AI integration.
Key Features:
- ⚡ Real-time donation tracking with instant leaderboard updates
- 🏆 Live leaderboard with automatic polling every 3 seconds
- 🤖 AI-powered predictions for rising donors and daily totals
- 📊 Comprehensive analytics with donation patterns and trends
- 🎨 Modern responsive UI with smooth animations
- 🔒 Production-ready with security, error handling, and health checks
Demo
🔗 Links
📱 Screenshots
- Main leaderboard interface
- Donation form in action
- AI predictions panel
- Mobile responsive design
- Real-time updates in progress
How I Used Redis 8
Redis 8 serves as the complete real-time data layer for this application, leveraging multiple Redis data structures and features:
🏗️ Core Redis Architecture
1. Sorted Sets for Leaderboard Rankings
// Real-time leaderboard updates using Redis Sorted Sets
// Increment donor's total amount atomically
multi.zIncrBy('leaderboard', donation.amount, donation.donorId);
// Get top donors with scores (descending order)
const results = await this.client.zRangeWithScores('leaderboard', 0, limit - 1, {
REV: true,
});
2. Streams for Event Sourcing
// Track all donation events with Redis Streams
multi.xAdd('donations:stream', '*', {
donorId: donation.donorId,
donorName: donation.donorName,
amount: donation.amount.toString(),
message: donation.message || '',
timestamp: donation.timestamp.toString(),
});
// Retrieve recent donations for AI analysis
const streamData = await this.client.xRange('donations:stream', '-', '+');
3. Hash Maps for Donor Information
// Store donor name mapping efficiently
multi.hSet('donor:names', donation.donorId, donation.donorName);
// Get donor names for leaderboard display
const donorIds = results.map(item => item.value);
const names = await this.client.hmGet('donor:names', donorIds);
4. Atomic Multi-Operations
// Use Redis multi for atomic operations
const multi = this.client.multi();
// Add to streams for event history
multi.xAdd('donations:stream', '*', donationData);
// Update leaderboard (sorted set)
multi.zIncrBy('leaderboard', donation.amount, donation.donorId);
// Store donor name mapping
multi.hSet('donor:names', donation.donorId, donation.donorName);
// Execute all commands atomically
await multi.exec();
🚀 Real-Time Performance Benefits
Connection Management with Retry Logic:
this.client = createClient({
username: process.env.REDIS_USERNAME || 'default',
password: process.env.REDIS_PASSWORD || '',
socket: {
host: process.env.REDIS_URL || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
reconnectStrategy: retries => Math.min(retries * 50, 500),
},
});
Health Monitoring:
async ping(): Promise<boolean> {
try {
await this.ensureConnection();
const result = await this.client.ping();
return result === 'PONG';
} catch (error) {
console.error('Redis ping failed:', error);
return false;
}
}
🤖 AI Integration with Redis
Redis serves as the intelligent data pipeline for AI predictions:
async generatePredictions(): Promise<PredictionData> {
// Get current leaderboard and recent donations from Redis
const [leaderboard, recentDonations, totalDonations] = await Promise.all([
redisService.getLeaderboard(20),
redisService.getRecentDonations(24),
redisService.getTotalDonations(),
]);
// Prepare data for AI analysis
const donationTrends = this.analyzeDonationTrends(recentDonations);
const leaderboardData = leaderboard.slice(0, 10);
// Generate AI predictions using OpenAI
const completion = await this.openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: [
{
role: 'system',
content: 'You are an AI analyst specializing in donation pattern analysis and predictions.',
},
{
role: 'user',
content: this.buildPredictionPrompt(leaderboardData, donationTrends, totalDonations),
},
],
temperature: 0.3,
max_tokens: 1000,
});
}
📊 Data Analysis from Redis Streams
private analyzeDonationTrends(donations: Donation[]): any {
const hourlyTrends: { [hour: string]: number } = {};
const donorActivity: { [donorId: string]: { count: number; total: number; recent: number } } = {};
const now = Date.now();
const oneHour = 60 * 60 * 1000;
donations.forEach(donation => {
const hour = Math.floor(donation.timestamp / oneHour);
hourlyTrends[hour] = (hourlyTrends[hour] || 0) + donation.amount;
if (!donorActivity[donation.donorId]) {
donorActivity[donation.donorId] = { count: 0, total: 0, recent: 0 };
}
donorActivity[donation.donorId].count++;
donorActivity[donation.donorId].total += donation.amount;
// Count recent activity (last 6 hours)
if (now - donation.timestamp < 6 * oneHour) {
donorActivity[donation.donorId].recent += donation.amount;
}
});
return { hourlyTrends, donorActivity };
}
Technical Architecture
🏗️ Full Stack Implementation
Backend: Node.js + Express + TypeScript
- RESTful API with comprehensive error handling
- Redis integration with connection pooling
- OpenAI API integration for predictions
- Security middleware (Helmet, CORS)
- Health monitoring and graceful shutdown
Frontend: React 18 + TypeScript + Vite
- Real-time polling every 3 seconds
- Responsive design with SCSS
- Smooth animations and loading states
- Error boundaries and user feedback
- Accessibility-compliant interface
Database: Redis as primary data store
- Sorted Sets for rankings
- Streams for event history
- Hash Maps for metadata
- Atomic operations for consistency
🔧 Key Technical Decisions
Why Redis Over Traditional Databases?
- Performance: In-memory operations for instant leaderboard updates
- Real-time: Native support for live data structures
- Simplicity: Single data store for multiple use cases
- AI-Ready: Fast data aggregation for ML pipelines
Actual Redis Data Model
leaderboard (Sorted Set)
├── john_doe → 1500.00
├── jane_smith → 1200.50
└── bob_johnson → 950.25
donations:stream (Stream)
├── 1691234567890-0: {donorId: "john_doe", donorName: "John Doe", amount: "25.00", ...}
├── 1691234567891-0: {donorId: "jane_smith", donorName: "Jane Smith", amount: "50.00", ...}
└── 1691234567892-0: {donorId: "john_doe", donorName: "John Doe", amount: "75.00", ...}
donor:names (Hash)
├── "john_doe" → "John Doe"
├── "jane_smith" → "Jane Smith"
└── "bob_johnson" → "Bob Johnson"
🚀 Performance Optimizations
Connection Management with Retry Logic
this.client = createClient({
username: process.env.REDIS_USERNAME || 'default',
password: process.env.REDIS_PASSWORD || '',
socket: {
host: process.env.REDIS_URL || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
reconnectStrategy: retries => Math.min(retries * 50, 500),
},
});
Atomic Multi-Operations
// Use Redis multi for atomic operations
const multi = this.client.multi();
// Add to streams for event history
multi.xAdd('donations:stream', '*', {
donorId: donation.donorId,
donorName: donation.donorName,
amount: donation.amount.toString(),
message: donation.message || '',
timestamp: donation.timestamp.toString(),
});
// Update leaderboard (sorted set)
multi.zIncrBy('leaderboard', donation.amount, donation.donorId);
// Store donor name mapping
multi.hSet('donor:names', donation.donorId, donation.donorName);
// Execute all commands atomically
await multi.exec();
Efficient Data Retrieval
// Get leaderboard with scores in one operation
const results = await this.client.zRangeWithScores('leaderboard', 0, limit - 1, {
REV: true,
});
// Batch get donor names
const donorIds = results.map(item => item.value);
const names = await this.client.hmGet('donor:names', donorIds);
AI-Powered Features
🤖 Intelligent Predictions
The application uses OpenAI GPT-3.5-turbo to analyze donation patterns and generate predictions:
Real AI Implementation
async generatePredictions(): Promise<PredictionData> {
try {
// Get current leaderboard and recent donations from Redis
const [leaderboard, recentDonations, totalDonations] = await Promise.all([
redisService.getLeaderboard(20),
redisService.getRecentDonations(24),
redisService.getTotalDonations(),
]);
// Prepare data for AI analysis
const donationTrends = this.analyzeDonationTrends(recentDonations);
const leaderboardData = leaderboard.slice(0, 10);
// Generate AI predictions using OpenAI
const completion = await this.openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: [
{
role: 'system',
content: 'You are an AI analyst specializing in donation pattern analysis and predictions.',
},
{
role: 'user',
content: this.buildPredictionPrompt(leaderboardData, donationTrends, totalDonations),
},
],
temperature: 0.3,
max_tokens: 1000,
});
const response = completion.choices[0]?.message?.content;
const predictions = JSON.parse(response);
return this.validateAndEnhancePredictions(predictions, leaderboard, totalDonations);
} catch (error) {
console.error('AI prediction error:', error);
return this.generateFallbackPredictions();
}
}
Data Analysis Pipeline
private analyzeDonationTrends(donations: Donation[]): any {
const hourlyTrends: { [hour: string]: number } = {};
const donorActivity: { [donorId: string]: { count: number; total: number; recent: number } } = {};
const now = Date.now();
const oneHour = 60 * 60 * 1000;
donations.forEach(donation => {
const hour = Math.floor(donation.timestamp / oneHour);
hourlyTrends[hour] = (hourlyTrends[hour] || 0) + donation.amount;
if (!donorActivity[donation.donorId]) {
donorActivity[donation.donorId] = { count: 0, total: 0, recent: 0 };
}
donorActivity[donation.donorId].count++;
donorActivity[donation.donorId].total += donation.amount;
// Count recent activity (last 6 hours)
if (now - donation.timestamp < 6 * oneHour) {
donorActivity[donation.donorId].recent += donation.amount;
}
});
return {
hourlyTrends,
donorActivity,
totalDonations: donations.length,
totalAmount: donations.reduce((sum, d) => sum + d.amount, 0),
};
}
AI Prompt Engineering
private buildPredictionPrompt(leaderboard: LeaderboardEntry[], trends: any, totalDonations: number): string {
return `
Analyze the following donation data and provide predictions in JSON format:
CURRENT LEADERBOARD:
${leaderboard.map(entry => `${entry.rank}. ${entry.donorName}: $${entry.totalAmount}`).join('\n')}
DONATION TRENDS:
- Total donations today: $${totalDonations}
- Recent donor activity: ${JSON.stringify(trends.donorActivity, null, 2)}
- Hourly trends: ${JSON.stringify(trends.hourlyTrends, null, 2)}
Please provide predictions in this EXACT JSON format:
{
"risingStars": [
{
"donorId": "donor_id",
"donorName": "Donor Name",
"currentAmount": 100,
"predictedGrowth": 25.5,
"confidence": 0.75
}
],
"dailyTotal": {
"currentTotal": ${totalDonations},
"predictedTotal": estimated_end_of_day_total,
"confidence": 0.8,
"timeRemaining": "X hours remaining"
}
}
`;
}
🧠 Fallback Intelligence
When OpenAI is unavailable, the system uses Redis-based heuristics:
private async generateFallbackPredictions(): Promise<PredictionData> {
const [leaderboard, totalDonations] = await Promise.all([
redisService.getLeaderboard(10),
redisService.getTotalDonations(),
]);
// Simple heuristic-based predictions
const risingStars = leaderboard
.slice(3, 8) // Pick middle-ranking donors as potential rising stars
.map(entry => ({
donorId: entry.donorId,
donorName: entry.donorName,
currentAmount: entry.totalAmount,
predictedGrowth: Math.random() * 50 + 10, // Random growth between 10-60
confidence: 0.5,
}));
const currentHour = new Date().getHours();
const hoursRemaining = 24 - currentHour;
const growthFactor = 1 + (Math.random() * 0.4 + 0.1); // 10-50% growth
return {
risingStars,
dailyTotal: {
currentTotal: totalDonations,
predictedTotal: Math.round(totalDonations * growthFactor),
confidence: 0.6,
timeRemaining: `${hoursRemaining} hours remaining`,
},
};
}
Prediction Validation
private validateAndEnhancePredictions(
predictions: any,
leaderboard: LeaderboardEntry[],
totalDonations: number,
): PredictionData {
// Ensure rising stars exist and are valid
const risingStars = (predictions.risingStars || [])
.filter((star: any) => star.donorId && star.donorName)
.slice(0, 5)
.map((star: any) => ({
donorId: star.donorId,
donorName: star.donorName,
currentAmount: Math.max(0, star.currentAmount || 0),
predictedGrowth: Math.max(0, Math.min(1000, star.predictedGrowth || 0)),
confidence: Math.max(0.1, Math.min(0.9, star.confidence || 0.5)),
}));
// Validate daily total prediction
const currentHour = new Date().getHours();
const hoursRemaining = 24 - currentHour;
const dailyTotal = {
currentTotal: totalDonations,
predictedTotal: Math.max(totalDonations, predictions.dailyTotal?.predictedTotal || totalDonations * 1.2),
confidence: Math.max(0.1, Math.min(0.9, predictions.dailyTotal?.confidence || 0.6)),
timeRemaining: `${hoursRemaining} hours remaining`,
};
return { risingStars, dailyTotal };
}
Real-World Impact
📈 Technical Benefits
- Response Time: Fast in-memory Redis operations
- Data Consistency: Atomic multi-operations ensure data integrity
- Scalability: Redis can handle high concurrent loads
- Memory Efficiency: Optimized data structures for leaderboard use case
- Real-time Updates: 3-second polling provides near real-time experience
🎯 Use Cases
This architecture is perfect for:
- Charity Fundraising: Real-time donation tracking and leaderboards
- Gaming Leaderboards: Live player rankings and achievements
- Sales Competitions: Team performance tracking and motivation
- Social Media: Trending content and engagement rankings
- E-commerce: Real-time inventory and sales tracking
🔮 Future Enhancements
- WebSocket Integration: Replace polling with real-time push notifications
- Redis Pub/Sub: Instant updates to all connected clients
- Advanced Analytics: More sophisticated trend analysis
- Machine Learning: Custom prediction models trained on historical data
- Geographic Analytics: Location-based donation insights
- Mobile App: Native mobile applications with push notifications
Getting Started
🚀 Quick Setup
# Clone the repository
git clone [your-repo-url]
cd leaderboard-donation
# Run automated setup
./setup.sh
# Configure environment variables
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.env
# Start the application
# Terminal 1: Backend
cd backend && npm run dev
# Terminal 2: Frontend
cd frontend && npm run dev
# Optional: Seed test data
cd backend && npm run seed
🔧 Configuration
Backend (.env):
PORT=3001
REDIS_URL=redis://your-redis-host:6379
REDIS_PASSWORD=your-redis-password
OPENAI_API_KEY=your-openai-api-key
Frontend (.env):
VITE_API_URL=http://localhost:3001
📚 API Implementation
Donation Submission
async submitDonation(req: Request, res: Response): Promise<void> {
try {
const { donorName, amount, message }: DonationRequest = req.body;
// Validation
if (!donorName || typeof donorName !== 'string' || donorName.trim().length === 0) {
res.status(400).json({ error: 'Donor name is required' });
return;
}
if (!amount || typeof amount !== 'number' || amount <= 0) {
res.status(400).json({ error: 'Valid donation amount is required' });
return;
}
if (amount > 10000) {
res.status(400).json({ error: 'Donation amount cannot exceed $10,000' });
return;
}
// Create donation object
const donation: Donation = {
id: uuidv4(),
donorId: donorName.toLowerCase().replace(/\s+/g, '_'),
donorName: donorName.trim(),
amount: Math.round(amount * 100) / 100, // Round to 2 decimal places
message: message?.trim(),
timestamp: Date.now(),
};
// Save to Redis
await redisService.addDonation(donation);
res.status(201).json({
success: true,
donation: {
id: donation.id,
donorName: donation.donorName,
amount: donation.amount,
message: donation.message,
timestamp: donation.timestamp,
},
});
} catch (error) {
console.error('Error submitting donation:', error);
res.status(500).json({ error: 'Internal server error' });
}
}
Leaderboard Retrieval
async getLeaderboard(req: Request, res: Response): Promise<void> {
try {
const limit = parseInt(req.query.limit as string) || 10;
if (limit > 50) {
res.status(400).json({ error: 'Limit cannot exceed 50' });
return;
}
const leaderboard = await redisService.getLeaderboard(limit);
res.json({
success: true,
leaderboard,
timestamp: Date.now(),
});
} catch (error) {
console.error('Error fetching leaderboard:', error);
res.status(500).json({ error: 'Internal server error' });
}
}
Statistics Calculation
async getStats(req: Request, res: Response): Promise<void> {
try {
const [totalDonations, recentDonations] = await Promise.all([
redisService.getTotalDonations(),
redisService.getRecentDonations(24),
]);
const stats = {
totalDonations,
donationsToday: recentDonations.length,
averageDonation:
recentDonations.length > 0
? recentDonations.reduce((sum, d) => sum + d.amount, 0) / recentDonations.length
: 0,
};
res.json({
success: true,
stats,
timestamp: Date.now(),
});
} catch (error) {
console.error('Error fetching stats:', error);
res.status(500).json({ error: 'Internal server error' });
}
}
Conclusion
This project demonstrates how Redis 8 can serve as a complete real-time data platform, enabling both high-performance applications and intelligent AI integrations. By leveraging Redis's diverse data structures and combining them with OpenAI's predictive capabilities, we've created a system that's not just fast and reliable, but also intelligent and insightful.
The combination of Redis's sub-millisecond performance with AI-powered predictions creates a new category of applications that are both reactive and predictive - responding instantly to user actions while providing intelligent insights about future trends.
🏆 Why This Matters
In today's real-time world, users expect instant feedback and intelligent insights. This project shows how Redis 8 makes it possible to deliver both, creating applications that are not just fast, but smart.
Redis isn't just a cache anymore - it's an intelligent real-time data platform.
Built with ❤️ using Redis 8, OpenAI, TypeScript, React, and Node.js
Top comments (0)