A Vite/React demonstration app showcasing SQLite Sync implementation for offline-first data synchronization across multiple devices. This example illustrates how to integrate SQLite AI's sync capabilities into modern web applications with proper authentication via Access Token and Row-Level Security (RLS).
From a user experience perspective, this is a simple sport tracking application where users can:
- Create accounts and log activities (running, cycling, swimming, etc.)
- View personal statistics and workout history
- Access "Coach Mode" for managing multiple users' workouts
From a developer perspective, this app showcases:
- Offline-first architecture with sync to the remote database using SQLite Sync extension for SQLite
- Row-Level Security (RLS) implementation for data isolation and access control on the SQLite Cloud database
- Access Tokens for secure user authentication with SQLite Sync and RLS policy enforcement
- Multi-user data isolation and sharing patterns across different user sessions
- Node.js 20.x or >=22.12.0
- SQLite Cloud account
- Create database in SQLite Cloud Dashboard.
- Execute the exact schema from
sport-tracker-schema.sql. - Enable OffSync for all tables on the remote database from the SQLite Cloud Dashboard -> Databases.
- Enable and configure RLS policies on the SQLite Cloud Dashboard -> Databases. See the file
rls-policies.md.
Rename the .env.example into .env and fill with your values.
npm install
npm run devThis app uses the packed WASM version of SQLite with the SQLite Sync extension enabled.
This walkthrough demonstrates how SQLite Sync handles offline-first synchronization between multiple users:
-
Bob starts tracking offline 📱
- Open localhost:5173 in your browser
- Create user
boband add some activities - Notice Bob's data is stored locally - no internet required!
-
Bob goes online and syncs 🌐
- Click
SQLite Syncto authenticate SQLite Sync - Click
Sync & Refresh- this generates an Access Token and synchronizes Bob's local data to the cloud - Bob's activities are now replicated in the cloud
- Click
-
Coach Sarah joins from another device 👩💼
- Open a new private/incognito browser window at localhost:5173
- Create user
coach(this triggers special coach privileges via RLS) - Enable
SQLite Syncand clickSync & Refresh. Coach can now see Bob's synced activities thanks to RLS policies
-
Coach creates a workout for Bob 💪
- Coach creates a workout assigned to Bob
- Click
Sync & Refreshto upload the workout to the cloud
-
Bob receives his workout 📲
- Go back to Bob's browser window
- Click
Sync & Refresh- Bob's local database downloads the new workout from Coach - Bob can now see his personalized workout
-
Bob gets a new device 📱➡️💻
- Log out Bob, then select it and click
Restore from cloud - This simulates Bob logging in from a completely new device with no local data
- Enable
SQLite Syncand sync - all of Bob's activities and workouts are restored from the cloud
- Log out Bob, then select it and click
Key takeaway: Users can work offline, sync when convenient, and seamlessly restore data on new devices!
// database.ts - Initialize sync for each table
export class Database {
async initSync() {
await this.exec('SELECT cloudsync_init("users")');
await this.exec('SELECT cloudsync_init("activities")');
await this.exec('SELECT cloudsync_init("workouts")');
}
}// SQLiteSync.ts - Access token handling
private async getValidToken(userId: string, name: string): Promise<string> {
const storedTokenData = localStorage.getItem('token');
if (storedTokenData) {
const parsed: TokenData = JSON.parse(storedTokenData);
const tokenExpiry = new Date(parsed.expiresAt);
if (tokenExpiry > new Date()) {
return parsed.token; // Use cached token
}
}
// Fetch new token from API
const tokenData = await this.fetchNewToken(userId, name);
localStorage.setItem('token', JSON.stringify(tokenData));
return tokenData.token;
}Then authorize SQLite Sync with the token. This operation is executed again when tokens expire and a new one is provided.
async sqliteSyncSetToken(token: string) {
await this.exec(`SELECT cloudsync_network_set_token('${token}')`);
}The sync operation sends local changes to the cloud and receives remote changes:
async sqliteSyncNetworkSync() {
await this.exec('SELECT cloudsync_network_sync()');
}This app demonstrates Row-Level Security configured in the SQLite Cloud Dashboard. RLS policies ensure:
- Users can only see their own activities and workouts
- Coaches can access all users' data and create workouts for the users
- Data isolation is enforced at the database level
-- Policy for selecting activities
auth_userid() = user_id OR json_extract(auth_json(), '$.name') = 'coach'
-- Policy for inserting into workouts table
json_extract(auth_json(), '$.name') = 'coach'Note: Configure RLS policies in your SQLite Cloud Dashboard under Databases → RLS
- Never expose API keys in client code
- Use server-side generation for Access Tokens
- Implement a proper authentication flow
Explore the code and learn more:
- SQLite Sync API: sqlite-sync
- Access Tokens Guide: SQLite Cloud Access Tokens
- Row-Level Security: SQLite Cloud RLS
The database is persisted in the Origin-Private FileSystem OPFS (if available) but performance is much lower. Read more here