Hybrid Storage System
The CAIPE UI supports two storage modes with automatic fallback for resilient operation:
- MongoDB Mode (Default) - Persistent storage with sharing and collaboration features
- localStorage Mode (Fallback) - Local-only storage when MongoDB is unavailable
Architecture
How It Works
Storage Mode Detection
The app automatically detects which storage mode to use:
import { isMongoDBAvailable, getStorageMode } from '@/lib/storage-mode';
// Check availability (cached for 60s)
const available = await isMongoDBAvailable();
// Get current mode
const mode = await getStorageMode(); // 'mongodb' | 'localStorage'
Automatic Fallback
The system gracefully falls back to localStorage in these scenarios:
- MongoDB not configured -
MONGODB_URIorMONGODB_DATABASEenv vars missing - MongoDB connection fails - Network issues, server down, timeout
- API errors - Any error when trying to access MongoDB endpoints
Feature Parity
| Feature | MongoDB Mode | localStorage Mode |
|---|---|---|
| Create conversations | ✅ Persistent across devices | ✅ Local device only |
| View chat history | ✅ Synced across devices | ✅ Local device only |
| Search conversations | ✅ Server-side search | ⚠️ Client-side only |
| Share conversations | ✅ Full sharing features | ❌ Not available |
| Export conversations | ✅ Available | ✅ Available |
| Message persistence | ✅ Server-side | ✅ localStorage |
| A2A event streaming | ✅ Both modes | ✅ Both modes |
Configuration
Enable MongoDB Mode (Recommended)
Set these environment variables in ui/.env.local:
# MongoDB Configuration
MONGODB_URI=mongodb://admin:changeme@localhost:27017
MONGODB_DATABASE=caipe
# The app will automatically use MongoDB if configured
Run in localStorage-Only Mode
Simply don't set the MongoDB environment variables, or comment them out:
# MONGODB_URI=mongodb://admin:changeme@localhost:27017
# MONGODB_DATABASE=caipe
The app will detect this and run in localStorage-only mode with a notification banner.
User Experience
MongoDB Mode Indicators
- ✅ No special indicators (default expected mode)
- Sidebar syncs conversations from MongoDB on load
- New conversations appear immediately in sidebar
localStorage Mode Indicators
- 📦 Amber banner in sidebar: "Local Storage Mode"
- 📦 Small badge in chat view: "Local storage mode"
- Console logs indicate fallback:
[ChatStore] MongoDB unavailable - using localStorage only
Implementation Details
Key Files
-
ui/src/lib/storage-mode.ts- Detects MongoDB availability
- Caches results for 60 seconds
- Provides storage mode API
-
ui/src/lib/mongodb.ts- MongoDB connection utility
- Gracefully handles missing configuration
- Exports
isMongoDBConfiguredflag
-
ui/src/store/chat-store.ts- Zustand store with localStorage persistence
syncConversationsFromMongoDB()- syncs from backendcreateConversation()- creates in both stores
-
ui/src/components/layout/Sidebar.tsx- Displays storage mode indicator
- Auto-syncs on mount if MongoDB available
State Synchronization
// Zustand Store (localStorage) ←→ MongoDB
// On app start:
1. Zustand hydrates from localStorage
2. Sidebar calls syncConversationsFromMongoDB()
3. If MongoDB available: merge MongoDB data into Zustand
4. If MongoDB unavailable: keep using localStorage only
// On new conversation:
1. Create in Zustand store immediately (instant UI update)
2. Async: Create in MongoDB if available
3. On success: Sync back to ensure consistency
4. On failure: Continue with localStorage only
Testing
Test MongoDB Mode
# Start MongoDB
docker run -d --name caipe-mongo -p 27017:27017 \
-e MONGO_INITDB_ROOT_USERNAME=admin \
-e MONGO_INITDB_ROOT_PASSWORD=changeme \
mongo:7.0
# Verify in UI
# ✅ No storage mode banner
# ✅ Conversations persist across browser sessions
Test localStorage Mode
# Stop MongoDB
docker stop caipe-mongo
# Verify in UI
# ✅ Amber "Local Storage Mode" banner appears
# ✅ Conversations work but are local-only
# ✅ No errors in console
Test Automatic Fallback
# Start with MongoDB running
# Create some conversations
# Stop MongoDB mid-session
docker stop caipe-mongo
# Continue using the app
# ✅ Existing conversations still visible (from Zustand cache)
# ✅ New conversations save to localStorage
# ✅ Banner appears indicating local mode
Migration Path
From localStorage-Only to MongoDB
When you enable MongoDB for the first time:
- Existing localStorage conversations remain accessible
- New conversations are saved to both places
- Over time, the MongoDB becomes the source of truth
- localStorage acts as a cache/fallback
No manual migration needed - the hybrid system handles it automatically!
From MongoDB to localStorage-Only
If you disable MongoDB:
- Recent conversations cached in localStorage remain accessible
- Older conversations (not in cache) become unavailable
- All new conversations save to localStorage only
- To recover: re-enable MongoDB and data will be accessible again
Troubleshooting
"Local Storage Mode" Banner Won't Go Away
Cause: MongoDB is not accessible
Solutions:
- Check MongoDB is running:
docker ps | grep mongo - Verify
MONGODB_URIin.env.local - Check network connectivity:
curl http://localhost:27017 - Check browser console for errors
Conversations Disappear When Switching Browsers
Cause: Running in localStorage mode (local-only)
Solution: Enable MongoDB to sync across devices
API Errors About MongoDB
Expected behavior if MongoDB not configured. The app continues to work with localStorage.
To fix: Set MONGODB_URI and MONGODB_DATABASE environment variables.
Best Practices
For Development
# Option 1: Quick local development (no MongoDB needed)
# Just comment out MongoDB env vars
# Pros: Fast setup, no dependencies
# Cons: Local-only data
# Option 2: Full-stack development (with MongoDB)
make caipe-mongodb # Start MongoDB
# Set MongoDB env vars
# Pros: Full feature testing
# Cons: Requires Docker
For Production
Always use MongoDB mode in production:
- ✅ Persistent storage
- ✅ Multi-user support
- ✅ Sharing features
- ✅ Server-side search
- ✅ Data backups possible
For Demos
localStorage mode is fine for single-user demos:
- ✅ No infrastructure needed
- ✅ Fast setup
- ✅ Core features work
- ⚠️ Data is local-only
Security Considerations
localStorage Mode
- Data stored in browser localStorage (unencrypted)
- Accessible to all JavaScript on same origin
- Not suitable for sensitive data
- Use HTTPS in production
MongoDB Mode
- Data encrypted in transit (if using TLS)
- Server-side authentication required
- Follow MongoDB security best practices
- Enable authentication in production
Performance
localStorage Mode
- ⚡ Instant - No network calls
- ⚡ Low latency - Local storage access
- ⚠️ Limited capacity - ~5-10MB per domain
- ⚠️ No server-side search - Linear scan only
MongoDB Mode
- 🌐 Network latency - API round-trip required
- 🔍 Server-side search - Indexed queries
- 💾 Unlimited storage - No practical limit
- 🔄 Caching - Zustand provides local cache
Monitoring
Console Logs
// Storage mode detection
[StorageMode] MongoDB backend unavailable, using localStorage only
// Conversation creation
[ChatStore] MongoDB unavailable - conversation saved to localStorage only
[ChatStore] Created local conversation with ID: abc-123
// Sync operations
[ChatStore] Synced 15 conversations from MongoDB
[ChatStore] MongoDB unavailable - using localStorage only
Visual Indicators
- 📦 Amber banner in sidebar (localStorage mode)
- ✅ No indicator (MongoDB mode - default)
Future Enhancements
Potential improvements:
- Offline sync - Queue operations when offline, sync when online
- Conflict resolution - Handle concurrent edits across devices
- Selective sync - Only sync recent conversations
- IndexedDB - Replace localStorage with IndexedDB for larger capacity
- Progressive Web App - Full offline support with service workers
Summary
The hybrid storage system provides:
- ✅ Zero-config local development - No MongoDB required to start coding
- ✅ Production-ready persistence - MongoDB for shared environments
- ✅ Graceful degradation - Continues working if MongoDB fails
- ✅ Automatic detection - No manual configuration needed
- ✅ User transparency - Clear indicators of storage mode
- ✅ No data loss - Cached data remains accessible
Bottom line: The app "just works" regardless of backend availability! 🎉