API Reference
Classes
Cheat Sheet
Operators & filters
Addons, Adapters and Derived Work
Typescript
Knowledge Base
Quick Reference
Declare Database
var db = new Dexie("MyDatabase");
db.version(1).stores({
friends: "++id, name, age, *tags",
gameSessions: "id, score"
});
NOTE: Don’t declare all columns like in SQL. You only declare properties you want to index, that is properties you want to use in a where(…) query.
Schema Syntax
++ | Auto-incremented primary key |
& | Unique |
* | Multi-entry index |
[A+B] | Compound index |
Upgrade
db.version(1).stores({
friends: "++id,name,age,*tags",
gameSessions: "id,score"
});
db.version(2).stores({
friends: "++id, [firstName+lastName], yearOfBirth, *tags", // Change indexes
gameSessions: null // Delete table
}).upgrade(tx => {
// Will only be executed if a version below 2 was installed.
return tx.table("friends").modify(friend => {
friend.firstName = friend.name.split(' ')[0];
friend.lastName = friend.name.split(' ')[1];
friend.birthDate = new Date(new Date().getFullYear() - friend.age, 0);
delete friend.name;
delete friend.age;
});
});
Read more about database versioning
Class Binding
class Friend {
// Prototype method
save() {
return db.friends.put(this); // Will only save own props.
}
// Prototype property
get age() {
return moment(Date.now()).diff (this.birthDate, 'years');
}
}
db.friends.mapToClass(Friend);
Reference: Table.mapToClass()
Add Items
await db.friends.add({name: "Josephine", age: 21});
await db.friends.bulkAdd([
{name: "Foo", age: 31},
{name: "Bar", age: 32}
]);
Reference: Table.add() Table.bulkAdd()
Update Items
await db.friends.put({id: 4, name: "Foo", age: 33});
await db.friends.bulkPut([
{id: 4, name: "Foo2", age: 34},
{id: 5, name: "Bar2", age: 44}
]);
await db.friends.update(4, {name: "Bar"});
await db.customers
.where("age")
.inAnyRange([ [0, 18], [65, Infinity] ])
.modify({discount: 0.5});
Reference: Table.put(), Table.bulkPut(), Table.update(), Collection.modify()
Delete items
await db.friends.delete(4);
await db.friends.bulkDelete([1,2,4]);
const oneWeekAgo = new Date(Date.now() - 60*60*1000*24*7);
await db.logEntries
.where('timestamp').below(oneWeekAgo)
.delete();
Reference: Table.delete(), Table.bulkDelete(), Collection.delete()
Query Items
const someFriends = await db.friends
.where("age").between(20, 25)
.offset(150).limit(25)
.toArray();
await db.friends
.where("name").equalsIgnoreCase("josephine")
.each(friend => {
console.log("Found Josephine", friend);
});
const abcFriends = await db.friends
.where("name")
.startsWithAnyOfIgnoreCase(["a", "b", "c"])
.toArray();
References: Table.where(), WhereClause, Collection
await db.friends
.where('age')
.inAnyRange([[0,18], [65, Infinity]])
.modify({discount: 0.5});
References: Table.where(), WhereClause, Collection.modify()
const friendsContainingLetterA = await db.friends
.filter(friend => /a/i.test(friend.name))
.toArray();
Reference: Table.filter()
const forbundsKansler = await db.friends
.where('[firstName+lastName]')
.equals(["Angela", "Merkel"])
.first();
Read more about compound index
In Dexie 2.0, you could do the above query a little simpler:
const forbundsKansler = await db.friends.where({
firstName: "Angela",
lastName: "Merkel"
}).first();
Or simply:
const forbundsKansler = await db.friends.get({
firstName: "Angela",
lastName: "Merkel"
});
// This query is equal to:
// select * from friends where firstName='Angela' order by lastName
const angelasSortedByLastName = await db.friends
.where('[firstName+lastName]')
.between([["Angela", ""], ["Angela", "\uffff"])
.toArray()
await db.friends
.where('age').above(25)
.or('shoeSize').below(8)
.or('interests').anyOf('sports', 'pets', 'cars')
.modify(friend => friend.tags.push("marketing-target"));
Reference: Collection.or()
Retrieve TOP 5 items
const best5GameSession = await db.gameSessions
.orderBy("score").reverse()
.limit(5)
.toArray();
References: Table.orderBy(), Collection.reverse(), Collection.limit()
Joining
var db = new Dexie('music');
db.version(1).stores({
genres: '++id,name',
albums: '++id,name,year,*tracks',
bands: '++id,name,*albumIds,genreId'
});
async function getBandsStartingWithA () {
// Query
const bands = await db.bands
.where('name')
.startsWith('A')
.toArray();
// Attach resolved properies "genre" and "albums" on each band
// using parallel queries:
await Promise.all (bands.map (async band => {
[band.genre, band.albums] = await Promise.all([
db.genres.get (band.genreId),
db.albums.where('id').anyOf(band.albumIds).toArray()
]);
}));
return bands;
}
Storing Binary Data
var db = new Dexie("MyImgDb");
db.version(1).stores({
friends: "name"
});
// Download and store an image
async function downloadAndStoreImage() {
const res = await fetch("some-url-to-an-image.png");
const blob = await res.blob();
// Store the binary data in indexedDB:
await db.friends.put({
name: "David",
image: blob
});
}
Indexing Binary Data (IndexedDB 2.0)
IndexedDB 2.0 contains support for indexing binary data. This spec is supported by Chrome and Safari and partially Firefox (Firefox has a bug when using binary primary key, but works well with binary index).
var db = new Dexie("MyImgDb");
db.version(1).stores({
friends: "id, name" // use binary UUID as id
});
// IndexedDB 2.0 allows indexing ArrayBuffer and XXXArray
// (typed arrays) (but not Blobs)
async function playWithBinaryPrimKey() {
// Store the binary data in indexedDB:
await db.friends.put({
id: new Uint8Array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]),
name: "David"
});
// Retrieve by binary search
const friend = await db.friends.get(
new Uint8Array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]));
if (friend) {
console.log(`Found friend: ${friend.name}`);
} else {
console.log(`Friend not found`);
}
}
Transaction
await db.transaction('rw', [db.friends], async () => {
const friend = await db.friends.get(1);
++friend.age;
await db.friends.put(friend);
});
Ongoing Transaction
const db = new Dexie("MyImgDb");
db.version(1).stores({
friends: "id, name, *tags"
});
function goodFriends() {
return db.friends
.where('tags')
.equals('close-friend');
}
async function addComment(friendId, comment) {
await db.friends
.where('id')
.equals(friendId)
.modify(friend => {
friend.comments.push(comment);
});
}
async function spreadYourLove() {
// Make an atomic change:
await db.transaction('rw', db.friends, async () => {
const goodFriendKeys = await goodFriends().primaryKeys();
await Promise.all(
goodFriendKeys.map(id => addComment(id, "I like you!"))
);
});
}
The above code snippet shows that you can reuse “transactionless” code (function goodFriends() and addComment()) but execute it within a transaction (spreadYourLove()).
Reference: Dexie.transaction()
Parent Transaction
// Make one large atomic change that calls other
// functions that already use a transaction.
db.transaction('rw', db.friends, db.diary, async () => {
await spreadYourLove();
await db.diary.log({date: Date.now(), text: "Today I successfully spread my love"});
}).catch (err => {
console.error ("I failed to spread my love :( " + err.stack);
});
The above snippet shows that you can also reuse code that is indeed transaction-aware, but encapsulate several such functions in an overall umbrella-transaction.
NOTE: The code above may look like it could only execute this transaction one-at-a-time, but with thanks to zone technology, this code can work in parallel with other transactions. (Dexie implements its own zone system and is not dependent on zone.js)
Reference: Dexie.transaction()
Working with Asynchronic APIs
Dexie.js is an asynchronic API. In synchronic APIs, errors are normally handled using exceptions. This is very convenient because you code on without doing error checking everywhere and instead catch exceptions on a higher level. Asynchronic APIs normally use success- and error events to signal back when operation complete. Since indexedDB uses a combination of exceptions and error events to notify the caller, it is quite cumbersome to code against it using correct error handling - you need both to do try..catch and request.onerror for each and every request. Dexie.js solves this by working with ECMAScript6 compliant Promises making error handling as easy as it is on a synchronous API with try..catch error handling.
Working with Promises
Promise based APIs (such as Dexie.js) will look more like synchronous APIs than event based APIs, but instead of returning the result, it will return an instance of Promise. ECMAScript6 promises has two methods: then() and catch(). These methods expects a callback to call when the operation succeeds or fails respectively. All asynchronic methods in Dexie returns a Promise instance and this makes the API way more easy to use, as you will see in our examples.
Promise-Specific Data (zone)
Dexie Promises supports a pattern similar to Thread-local storage where it is possible to have static properties that is bound to the executing promise and all it’s child-promises. This is similar Angular’s zone.js but in an unobtrusive way (no requirement of including any monkey-patching script). Dexie.js and it’s transaction API heavily depends on it’s transaction zones since it enables code to be aware of the currently executing transaction without having to pass transaction objects around. Promise-Specific Data doc.
Exception Handling
With Dexie, in contrary to indexedDB, there is one single way to catch exceptions - through the Promise.catch() method. Nowhere do you need to do a standard try..catch(). The reason for this is to not enforce the caller to need to think about several ways of error handling. When you work with transactions, you will also get the benefit of being able to catch all errors in one single place - at the end of the transaction, instead of having to catch() every promise of each database operation. Any uncaught error (no matter error events, exception or miss-spelled variable in your code) will abort the ongoing Transaction and trigger its returned Promise to reject, waking up any catch() clause attached to the transaction scope.
db.transaction('rw', db.friends, function() {
...
window.MissSpelledVar.NonExistingVar = 3; // Will fail!
}).catch (function (err) {
// Transaction will abort!
console.error(err);
});
All transaction promises should either be caught or returned to its caller.
Catching means Handling!
If you catch a Promise from a database operation within a transaction, it will be considered to be handled and the transaction will not be aborted. This could be a common pitfall when people catch promises within transactions just to log it but expecting the transaction to abort. Solution: re-throw the errors that you don’t handle!
db.transaction('rw', db.friends, function() {
db.friends.add({id: 1, name: "Foo"}).catch(function(e) {
console.error("Failed to add Foo friend");
throw e; // Rethrow to abort transaction.
});
})
Working With Transactions
Whenever you want to do more than a single operation, you simplify your code by using transactions. By working with transactions, you get the following benefits:
- If modifying database and any error occur - error event or exception of any kind - then transaction will abort and every modification will be rolled back.
- No need to handle promises if you don’t like. Everything is encapsulated in the transaction so you can handle that instead.
- You may do all write operations synchronically without the need to wait for it to finish before starting the next one. (see the 2nd example code below).
- Even read-operations can be done the line after a write operations without waiting for write to finish - still your result will include all modifications. This is possible because all operations are queued when there is a pending write operation going on in current transaction.
- Not a single error will just slip away - you catch all in the final catch() method - both error events and ordinary exceptions of any kind.
Here is how you enter a transaction block:
db.transaction('rw', db.friends, db.pets, function () {
// Any database error event that occur will abort transaction and
// be sent to the catch() method below.
// The exact same rule if any exception is thrown what so ever.
return db.pets.add({name: 'Bamse', kind: 'cat'}).then(function (petId) {
return db.friends.add({name: 'Kate', age: 89, pets: [petId]});
});
}).catch(function (error) {
// Log or display the error
console.error (error.stack || error);
});
Notes:
- ‘friends’ and ‘pets’ are objectStores registered using Version.stores() method.
"rw"
should be replaced with"r"
if you are just going to do read operations.- Also errors from chained database operations within the transaction, or plain exceptions happening in any then() callback of any chained operation will be caught by the transaction’s catch() method.
- It is possible to prohibit the transaction from being aborted if a failing DB operation is caught explicitly:
db.transaction('rw', db.friends, function() {
db.friends.add({id:1, name:"Fredrik"});
db.friends.add({id:1, name:"Fredrik"}).catch(function (err) {
// Adding same primary key twice will of course fail. But
// since we catch this error explicitly, the transaction
// wont abort. This makes it possible to continue the
// transaction in a managed way. If you still want to abort
// the transaction, just do Dexie.currentTransaction.abort(),
// throw an exception, or just:
// return Promise.reject(err);
});
}).then (function () {
alert ("Transaction successfully completed");
});
When working with transactions, you may query a recent add(), put(), update(), delete() or modify() operation on the next line without waiting for it to finish. The waiting is taken care of by the framework. See the difference below on how that may simplify your code to work with a transaction and not having to call .then() all the time.
Code Example Without Transaction
db.friends.add({ name: "Ulla Bella", age: 87, isCloseFriend: 0 }).then(function () {
return db.friends.add({ name: "Elna", age: 99, isCloseFriend: 1 });
}).then(function (){
return db.friends.where("age").above(65).each(function (friend) {
console.log("Retired friend: " + friend.name);
});
}).catch(function (error) {
console.error(error);
})
Same Code Example With Transaction
db.transaction("rw", db.friends, function () {
db.friends.add({ name: "Ulla Bella", age: 87, isCloseFriend: 0 });
db.friends.add({ name: "Elna", age: 99, isCloseFriend: 1 });
db.friends.where("age").above(65).each(function (friend) {
console.log("Retired friend: " + friend.name);
});
}).catch(function (error) {
console.error(error);
});
The sample above shows that there’s no need to wait for the add() operations to finish when working within the same transaction.
The Auto-Commit Behavior of IndexedDB Transactions
IndexedDB will commit a transaction as soon as it isn’t used within the same task. This means that you MUST NOT call any other async API (at least not wait for it to finish) within a transaction scope. If you do, you will get a TransactionInactiveError thrown at you as soon as you try to continue using the transaction. This cannot be worked around by encapsulating the call with Dexie.Promise since it is a behaviour of IndexedDB.
News in Dexie 2.0.0-beta.6: You can now wait for other async APIs and still hold the transaction active, using Dexie.waitFor()
Nested IndexedDB Transactions
Since version 0.9.8, Dexie supports nested transactions:
db.transaction('rw', db.friends, db.pets, function () {
// MAIN transaction block
db.transaction('rw', db.pets, function () {
// SUB transaction block
});
});
The power with nested transactions is that functions that use a transaction can be reused by higher-level code that surrounds all of its calls into a bigger transaction.
See also Dexie.transaction() for more information on how to use transactions.
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 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()
- 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()