Overview
The Zero client library (@rocicorp/zero) provides the core functionality for building real-time, sync-enabled applications. Built on top of Replicache , Zero provides a powerful client-side data layer with automatic sync, optimistic updates, and reactive queries.
Installation
npm install @rocicorp/zero
Creating a Zero Client
The Zero class is the main entry point for the client library. It manages the connection to your Zero server, handles sync, and provides query and mutation APIs.
Basic Setup
import { Zero , createSchema , table , string } from '@rocicorp/zero' ;
const schema = createSchema ({
tables: [
table ( 'user' )
. columns ({
id: string (),
name: string (),
email: string (),
})
. primaryKey ( 'id' ),
],
});
const zero = new Zero ({
schema ,
server: 'https://your-zero-server.com' ,
userID: 'user-123' ,
});
Configuration Options
The Zero constructor accepts a ZeroOptions object:
Option Type Required Description schemaSchemaYes Schema definition for your data model server or cacheURLstringYes URL of your Zero cache server userIDstringYes Unique identifier for the current user authstring | () => Promise<string>No Authentication token or function returning token mutatorsCustomMutatorDefsNo Custom mutation functions contextobjectNo User context passed to mutations logLevel'debug' | 'info' | 'error'No Logging verbosity (default: ‘error’) kvStore'idb' | 'mem' | KVStoreNo Storage backend (default: ‘idb’ for IndexedDB) storageKeystringNo Custom storage key for multiple instances mutateURLstringNo Custom endpoint for mutations queryURLstringNo Custom endpoint for custom queries pingTimeoutMsnumberNo Ping timeout in milliseconds (default: 5000) hiddenTabDisconnectDelaynumberNo Time before disconnecting hidden tabs (default: 300000) onOnlineChange(online: boolean) => voidNo Callback when online status changes onUpdateNeeded(reason: UpdateNeededReason) => voidNo Callback when client needs to update
With Authentication
const zero = new Zero ({
schema ,
server: 'https://your-zero-server.com' ,
userID: 'user-123' ,
auth: 'your-auth-token' ,
// Or as a function for dynamic tokens
auth : async () => {
const response = await fetch ( '/api/auth/token' );
return response . text ();
},
});
Connection Management
Zero automatically manages the WebSocket connection to your server.
Connection State
// Get current connection state
const state = zero . connection . state . current ;
// Subscribe to connection state changes
const unsubscribe = zero . connection . state . subscribe (( newState ) => {
console . log ( 'Connection status:' , newState . status );
console . log ( 'Connected:' , newState . connected );
});
// Clean up subscription
unsubscribe ();
Connection Status Values
Connecting - Attempting to establish connection
Connected - Successfully connected and syncing
Error - Connection error occurred
Disconnected - Intentionally disconnected
Manual Connection Control
// Manually trigger connection
await zero . connection . connect ();
// Manually trigger connection with new auth
await zero . connection . connect ({ auth: 'new-token' });
// Disconnect
await zero . connection . disconnect ();
Online Status
// Check if client is online
if ( zero . online ) {
console . log ( 'Client is connected and syncing' );
}
// Subscribe to online/offline changes
const zero = new Zero ({
schema ,
server: 'https://your-zero-server.com' ,
userID: 'user-123' ,
onOnlineChange : ( online ) => {
if ( online ) {
console . log ( 'Client is now online' );
} else {
console . log ( 'Client is now offline' );
}
},
});
Query Builder
Zero provides a powerful query builder API for querying data from your schema.
import { createBuilder } from '@rocicorp/zero' ;
const zql = createBuilder ( schema );
// Simple query
const allUsers = zql . user ;
// Filter by condition
const activeUsers = zql . user . where ( 'status' , 'active' );
// Get a single record
const user = zql . user . where ( 'id' , 'user-123' ). one ();
// Complex queries with multiple conditions
const filteredUsers = zql . user
. where ( 'status' , 'active' )
. where ( 'role' , 'admin' )
. orderBy ( 'name' , 'asc' )
. limit ( 10 );
See Queries for detailed query API documentation.
Materializing Queries
Materialize a query to get reactive updates:
const view = zero . materialize ( zql . user . where ( 'status' , 'active' ));
// Listen for changes
view . addListener (( data , resultType ) => {
console . log ( 'Users:' , data );
console . log ( 'Result type:' , resultType ); // 'unknown', 'complete', or 'error'
});
// Clean up
view . destroy ();
Materialization Options
const view = zero . materialize ( query , {
ttl: '5m' , // Keep alive for 5 minutes after last subscriber
// ttl can be: 'forever', 'never', '30s', '5m', or milliseconds
});
Mutations
Zero provides multiple ways to mutate data:
CRUD Operations (Legacy)
// Insert
await zero . mutate . user . insert ({
id: 'user-123' ,
name: 'Alice' ,
email: '[email protected] ' ,
});
// Update
await zero . mutate . user . update ({
id: 'user-123' ,
name: 'Alice Smith' ,
});
// Upsert
await zero . mutate . user . upsert ({
id: 'user-123' ,
name: 'Alice' ,
email: '[email protected] ' ,
});
// Delete
await zero . mutate . user . delete ( 'user-123' );
Batch Mutations
await zero . mutateBatch ( async ( m ) => {
await m . user . insert ({ id: 'user-1' , name: 'Alice' });
await m . user . insert ({ id: 'user-2' , name: 'Bob' });
await m . post . insert ({ id: 'post-1' , title: 'Hello' });
});
Custom Mutations
Define custom business logic with mutation functions:
import { defineMutators } from '@rocicorp/zero' ;
const mutators = defineMutators ( schema , {
createUserWithProfile : async ( tx , args : { name : string ; bio : string }) => {
const userID = generateID ();
await tx . mutate . user . insert ({ id: userID , name: args . name });
await tx . mutate . profile . insert ({ userID , bio: args . bio });
},
});
const zero = new Zero ({
schema ,
server: 'https://your-zero-server.com' ,
userID: 'user-123' ,
mutators ,
});
// Use custom mutator
await zero . mutate . createUserWithProfile ({
name: 'Alice' ,
bio: 'Software engineer' ,
});
See Mutations for detailed mutation API documentation.
// Get client ID
console . log ( zero . clientID ); // Unique ID for this client instance
// Get user ID
console . log ( zero . userID ); // User ID passed in options
// Get version
console . log ( zero . version ); // Zero client version
Cleanup
Always close the Zero client when you’re done:
// Close the client and clean up resources
await zero . close ();
TypeScript Types
Zero is fully typed with TypeScript. Define default types for better type inference:
// Define default types module augmentation
declare module '@rocicorp/zero' {
interface DefaultTypes {
schema : typeof schema ;
context : { sub : string ; role : string };
}
}
// Now hooks and queries are fully typed
const [ users ] = useQuery ( zql . user );
// users is typed as User[]
Storage Options
Zero supports multiple storage backends:
IndexedDB (Default)
const zero = new Zero ({
schema ,
server: 'https://your-zero-server.com' ,
userID: 'user-123' ,
kvStore: 'idb' , // Default
});
In-Memory (Testing)
const zero = new Zero ({
schema ,
server: 'https://your-zero-server.com' ,
userID: 'user-123' ,
kvStore: 'mem' , // No persistence
});
Custom Storage
import { createCustomKVStore } from './my-storage' ;
const zero = new Zero ({
schema ,
server: 'https://your-zero-server.com' ,
userID: 'user-123' ,
kvStore: createCustomKVStore (),
});
Error Handling
try {
await zero . mutate . user . insert ({ id: 'user-123' , name: 'Alice' });
} catch ( error ) {
if ( error instanceof ApplicationError ) {
// Application-level error from server
console . error ( 'Application error:' , error . message );
} else {
// Network or other error
console . error ( 'Error:' , error );
}
}
Best Practices
Single Instance : Create one Zero instance per user session and reuse it throughout your application.
Clean Up Views : Always call view.destroy() when you’re done with materialized queries to prevent memory leaks.
Use Framework Hooks : For React, Solid, and React Native, use the framework-specific hooks instead of calling materialize directly.
Don’t Block Renders : Avoid creating Zero instances or materializing queries during component render. Use effects or providers instead.
Next Steps
Queries Learn the query API for fetching and filtering data
Mutations Explore mutation patterns for data changes
React Integration Use Zero with React hooks
Solid Integration Use Zero with Solid.js