Real-time Firebase Features in GaragePRO
The Real-time Challenge
GaragePRO needed job-specific chat where mechanics, managers, and office staff could communicate in real time - but here's the twist: the team spoke five different languages (English, French, Portuguese, Spanish, German). Messages needed to be translated automatically. See the full GaragePRO case study for the complete feature breakdown.
Architecture Decisions
Why Firebase over WebSockets
I considered building a custom WebSocket server, but Firebase Firestore's real-time listeners gave me exactly what I needed with zero server management:
- •Instant sync across all connected clients
- •Offline support with persistent local cache
- •Security rules that enforce per-job access control
- •Automatic reconnection - no heartbeat logic needed
Message Flow
- User sends a message in their language
- Firestore trigger fires a Cloud Function
- Cloud Function calls Google Translation API
- Translated versions are written back to the message document
- All listeners receive the update in their preferred language
typescript// Simplified message structure
interface Message {
id: string;
jobId: string;
senderId: string;
originalText: string;
originalLang: string;
translations: Record<string, string>;
timestamp: Timestamp;
}Security Rules That Scale
The biggest lesson was writing Firestore security rules that are both secure and performant:
match /jobs/{jobId}/messages/{messageId} {
allow read: if isAuthenticated() &&
(isAdmin() || isAssignedToJob(jobId));
allow create: if isAuthenticated() &&
isAssignedToJob(jobId) &&
request.resource.data.senderId == request.auth.uid;
}Key principle: validate at the database level, not just the UI. Custom Claims RBAC ensures that even if someone bypasses the frontend, they can't access data they shouldn't.
Offline-First Lessons
Making GaragePRO work offline was harder than expected. Key learnings:
- **Enable persistence early** - `enableIndexedDbPersistence()` must be called before any Firestore reads
- **Design for conflicts** - When two mechanics update the same job offline, last-write-wins isn't always correct
- **Cache management** - Firestore's local cache can grow large on devices with many jobs; implement periodic cleanup
Cost Optimisation
Firebase pricing is per-read, and real-time listeners count every document change. My initial implementation was reading the entire messages collection on every update - costing 200+ reads per minute for active jobs.
Solution: Paginate messages (load last 50), use limit() queries, and implement a "load more" button. This reduced reads by 85%.
Results
- •200+ real-time messages sent daily across the platform
- •Sub-second delivery - messages appear within 400ms on average
- •Zero downtime - Firebase's infrastructure handles scaling automatically
- •Offline resilience - mechanics can log updates in dead zones and sync later
The project taught me that real-time features are the single biggest "wow factor" for non-technical clients. When a manager sees a message appear instantly without refreshing, that's when they trust the software.
---
*Want real-time features in your app? See what I offer or get in touch.*
Need help building something similar?
I build production-grade web applications with transparent pricing and clear timelines.