Realtime
Realtime Database Service
Real-time data synchronization with Firebase Realtime Database using Svelte 5 runes
Realtime Database Service
The firekitRealtimeDB
and firekitRealtimeList
services provide real-time data synchronization with Firebase Realtime Database, offering reactive state management for single values and lists of data.
Overview
The Realtime Database service handles:
- Real-time data synchronization
- Single value subscriptions
- List data management with pagination
- Data transformations and filtering
- Offline support and caching
- Conflict resolution
- Performance optimization
- Error handling and retry mechanisms
Quick Start
<script>
import { firekitRealtimeDB, firekitRealtimeList } from 'svelte-firekit';
// Single value subscription
const userStatus = firekitRealtimeDB<{ online: boolean; lastSeen: number }>('users/123/status');
const statusData = $derived(userStatus.data);
const statusLoading = $derived(userStatus.loading);
const statusError = $derived(userStatus.error);
// List data subscription
const messages = firekitRealtimeList<Message>('messages');
const messagesList = $derived(messages.list);
const messagesLoading = $derived(messages.loading);
const messagesError = $derived(messages.error);
// Update data
async function updateStatus() {
await userStatus.set({ online: true, lastSeen: Date.now() });
}
async function addMessage() {
await messages.push({
text: 'Hello World',
userId: '123',
timestamp: Date.now()
});
}
</script>
{#if statusLoading}
<p>Loading status...</p>
{:else if statusData}
<div>
<p>Status: {statusData.online ? 'Online' : 'Offline'}</p>
<button onclick={updateStatus}>Update Status</button>
</div>
{/if}
{#if messagesLoading}
<p>Loading messages...</p>
{:else}
<div>
{#each messagesList as message}
<div>{message.text}</div>
{/each}
<button onclick={addMessage}>Add Message</button>
</div>
{/if}
Single Value Subscriptions
Basic Usage
import { firekitRealtimeDB } from 'svelte-firekit';
// Simple subscription
const userProfile = firekitRealtimeDB<UserProfile>('users/123/profile');
// With default value
const userSettings = firekitRealtimeDB<UserSettings>('users/123/settings', {
theme: 'light',
notifications: true
});
// With options
const userStatus = firekitRealtimeDB<UserStatus>(
'users/123/status',
{
online: false,
lastSeen: 0
},
{
persist: true,
transform: (data) => ({
...data,
lastSeenFormatted: new Date(data.lastSeen).toLocaleString()
})
}
);
Data Operations
import { firekitRealtimeDB } from 'svelte-firekit';
const userProfile = firekitRealtimeDB<UserProfile>('users/123/profile');
// Set data
await userProfile.set({
name: 'John Doe',
email: '[email protected]',
age: 30
});
// Update specific fields
await userProfile.update({
age: 31,
lastUpdated: Date.now()
});
// Remove data
await userProfile.remove();
// Push data (creates unique key)
const newKey = await userProfile.push({
name: 'Jane Doe',
email: '[email protected]'
});
// Transaction
await userProfile.transaction((currentData) => {
if (!currentData) return { name: 'New User' };
return { ...currentData, visits: (currentData.visits || 0) + 1 };
});
Reactive State
import { firekitRealtimeDB } from 'svelte-firekit';
const userProfile = firekitRealtimeDB<UserProfile>('users/123/profile');
// Reactive state
const profileData = $derived(userProfile.data);
const profileLoading = $derived(userProfile.loading);
const profileError = $derived(userProfile.error);
const profileRef = $derived(userProfile.ref);
// Effects
$effect(() => {
if (profileData) {
console.log('Profile updated:', profileData);
}
});
$effect(() => {
if (profileError) {
console.error('Profile error:', profileError);
}
});
List Data Management
Basic List Usage
import { firekitRealtimeList } from 'svelte-firekit';
// Simple list
const messages = firekitRealtimeList<Message>('messages');
// With options
const paginatedMessages = firekitRealtimeList<Message>('messages', {
limit: 50,
orderBy: 'timestamp',
reverse: true
});
// With filters
const userMessages = firekitRealtimeList<Message>('messages', {
where: [['userId', '==', '123']],
limit: 20
});
List Operations
import { firekitRealtimeList } from 'svelte-firekit';
const messages = firekitRealtimeList<Message>('messages');
// Add item
await messages.push({
text: 'Hello World',
userId: '123',
timestamp: Date.now()
});
// Add item with custom key
await messages.set('custom-key', {
text: 'Custom message',
userId: '123',
timestamp: Date.now()
});
// Update item
await messages.update('message-id', {
text: 'Updated message'
});
// Remove item
await messages.remove('message-id');
// Clear all items
await messages.clear();
// Batch operations
await messages.batch([
{ type: 'push', data: { text: 'Message 1', userId: '123' } },
{ type: 'push', data: { text: 'Message 2', userId: '456' } },
{ type: 'remove', key: 'old-message' }
]);
Pagination and Filtering
import { firekitRealtimeList } from 'svelte-firekit';
// Paginated list
const paginatedMessages = firekitRealtimeList<Message>('messages', {
limit: 20,
orderBy: 'timestamp',
reverse: true
});
// Load more
await paginatedMessages.loadMore();
// Filtered list
const filteredMessages = firekitRealtimeList<Message>('messages', {
where: [
['userId', '==', '123'],
['timestamp', '>', Date.now() - 86400000] // Last 24 hours
],
limit: 50
});
// Search functionality
const searchResults = firekitRealtimeList<Message>('messages', {
where: [['text', 'contains', 'hello']],
limit: 10
});
Reactive List State
import { firekitRealtimeList } from 'svelte-firekit';
const messages = firekitRealtimeList<Message>('messages');
// Reactive state
const messagesList = $derived(messages.list);
const messagesLoading = $derived(messages.loading);
const messagesError = $derived(messages.error);
const messagesCount = $derived(messages.count);
const hasMore = $derived(messages.hasMore);
// Computed values
const sortedMessages = $derived(messagesList.sort((a, b) => b.timestamp - a.timestamp));
const userMessages = $derived(messagesList.filter((m) => m.userId === '123'));
// Effects
$effect(() => {
console.log('Messages count:', messagesCount);
console.log('Has more:', hasMore);
});
Advanced Features
Data Transformations
import { firekitRealtimeDB, firekitRealtimeList } from 'svelte-firekit';
// Transform single value
const userProfile = firekitRealtimeDB<UserProfile>('users/123/profile', null, {
transform: (data) => ({
...data,
fullName: `${data.firstName} ${data.lastName}`,
age: calculateAge(data.birthDate),
formattedCreated: new Date(data.createdAt).toLocaleDateString()
})
});
// Transform list items
const messages = firekitRealtimeList<Message>('messages', {
transform: (item) => ({
...item,
formattedTime: new Date(item.timestamp).toLocaleTimeString(),
isOwn: item.userId === currentUserId
})
});
Offline Support
import { firekitRealtimeDB, firekitRealtimeList } from 'svelte-firekit';
// Enable offline persistence
const userProfile = firekitRealtimeDB<UserProfile>('users/123/profile', null, {
persist: true,
offlineFirst: true
});
// Check connection status
const isOnline = $derived(userProfile.isOnline);
const pendingWrites = $derived(userProfile.pendingWrites);
// Sync when online
$effect(() => {
if (isOnline && pendingWrites.length > 0) {
userProfile.sync();
}
});
Conflict Resolution
import { firekitRealtimeDB } from 'svelte-firekit';
const userProfile = firekitRealtimeDB<UserProfile>('users/123/profile', null, {
conflictResolution: 'server-wins', // 'server-wins' | 'client-wins' | 'merge'
mergeStrategy: (serverData, clientData) => ({
...serverData,
...clientData,
lastModified: Math.max(serverData.lastModified, clientData.lastModified)
})
});
Performance Optimization
import { firekitRealtimeDB, firekitRealtimeList } from 'svelte-firekit';
// Optimize for frequent updates
const userStatus = firekitRealtimeDB<UserStatus>('users/123/status', null, {
throttle: 1000, // Update at most once per second
debounce: 500 // Wait 500ms after last change
});
// Optimize list rendering
const messages = firekitRealtimeList<Message>('messages', {
virtualization: true,
itemHeight: 60,
viewportHeight: 400
});
Svelte Component Integration
Single Value Component
<script>
import { firekitRealtimeDB } from 'svelte-firekit';
import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
export let path: string;
export let defaultValue: any = null;
const data = firekitRealtimeDB(path, defaultValue);
const value = $derived(data.data);
const loading = $derived(data.loading);
const error = $derived(data.error);
async function updateValue() {
await data.set({ ...value, updatedAt: Date.now() });
}
async function removeValue() {
await data.remove();
}
</script>
<Card>
<CardHeader>
<CardTitle>Realtime Data: {path}</CardTitle>
</CardHeader>
<CardContent>
{#if loading}
<p>Loading...</p>
{:else if error}
<p class="text-red-600">Error: {error.message}</p>
{:else if value}
<pre>{JSON.stringify(value, null, 2)}</pre>
<div class="mt-4 flex gap-2">
<Button onclick={updateValue}>Update</Button>
<Button variant="destructive" onclick={removeValue}>Remove</Button>
</div>
{:else}
<p>No data</p>
{/if}
</CardContent>
</Card>
List Component
<script>
import { firekitRealtimeList } from 'svelte-firekit';
import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
export let path: string;
export let limit = 20;
let newItemText = '';
const list = firekitRealtimeList(path, { limit });
const items = $derived(list.list);
const loading = $derived(list.loading);
const error = $derived(list.error);
const hasMore = $derived(list.hasMore);
async function addItem() {
if (newItemText.trim()) {
await list.push({
text: newItemText,
timestamp: Date.now(),
userId: 'current-user'
});
newItemText = '';
}
}
async function removeItem(key: string) {
await list.remove(key);
}
async function loadMore() {
await list.loadMore();
}
</script>
<Card>
<CardHeader>
<CardTitle>Realtime List: {path}</CardTitle>
</CardHeader>
<CardContent>
<div class="space-y-4">
<!-- Add new item -->
<div class="flex gap-2">
<Input
bindvalue={newItemText}
placeholder="Enter new item..."
onkeydown={(e) => e.key === 'Enter' && addItem()}
/>
<Button onclick={addItem}>Add</Button>
</div>
<!-- List items -->
{#if loading}
<p>Loading...</p>
{:else if error}
<p class="text-red-600">Error: {error.message}</p>
{:else}
<div class="space-y-2">
{#each items as item, key}
<div class="flex items-center justify-between rounded border p-2">
<span>{item.text}</span>
<Button variant="destructive" size="sm" onclick={() => removeItem(key)}>
Remove
</Button>
</div>
{/each}
</div>
{#if hasMore}
<Button onclick={loadMore}>Load More</Button>
{/if}
{/if}
</div>
</CardContent>
</Card>
Real-time Chat Component
<script>
import { firekitRealtimeList } from 'svelte-firekit';
import { firekitUser } from 'svelte-firekit';
import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Avatar } from '$lib/components/ui/avatar';
export let chatId: string;
const user = $derived(firekitUser.user);
const messages = firekitRealtimeList<ChatMessage>(`chats/${chatId}/messages`, {
limit: 50,
orderBy: 'timestamp',
reverse: true
});
const messagesList = $derived(messages.list);
const loading = $derived(messages.loading);
let newMessage = '';
async function sendMessage() {
if (newMessage.trim() && user) {
await messages.push({
text: newMessage,
userId: user.uid,
userName: user.displayName || user.email,
userPhoto: user.photoURL,
timestamp: Date.now()
});
newMessage = '';
}
}
</script>
<Card class="flex h-96 flex-col">
<CardHeader>
<CardTitle>Chat Room</CardTitle>
</CardHeader>
<CardContent class="flex flex-1 flex-col">
<!-- Messages -->
<div class="mb-4 flex-1 space-y-2 overflow-y-auto">
{#if loading}
<p>Loading messages...</p>
{:else}
{#each messagesList as message, key}
<div
class="flex items-start gap-2 {message.userId === user?.uid ? 'flex-row-reverse' : ''}"
>
<Avatar src={message.userPhoto} alt={message.userName} />
<div class="flex flex-col {message.userId === user?.uid ? 'items-end' : 'items-start'}">
<div class="text-sm font-medium">{message.userName}</div>
<div class="max-w-xs rounded-lg bg-gray-100 p-2">
{message.text}
</div>
<div class="text-xs text-gray-500">
{new Date(message.timestamp).toLocaleTimeString()}
</div>
</div>
</div>
{/each}
{/if}
</div>
<!-- Message input -->
<div class="flex gap-2">
<Input
bindvalue={newMessage}
placeholder="Type a message..."
onkeydown={(e) => e.key === 'Enter' && sendMessage()}
/>
<Button onclick={sendMessage}>Send</Button>
</div>
</CardContent>
</Card>
Type Definitions
Single Value Types
interface RealtimeDBOptions<T> {
defaultValue?: T;
persist?: boolean;
offlineFirst?: boolean;
throttle?: number;
debounce?: number;
transform?: (data: T) => T;
conflictResolution?: 'server-wins' | 'client-wins' | 'merge';
mergeStrategy?: (serverData: T, clientData: T) => T;
}
interface RealtimeDBState<T> {
data: T | null;
loading: boolean;
error: Error | null;
ref: any;
isOnline: boolean;
pendingWrites: any[];
}
List Types
interface RealtimeListOptions<T> {
limit?: number;
orderBy?: string;
reverse?: boolean;
where?: Array<[string, string, any]>;
transform?: (item: T) => T;
virtualization?: boolean;
itemHeight?: number;
viewportHeight?: number;
}
interface RealtimeListState<T> {
list: T[];
loading: boolean;
error: Error | null;
count: number;
hasMore: boolean;
keys: string[];
}
Common Types
interface ChatMessage {
text: string;
userId: string;
userName: string;
userPhoto?: string;
timestamp: number;
}
interface UserProfile {
name: string;
email: string;
age: number;
createdAt: number;
lastUpdated: number;
}
interface UserStatus {
online: boolean;
lastSeen: number;
status: 'online' | 'away' | 'busy' | 'offline';
}
Best Practices
1. Use Appropriate Data Structure
// ✅ Good - Flat structure for simple data
const userStatus = firekitRealtimeDB('users/123/status');
// ✅ Good - Nested structure for complex data
const userProfile = firekitRealtimeDB('users/123/profile');
// ❌ Avoid - Deep nesting
const deepData = firekitRealtimeDB('users/123/profile/settings/notifications/email');
2. Handle Loading States
{#if loading}
<LoadingSpinner />
{:else if error}
<ErrorMessage {error} />
{:else if data}
<DataDisplay {data} />
{/if}
3. Optimize for Performance
// ✅ Good - Use limits for large lists
const messages = firekitRealtimeList('messages', { limit: 50 });
// ✅ Good - Use throttling for frequent updates
const userStatus = firekitRealtimeDB('users/123/status', null, {
throttle: 1000
});
// ❌ Avoid - Loading all data at once
const allMessages = firekitRealtimeList('messages');
4. Handle Offline Scenarios
// ✅ Good - Enable offline persistence
const userProfile = firekitRealtimeDB('users/123/profile', null, {
persist: true,
offlineFirst: true
});
// ✅ Good - Check connection status
const isOnline = $derived(userProfile.isOnline);
$effect(() => {
if (!isOnline) {
showOfflineIndicator();
}
});
5. Clean Up Subscriptions
import { onDestroy } from 'svelte';
const userProfile = firekitRealtimeDB('users/123/profile');
onDestroy(() => {
userProfile.dispose();
});
API Reference
RealtimeDB Methods
set(data)
- Set data at pathupdate(data)
- Update specific fieldsremove()
- Remove data at pathpush(data)
- Push data with auto-generated keytransaction(updater)
- Run transactiondispose()
- Clean up subscription
RealtimeList Methods
push(data)
- Add item to listset(key, data)
- Set item with specific keyupdate(key, data)
- Update specific itemremove(key)
- Remove specific itemclear()
- Clear all itemsloadMore()
- Load more itemsbatch(operations)
- Batch operationsdispose()
- Clean up subscription
Reactive State
data
- Current data valueloading
- Loading stateerror
- Error stateref
- Firebase referenceisOnline
- Connection statuspendingWrites
- Pending write operations
List State
list
- Array of itemscount
- Total item counthasMore
- Whether more items are availablekeys
- Array of item keys