RTDB (Real-Time Database)
Real-Time Database
The Cloudillo RTDB provides a Firebase-like real-time database with TypeScript support.
Installation
pnpm add @cloudillo/rtdbQuick Start
import * as cloudillo from '@cloudillo/base'
import { RtdbClient } from '@cloudillo/rtdb'
// Initialize Cloudillo
await cloudillo.init('my-app')
// Create RTDB client
const rtdb = new RtdbClient({
fileId: 'my-database-file-id',
token: cloudillo.accessToken,
url: 'wss://your-server.com/ws/rtdb'
})
// Get collection reference
const todos = rtdb.collection('todos')
// Create document
await todos.create({
title: 'Learn Cloudillo RTDB',
completed: false,
createdAt: Date.now()
})
// Subscribe to changes
todos.onSnapshot((snapshot) => {
console.log('Todos:', snapshot)
})Core Concepts
Collections
Collections are groups of documents, similar to tables in SQL.
const users = rtdb.collection('users')
const posts = rtdb.collection('posts')
const comments = rtdb.collection('comments')Documents
Documents are individual records with unique IDs.
// Create with auto-generated ID
const doc = await users.create({
name: 'Alice',
email: 'alice@example.com'
})
console.log(doc.id) // Auto-generated ID
// Create with custom ID
await users.doc('user_alice').set({
name: 'Alice',
email: 'alice@example.com'
})CRUD Operations
Create
// Auto-generated ID
const newDoc = await todos.create({
title: 'New task',
completed: false
})
// Custom ID
await todos.doc('task_123').set({
title: 'Specific task',
completed: false
})Read
// Get single document
const doc = await todos.doc('task_123').get()
console.log(doc.data())
// Get all documents
const allTodos = await todos.get()
// Get with query
const incompleteTodos = await todos
.where('completed', '==', false)
.get()Update
// Partial update
await todos.doc('task_123').update({
completed: true
})
// Full replacement
await todos.doc('task_123').set({
title: 'Updated title',
completed: true,
updatedAt: Date.now()
})Delete
await todos.doc('task_123').delete()Queries
where(field, operator, value)
Filter documents by field values.
Operators:
==- Equal!=- Not equal>- Greater than>=- Greater than or equal<- Less than<=- Less than or equalin- Value in arraycontains- Array contains value
// Equal
const activeTodos = await todos
.where('completed', '==', false)
.get()
// Greater than
const recentTodos = await todos
.where('createdAt', '>', Date.now() - 86400000)
.get()
// In array
const priorityTodos = await todos
.where('priority', 'in', ['high', 'urgent'])
.get()
// Array contains
const taggedTodos = await todos
.where('tags', 'contains', 'work')
.get()orderBy(field, direction)
Sort results by field.
// Ascending (default)
const oldestFirst = await todos
.orderBy('createdAt', 'asc')
.get()
// Descending
const newestFirst = await todos
.orderBy('createdAt', 'desc')
.get()
// Multiple sorts
const sorted = await todos
.orderBy('priority', 'desc')
.orderBy('createdAt', 'asc')
.get()limit(n)
Limit number of results.
const first10 = await todos
.limit(10)
.get()offset(n)
Skip first n results (for pagination).
const page2 = await todos
.orderBy('createdAt', 'desc')
.limit(20)
.offset(20)
.get()Complex Queries
Chain multiple query methods:
const results = await todos
.where('completed', '==', false)
.where('priority', '==', 'high')
.orderBy('createdAt', 'desc')
.limit(10)
.get()Real-Time Subscriptions
onSnapshot(callback)
Subscribe to real-time updates.
// Subscribe to all documents
const unsubscribe = todos.onSnapshot((snapshot) => {
console.log('Current todos:', snapshot)
snapshot.forEach(doc => {
console.log(doc.id, doc.data())
})
})
// Unsubscribe later
unsubscribe()Query Subscriptions
Subscribe to filtered results:
const unsubscribe = todos
.where('completed', '==', false)
.orderBy('createdAt', 'desc')
.onSnapshot((snapshot) => {
console.log('Incomplete todos:', snapshot)
})Document Subscriptions
Subscribe to a single document:
const unsubscribe = todos.doc('task_123').onSnapshot((doc) => {
console.log('Task updated:', doc.data())
})Batch Operations
Perform multiple operations atomically.
const batch = rtdb.batch()
// Create
batch.create(todos, {
title: 'Task 1',
completed: false
})
// Update
batch.update(todos.doc('task_123'), {
completed: true
})
// Delete
batch.delete(todos.doc('task_456'))
// Commit all operations
await batch.commit()Computed Values
Use special operators for server-side computation.
Increment
await todos.doc('task_123').update({
views: { $op: 'increment', $value: 1 }
})Timestamp
await todos.doc('task_123').update({
updatedAt: { $fn: 'now' }
})Query Reference
await todos.create({
title: 'Assigned task',
assignee: {
$query: {
collection: 'users',
where: [['email', '==', 'alice@example.com']],
limit: 1
}
}
})TypeScript Support
Full type safety with generics:
interface Todo {
id?: string
title: string
completed: boolean
createdAt: number
tags?: string[]
}
const todos = rtdb.collection<Todo>('todos')
// TypeScript knows the shape
const todo = await todos.doc('task_123').get()
console.log(todo.data().title) // ✅ Typed as string
console.log(todo.data().invalid) // ❌ TypeScript error
React Integration
import { useEffect, useState } from 'react'
import { useApi } from '@cloudillo/react'
import { RtdbClient } from '@cloudillo/rtdb'
function TodoList() {
const api = useApi()
const [todos, setTodos] = useState([])
useEffect(() => {
const rtdb = new RtdbClient({
fileId: 'my-todos',
token: api.token,
url: 'wss://server.com/ws/rtdb'
})
const unsubscribe = rtdb.collection('todos')
.where('completed', '==', false)
.orderBy('createdAt', 'desc')
.onSnapshot(setTodos)
return () => unsubscribe()
}, [api])
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}Best Practices
1. Use Subscriptions Wisely
// ✅ Subscribe once per component
useEffect(() => {
const unsubscribe = todos.onSnapshot(setData)
return () => unsubscribe()
}, [])
// ❌ Don't create subscriptions in render
function TodoList() {
todos.onSnapshot(setData) // Creates new subscription on every render!
}2. Clean Up Subscriptions
// Always unsubscribe to prevent memory leaks
const unsubscribe = todos.onSnapshot(callback)
// Later or on component unmount
unsubscribe()3. Use Batch for Multiple Operations
// ✅ Atomic batch operation
const batch = rtdb.batch()
batch.create(todos, { title: 'Task 1' })
batch.create(todos, { title: 'Task 2' })
await batch.commit()
// ❌ Multiple separate requests
await todos.create({ title: 'Task 1' })
await todos.create({ title: 'Task 2' })4. Optimize Queries
// ✅ Specific query
const recent = await todos
.where('createdAt', '>', yesterday)
.limit(20)
.get()
// ❌ Fetch all then filter in code
const all = await todos.get()
const recent = all.filter(t => t.createdAt > yesterday).slice(0, 20)See Also
- Getting Started - Initialize Cloudillo
- WebSocket API - RTDB protocol
- Files API - Create RTDB files