Understanding the basics
Creating Database vs opening existing
IndexedDB (and Dexie) has a built-in system for db installation and schema upgrades. Many people think they will have to check if the database needs to be created, and then run different code depending on whether or not the database was installed on the client. This is not needed. Your Dexie code can be declarative (you declare what tables and schemas you have in current and previously released versions and you let Dexie / indexedDB handle the situation where a database wasn’t created yet, needs upgrading, or is already on latest version).
IndexedDB is designed for handling database creation and upgrades through the onupgradeneeded
event, and defines the schema there. There is no native method for checking whether or not a database exists. Dexie adds a declarative schema syntax on top of that so that you don’t need to subscribe to the onupgradeneeded
event either.
Declarative Schema
The database schema is declarative, not imperative.
var db = new Dexie('dbname');
db.version(1).stores({
friends: 'id, name, age'
});
db.open().then(function (db) {
// Database opened successfully
}).catch (function (err) {
// Error occurred
});
Primary and secondary indexes
When declaring friends: 'name, age'
the first property implicitly becomes the primary index (also called primary key). This declaration states that we want a table called ‘friends’ where the property ‘name’ is the primary key, and where we add a secondary index on ‘age’. A primary key is a unique key, meaning you can never store two objects with the same primary key in the same table. For this reason, most examples you’ll see will use a property ‘id’ or ‘key’ as primary key instead. You will also see examples using a ++
before the primary index, which makes it auto-incremented. You may also see examples with a &
before a secondary index, making that index unique the same way as primary indexes are implicitly unique.
Understanding the flow
First time
The first time a browser hits the appdb.js code, the following happens:
- The database is created
- The populate event is triggered to allow the developer to populate the database
- The db.open() promise resolves
Second time
- The database is simply opened and the promise resolves.
Modify Schema
When you need to modify the schema, also update the version number. In the sample below, we remove the “name” index and add two new indexes “firstName”, “lastName” to the friends table. We also change the version number from 1 to 2 in order for this change to have effect:
db.version(2).stores({
friends: 'id, firstName, lastName, age'
});
In older version of Dexie (version <3.0), you were required to keep all previous version declarations alongside the new one:
db.version(1).stores({
friends: 'id, name, age'
});
db.version(2).stores({
friends: 'id, name, firstName, lastName',
});
Migrate Data
When migrating existing data, you need to keep the old version alongside the new one and attach an upgrade function to the new version.
db.version(1).stores({
friends: 'id, name, age'
});
db.version(2).stores({
friends: 'id, name, firstName, lastName',
}).upgrade(tx => {
return tx.table("friends").toCollection().modify(friend => {
const names = friend.name.split(' ');
friend.firstName = names.shift();
friend.lastName = names.join(' ');
delete friend.name;
});
});
Changing a few tables only
If you are just adding or changing a few tables, you do not need to repeat the schemas of all the old tables if those tables are already present in an older version being kept.
db.version(2).stores({
friends: 'id, name, age, firstName, lastName',
});
db.version(3).stores({
pets: 'petId, petName' // Only need to specify pets, as friends should be same as for version 2.
});
Deleting tables
Since it is not mandatory to repeat old table definitions, Dexie has to be explicitly informed about table deletion. This is done by specifying null as the table schema.
db.version(1).stores({
friends: 'id, name, age'
});
db.version(2).stores({
friends: 'id, name, age, firstName, lastName',
});
db.version(3).stores({
pets: 'petId, petName'
});
db.version(4).stores({
pets: null // Will delete 'pets' table
});
Changes in Dexie 3.0
Before version 3.0, it was not recommended to modify an existing schema, but to instead always add a new version with the new declaration. Since Dexie version 3.0, you only have to do that when an upgrader is attached to the new version. In other cases, you only need to edit the schema and modify the version number.
Conclusions
- You never need to check whether the database needs to be created. Your code is just declarative.
- Whenever editing the schema, also remember to update the version number.
- Historical versions that have upgraders attached should be kept as long as there are people out there that may have it installed.
- To delete a table, add a new version specifying the table as null.
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()