Architecture
System Design
This section explains the overall architecture of the chat template, how data flows between Firebase and the frontend, and the design decisions that make it scalable and cost-effective.
Architecture Overview
The chat template follows a layered architecture that separates concerns between data persistence, business logic, and UI presentation. This design makes it easy to understand, maintain, and extend.
Layer Structure
Realtime Database: Fast, ephemeral state (presence, typing, room presence)
Firestore: Persistent data (users, rooms, messages)
Thin wrappers around Firebase SDK that handle subscriptions, writes, and data transformations. Located in features/chat/services.
React hooks that connect services to components, manage subscriptions lifecycle, and provide loading/error states. Located in features/chat/hooks.
Presentational and container components that render the UI. Located in features/chat/components.
Data Flow Patterns
Reading Data (Subscriptions)
The template uses Firebase real-time listeners to keep the UI synchronized with the database:
useRoomList)subscribeRoomsByUser)onSnapshot) and returns unsubscribe functionWriting Data
Writes are typically triggered by user actions and use Firebase batch operations when multiple documents need to be updated atomically:
useSendMessage)sendMessage)Design Decisions
Why Realtime Database for Presence & Typing?
Presence and typing indicators change very frequently (multiple times per second) and are ephemeral (they don't need to be stored long-term). Realtime Database is optimized for this use case:
- Lower cost for high-frequency writes compared to Firestore
- Built-in
onDisconnecthooks for automatic cleanup - Faster updates with lower latency
- No need for complex indexing or querying
Why Firestore for Messages & Rooms?
Messages and rooms need to be persisted, queried, and indexed:
- Complex queries (e.g. "all rooms where I'm a participant, sorted by last message")
- Pagination support for large message histories
- Offline persistence and sync
- Better security rules for fine-grained access control
Denormalization Strategy
The template denormalizes some data to optimize read performance:
rooms.lastMessage– avoids querying messages collection just to show previewrooms.participantsCount– enables sorting without reading all participantsrooms.unreadCounts– per-user counters updated atomically with messages
Trade-off: These fields must be kept in sync when the source data changes (e.g. when sending a message, both the message and lastMessage are updated in a batch).
Subscription Management
All Firebase listeners are properly cleaned up to prevent memory leaks and unnecessary costs:
- Hooks return cleanup functions that unsubscribe when components unmount
- Services return unsubscribe functions that can be called explicitly
- Presence sync tracks active subscriptions and removes unused ones automatically
Scalability Considerations
The template is designed to scale efficiently:
- Efficient queries: Room list uses
array-containson participants array, which is indexed by default in Firestore - Sub-collections: Messages are stored as sub-collections under rooms, enabling efficient pagination and isolation
- Selective subscriptions: Only active rooms and visible users have active listeners
- Batch operations: Multiple writes are combined into single transactions to reduce costs
- Client-side filtering: User search and presence tracking happen on the client to reduce Firestore reads