Typescript (old)
This is a guide on how to use Dexie with Typescript.
To see it in action, watch this stackblitz sample!
Download and install dexie
npm install dexie
Import dexie
import Dexie from 'dexie';
Assuming you have moduleResolution: “node” in your tsconfig, this will work out-of-the-box. Don’t use tsd (DefinitelyTyped) because we bundle dexie.d.ts with our npm package and tsc compiler will understand how to find it (it reads “typings” from package.json).
Create a Subclass
class MyAppDatabase extends Dexie {
// Declare implicit table properties.
// (just to inform Typescript. Instantiated by Dexie in stores() method)
contacts!: Dexie.Table<IContact, number>; // number = type of the primkey
//...other tables goes here...
constructor() {
super('MyAppDatabase');
this.version(1).stores({
contacts: '++id, first, last'
//...other tables goes here...
});
}
}
interface IContact {
id?: number;
first: string;
last: string;
}
Here’s why you need a sub class.
In vanilla javascript, when defining the database schema you get implicit table properties that are not detected with Typescript since they are defined runtime. The following line would not compile in typescript:
var db = new Dexie('MyAppDatabase');
db.version(1).stores({ contacts: 'id, first, last' });
db.contacts.put({ first: 'First name', last: 'Last name' }); // Fails to compile
This can be worked around by using the Dexie.table() static method:
db.table('contacts').put({ first: 'First name', last: 'Last name' });
However, this is not just cumbersome but will also give you bad intellisense and type safety for the objects and primary keys you are working with.
Therefore, in order to gain the best code completion, it is recommended to define a typescript class that extends Dexie and defines the Table properties on it as shown in the sample below. Here’s a sample with some more tables.
export class MyAppDatabase extends Dexie {
contacts!: Dexie.Table<IContact, number>;
emails!: Dexie.Table<IEmailAddress, number>;
phones!: Dexie.Table<IPhoneNumber, number>;
constructor() {
super('MyAppDatabase');
//
// Define tables and indexes
// (Here's where the implicit table props are dynamically created)
//
this.version(1).stores({
contacts: '++id, first, last',
emails: '++id, contactId, type, email',
phones: '++id, contactId, type, phone'
});
}
}
// By defining the interface of table records,
// you get better type safety and code completion
export interface IContact {
id?: number; // Primary key. Optional (autoincremented)
first: string; // First name
last: string; // Last name
}
export interface IEmailAddress {
id?: number;
contactId: number; // "Foreign key" to an IContact
type: string; // Type of email such as "work", "home" etc...
email: string; // The email address
}
export interface IPhoneNumber {
id?: number;
contactId: number;
type: string;
phone: string;
}
Storing real classes instead of just interfaces
In the sample above, you only inform the typescript engine about how the objects in the database looks like. This is good for type safety code completion. But you could also work with real classes so that the objects retrieved from the database will be actual instances of the class you have defined in typescript. This is simply accomplished by using Table.mapToClass(). You will then go a step further and map a Typescript class to a table, so that it may have methods and computed properties.
/* This is a 'physical' class that is mapped to
* the contacts table. We can have methods on it that
* we could call on retrieved database objects.
*/
export class Contact implements IContact {
id: number;
first: string;
last: string;
emails: IEmailAddress[];
phones: IPhoneNumber[];
constructor(first: string, last: string, id?: number) {
this.first = first;
this.last = last;
if (id) this.id = id;
}
loadEmailsAndPhones() {
return Promise.all(
db.emails
.where('contactId')
.equals(this.id)
.toArray((emails) => (this.emails = emails)),
db.phones
.where('contactId')
.equals(this.id)
.toArray((phones) => (this.phones = phones))
).then((x) => this);
}
save() {
return db.transaction('rw', db.contacts, db.emails, db.phones, () => {
return Promise.all(
// Save existing arrays
Promise.all(this.emails.map((email) => db.emails.put(email))),
Promise.all(this.phones.map((phone) => db.phones.put(phone)))
).then((results) => {
// Remove items from DB that is was not saved here:
var emailIds = results[0], // array of resulting primary keys
phoneIds = results[1]; // array of resulting primary keys
db.emails
.where('contactId')
.equals(this.id)
.and((email) => emailIds.indexOf(email.id) === -1)
.delete();
db.phones
.where('contactId')
.equals(this.id)
.and((phone) => phoneIds.indexOf(phone.id) === -1)
.delete();
// At last, save our own properties.
// (Must not do put(this) because we would get
// redundant emails/phones arrays saved into db)
db.contacts
.put(new Contact(this.first, this.last, this.id))
.then((id) => (this.id = id));
});
});
}
}
As shown in this sample, Contact has a method for resolving the foreign collections emails and phones into local array properties. It also has a save() method that will translate changes of the local arrays into database changes. These methods are just examples of a possible use case of class methods in database objects. In this case, I chose to write methods that are capable of resolving related objects into member properties and saving them back to database again using a relational pattern.
To inform Dexie about the table/class mapping, use Table.mapToClass(). This will make all instances returned by the database actually being instances of the Contact class with a proper prototype inheritance chain.
db.contacts.mapToClass(Contact);
async and await
Async functions works perfectly well with Dexie as of v2.0.
await db.transaction('rw', db.friends, async () => {
// Transaction block
let numberOfOldFriends = await db.friends.where('age').above(75).toArray();
});
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()