Get started with Dexie Cloud
1. Bootstrapping
No matter if you create a brand new app or adjust an existing one, this tutorial will guide you through the steps.
You can use whatever framework you prefer but in this tutorial weāll be showing some sample components in React, so if you start on an empty paper, Iād recommend using vite to bootstrap a react app:
npm create vite@latest my-app -- --template react-ts
Make sure to have dexie-related dependencies installed:
npm install dexie
npm install dexie-cloud-addon
npm install dexie-react-hooks # If using react
2. Declare a db
Unless you already use Dexie (in which case you could just adjust it), create a new module db.ts
where you declare the database.
If migrating from vanilla Dexie.js to Dexie Cloud, make sure to remove any auto-incrementing keys (such as ++id
- replace with @id
or just id
) as primary keys has to be globally unique strings in Dexie Cloud.
// db.ts
import { Dexie } from 'dexie';
import dexieCloud from 'dexie-cloud-addon';
export const db = new Dexie('mydb', { addons: [dexieCloud] });
db.version(1).stores({
items: 'itemId',
animals: `
@animalId,
name,
age,
[name+age]`
});
In this example we use the property itemId
as primary key for items
and animalId
for animals
.
Notice the @
in @animalId
. This makes it auto-generated and is totally optional but can be handy since it makes it easier to add new objects to the table.
Note that animals
also declares some secondary indices name
, age
and a compound index of the combination of these. These indices are just to examplify. For this tutorial, we only need the ānameā index. A rule of thumb here is to only declare secondary index if needed in a where- or orderBy expression. And donāt worry - you can add or remove indices later
3. Make it Typing-Friendly
// Item.ts
export interface Item {
itemId: string;
name: string;
description: string;
}
// Animal.ts
export interface Animal {
animalId: string;
name: string;
age: number;
}
Then adjust the db.ts
module weāve already created so that it looks something like this:
// db.ts
import dexieCloud, { type DexieCloudTable } from 'dexie-cloud-addon';
import type { Item } from './Item.ts';
import type { Animal } from './Animal.ts';
export const db = new Dexie('mydb', { addons: [dexieCloud] }) as Dexie & {
items: DexieCloudTable<Item, 'itemId'>;
animals: DexieCloudTable<Animal, 'animalId'>;
};
db.version(1).stores({
items: 'itemId',
animals: `
@animalId,
name,
age,
[name+age]`
});
Weāre actually just casting our Dexie to force the typings to reflect the items
and animals
tables that we are declaring in db.version(1).stores(ā¦).
_Thereās also the option to declare the entities as classes instead of interfaces. See TodoList.ts, TodoDB.ts and db.ts in the dexie-cloud-todo-list example. If you find that way more appealing, thatās also ok.
4. Start Playing with it
Create some components that renders and manipulates the database. In this example, we use React + Typescript that demonstrate basic CRUD with a Dexie Cloud animals
table.
// components/App.tsx
import React from 'react';
import CreateAnimal from './CreateAnimal';
import AnimalList from './AnimalList';
export default function App() {
return (
<>
<style>
div.animal { display: 'flex', align-items: 'center', gap: 8 }
div.create-form { display: 'flex', gap: 8, margin-bottom: 12 }
</style>
<div>
<h1>Animals</h1>
<CreateAnimal />
<AnimalList />
</div>
</>
);
}
App: top-level component that renders CreateAnimal
and AnimalList
.
// components/AnimalList.tsx
import React from 'react';
import { useLiveQuery } from 'dexie-react-hooks';
import { db } from '../db';
import AnimalView from './AnimalView';
import type { Animal } from '../Animal';
export default function AnimalList() {
const animals = useLiveQuery(() => db.animals.orderBy('name').toArray(), []);
if (!animals) return <div>Loadingā¦</div>;
return (
<ul>
{animals.map((a: Animal) => (
<li key={a.animalId}>
<AnimalView animal={a} />
</li>
))}
</ul>
);
}
AnimalList: lists animals using useLiveQuery
(live updates) and renders AnimalView
for each.
// components/AnimalView.tsx
import React from 'react';
import { db } from '../db';
import type { Animal } from '../Animal';
export default function AnimalView({ animal }: { animal: Animal }) {
const onDelete = async () => {
await db.animals.delete(animal.animalId);
};
return (
<div className="animal">
<div>
<strong>{animal.name}</strong> ā {animal.age} yrs
</div>
<button aria-label="Delete" onClick={onDelete} title="Delete">
šļø
</button>
</div>
);
}
AnimalView: shows name
and age
and a delete button that removes the item from the table.
// components/CreateAnimal.tsx
import React, { useState } from 'react';
import { db } from '../db';
export default function CreateAnimal() {
const [name, setName] = useState('');
const [age, setAge] = useState<number | ''>('');
const onSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!name || age === '') return;
await db.animals.add({ name, age: Number(age) });
setName('');
setAge('');
};
return (
<form onSubmit={onSubmit} className="create-form">
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<input
type="number"
value={age}
onChange={(e) => setAge(e.target.value ? Number(e.target.value) : '')}
placeholder="Age"
/>
<button type="submit">Add</button>
</form>
);
}
CreateAnimal: small form that adds a new animal to db.animals
(the table uses an auto-generated @animalId
).
Start the app and browse to it. Add and delete animals - see the app work with a local database only.
5. Make it Sync
Still, we havenāt connected Dexie Cloud in the picture. Everything is happening locally so far. Yes, weāve prepared the code but we havenāt yet connected it to a cloud database.
-
Create a database in the cloud
npx dexie-cloud create
This will produde two local files:
dexie-cloud.json
anddexie-cloud.key
. Make sure to .gitignore them:echo "dexie-cloud.json" >> .gitignore echo "dexie-cloud.key" >> .gitignore
-
White-list application URL (such as http://localhost:3000)
npx dexie-cloud whitelist http://localhost:3000 # assuming port 3000 # ...Dont forget (at a later stage) to also white-list public URLs: npx dexie-cloud whitelist https://mygreatapp02240s.azurewebsites.net
-
Pick the
dbUrl
from your localdexie-cloud.json
file and configure the database indb.ts
// db.ts ... db.cloud.configure({ databaseUrl: "<dbUrl>", })
-
Add a Login button to your App.tsx:
<button onClick={() => db.cloud.login()}>Login</button>
-
Now, launch the app and navigate a browser to it
6. Learn about Access Control and Sharing (optional)
By default, all data being created will remain private to the end user, even though kept in sync with the cloud. Learn more how you can create realms, roles, members and permissions to invite a group of users to a commonly shared realm of data.
See Access Control in Dexie Cloud
7. Use Dexie Cloud Manager (optional)
Login to Dexie Cloud Manager to manage:
- end-users seats
- end-user evaluation policy
- SMTP settings
- Free / paid subscription
8. Use dexie-cloud
CLI
The CLI can be used to switch between databases, export, import, authorize colleguaes. See all commands in the CLI docs.
8. Customize Authentication (optional)
Choose between:
- Keep the default authentication but customize the GUI
- Replace authentication in its whole with a custom solution
9. Customize Email Templates
Email templates for outgoing emails can be customized using the npx dexie-cloud templates command.
10. FAQ
What happens when clicking login button?
The default authentication dialog (which is customizable) will ask for an email address for one-time password (OTP) authentication and prompt for the OTP. If this was the first time of login, your user will be registered in the database - otherwise it acts as a normal login. Once logged in / registered - the local database will be in sync with your account on your dexie-cloud database.
- You get prompted for email
- You get prompted for OTP
- You enter OTP
- You get logged in
- All local data is uploaded to cloud and cloud data is downloaded
- Now the local and remote databases are connected in real time.
The login flow typically happens once per end user and device. Itās a part of the setup process for your application. Users can logout but if not, their device will be persistently logged in for as long as the local database lives.
Can I force a login + initial sync before any data is accessed?
Yes, a requireAuth property can be passed to db.cloud.configure(). This will block an query until a user is logged in and has completed an initial sync flow. Itās also possible to force a login as a specified email or userId and even to provide an OTP token this way (for example read from the query if the a magic link was sent).
Is it possible to Logout?
Yes, but local first apps are normally intended to have long or even eternal login sessions. A logout from a local first app is similar to erasing the local database.
A logout button can be added that calls db.cloud.logout()
when clicked.
What is dexie-cloud.key
good for?
This file is needed when you use the CLI (npx dexie-cloud
) to whitelist, export, import etc. Itās not needed for web applications as it is authorized using the npx dexie-cloud whitelist
command instead. The clientId and clientSecret is also needed when using the the REST API.
Why should dexie-cloud.json
and dexie-cloud.key
be .gitignored?
Keys shall never be committed to git (dexie-cloud.key
). dexie-cloud.json
does not contain any sensitive data but is still not tied to your code base - some other person might want to run the app on another database.
Table of Contents
- API Reference
- Access Control in Dexie Cloud
- Add demo users
- Add public data
- Authentication in Dexie Cloud
- Best Practices
- Building Addons
- Collection
- Collection.and()
- Collection.clone()
- Collection.count()
- Collection.delete()
- Collection.desc()
- Collection.distinct()
- Collection.each()
- Collection.eachKey()
- Collection.eachPrimaryKey()
- Collection.eachUniqueKey()
- Collection.filter()
- Collection.first()
- Collection.keys()
- Collection.last()
- Collection.limit()
- Collection.modify()
- Collection.offset()
- Collection.or()
- Collection.primaryKeys()
- Collection.raw()
- Collection.reverse()
- Collection.sortBy()
- Collection.toArray()
- Collection.uniqueKeys()
- Collection.until()
- Compound Index
- Consistency in Dexie Cloud
- Consistent add() operator
- Consistent remove() operator
- Consistent replacePrefix() operator
- Consuming Dexie as a module
- Custom Emails in Dexie Cloud
- DBCore
- DBCoreAddRequest
- DBCoreCountRequest
- DBCoreCursor
- DBCoreDeleteRangeRequest
- DBCoreDeleteRequest
- DBCoreGetManyRequest
- DBCoreGetRequest
- DBCoreIndex
- DBCoreKeyRange
- DBCoreMutateRequest
- DBCoreMutateResponse
- DBCoreOpenCursorRequest
- DBCorePutRequest
- DBCoreQuery
- DBCoreQueryRequest
- DBCoreQueryResponse
- DBCoreRangeType
- DBCoreSchema
- DBCoreTable
- DBCoreTableSchema
- DBCoreTransaction
- DBCoreTransactionMode
- DBPermissionSet
- Deprecations
- Derived Work
- Design
- Dexie Cloud API
- Dexie Cloud API Limits
- Dexie Cloud Best Practices
- Dexie Cloud CLI
- Dexie Cloud Docs
- Dexie Cloud REST API
- Dexie Cloud Web Hooks
- Dexie Constructor
- Dexie.AbortError
- Dexie.BulkError
- Dexie.ConstraintError
- Dexie.DataCloneError
- Dexie.DataError
- Dexie.DatabaseClosedError
- Dexie.IncompatiblePromiseError
- Dexie.InternalError
- Dexie.InvalidAccessError
- Dexie.InvalidArgumentError
- Dexie.InvalidStateError
- Dexie.InvalidTableError
- Dexie.MissingAPIError
- Dexie.ModifyError
- Dexie.NoSuchDatabaseErrorError
- Dexie.NotFoundError
- Dexie.Observable
- Dexie.Observable.DatabaseChange
- Dexie.OpenFailedError
- Dexie.PrematureCommitError
- Dexie.QuotaExceededError
- Dexie.ReadOnlyError
- Dexie.SchemaError
- Dexie.SubTransactionError
- Dexie.Syncable
- Dexie.Syncable.IDatabaseChange
- Dexie.Syncable.IPersistentContext
- Dexie.Syncable.ISyncProtocol
- Dexie.Syncable.StatusTexts
- Dexie.Syncable.Statuses
- Dexie.Syncable.registerSyncProtocol()
- Dexie.TimeoutError
- Dexie.TransactionInactiveError
- Dexie.UnknownError
- Dexie.UnsupportedError
- Dexie.UpgradeError()
- Dexie.VersionChangeError
- Dexie.VersionError
- Dexie.[table]
- Dexie.addons
- Dexie.async()
- Dexie.backendDB()
- Dexie.close()
- Dexie.currentTransaction
- Dexie.debug
- Dexie.deepClone()
- Dexie.defineClass()
- Dexie.delByKeyPath()
- Dexie.delete()
- Dexie.derive()
- Dexie.events()
- Dexie.exists()
- Dexie.extend()
- Dexie.fakeAutoComplete()
- Dexie.getByKeyPath()
- Dexie.getDatabaseNames()
- Dexie.hasFailed()
- Dexie.ignoreTransaction()
- Dexie.isOpen()
- Dexie.js
- Dexie.name
- Dexie.on()
- Dexie.on.blocked
- Dexie.on.close
- Dexie.on.error
- Dexie.on.populate
- Dexie.on.populate-(old-version)
- Dexie.on.ready
- Dexie.on.storagemutated
- Dexie.on.versionchange
- Dexie.open()
- Dexie.override()
- Dexie.semVer
- Dexie.setByKeyPath()
- Dexie.shallowClone()
- Dexie.spawn()
- Dexie.table()
- Dexie.tables
- Dexie.transaction()
- Dexie.transaction()-(old-version)
- Dexie.use()
- Dexie.verno
- Dexie.version
- Dexie.version()
- Dexie.vip()
- Dexie.waitFor()
- DexieCloudOptions
- DexieError
- Docs Home
- Download
- EntityTable
- Export and Import Database
- Get started with Dexie Cloud
- Get started with Dexie in Angular
- Get started with Dexie in React
- Get started with Dexie in Svelte
- Get started with Dexie in Vue
- Hello World
- How To Use the StorageManager API
- Inbound
- IndexSpec
- Indexable Type
- IndexedDB on Safari
- Invite
- Member
- Migrating existing DB to Dexie
- MultiEntry Index
- PersistedSyncState
- Privacy Policy
- Promise
- Promise.PSD
- Promise.catch()
- Promise.finally()
- Promise.on.error
- Promise.onuncatched
- Questions and Answers
- Realm
- Releasing Dexie
- Road Map
- Road Map: Dexie 5.0
- Road Map: Dexie Cloud
- Role
- Run Dexie Cloud on Own Servers
- Sharding and Scalability
- Simplify with yield
- Support Ukraine
- SyncState
- Table
- Table Schema
- Table.add()
- Table.bulkAdd()
- Table.bulkDelete()
- Table.bulkGet()
- Table.bulkPut()
- Table.bulkUpdate()
- Table.clear()
- Table.count()
- Table.defineClass()
- Table.delete()
- Table.each()
- Table.filter()
- Table.get()
- Table.hook('creating')
- Table.hook('deleting')
- Table.hook('reading')
- Table.hook('updating')
- Table.limit()
- Table.mapToClass()
- Table.name
- Table.offset()
- Table.orderBy()
- Table.put()
- Table.reverse()
- Table.schema
- Table.toArray()
- Table.toCollection()
- Table.update()
- Table.where()
- The main limitations of IndexedDB
- Transaction
- Transaction.abort()
- Transaction.on.abort
- Transaction.on.complete
- Transaction.on.error
- Transaction.table()
- Tutorial
- Typescript
- Typescript (old)
- Understanding the basics
- UserLogin
- Version
- Version.stores()
- Version.upgrade()
- WhereClause
- WhereClause.above()
- WhereClause.aboveOrEqual()
- WhereClause.anyOf()
- WhereClause.anyOfIgnoreCase()
- WhereClause.below()
- WhereClause.belowOrEqual()
- WhereClause.between()
- WhereClause.equals()
- WhereClause.equalsIgnoreCase()
- WhereClause.inAnyRange()
- WhereClause.noneOf()
- WhereClause.notEqual()
- WhereClause.startsWith()
- WhereClause.startsWithAnyOf()
- WhereClause.startsWithAnyOfIgnoreCase()
- WhereClause.startsWithIgnoreCase()
- Y.js
- db.cloud.configure()
- db.cloud.currentUser
- db.cloud.currentUserId
- db.cloud.events.syncComplete
- db.cloud.invites
- db.cloud.login()
- db.cloud.logout()
- db.cloud.options
- db.cloud.permissions()
- db.cloud.persistedSyncState
- db.cloud.roles
- db.cloud.schema
- db.cloud.sync()
- db.cloud.syncState
- db.cloud.userInteraction
- db.cloud.usingServiceWorker
- db.cloud.version
- db.cloud.webSocketStatus
- db.members
- db.realms
- db.roles
- db.syncable.connect()
- db.syncable.delete()
- db.syncable.disconnect()
- db.syncable.getOptions()
- db.syncable.getStatus()
- db.syncable.list()
- db.syncable.on('statusChanged')
- db.syncable.setFilter()
- dexie-cloud-addon
- dexie-react-hooks
- liveQuery()
- unhandledrejection-event
- useLiveQuery()
- useObservable()
- usePermissions()
- y-dexie