This is a guide on how to use Dexie with Typescript using Dexie 4 - a little more lightweight than the old docs by utilizing new features in 4.0. Since Dexie 4 is practically backward compatible, you may also still follow the old Typescript docs if your prefer to do so.

Install dexie

Typings are part of the npm package so there’s no need to for any @types library.

npm install dexie

Like all other npm packages, dexie can also be installed using yarn or pnpm as alternatives to npm.

Simplest Typescript Example

If you are storing plain data and just want the a minimal get-started code in a single module. You can extend this sample with additional tables and corresponding interfaces:

db.ts

import Dexie, { type EntityTable } from 'dexie';

interface Friend {
  id: number; // This prop will be used as primary key (see below)
  name: string;
  age: number;
}

const db = new Dexie('FriendsDatabase') as Dexie & {
  friends: EntityTable<
    Friend,
    'id' // primary key "id" (for the typings only)
  >;
};

// Schema declaration:
db.version(1).stores({
  friends: '++id, name, age' // primary key "id" (for the runtime!)
});

export type { Friend };
export { db };

See EntityTable

Example with Mapped Classes

Here’s an example of how to use mapped classes in Dexie 4. In this example, we split db.ts into three different modules:

  • db.ts - exports the singleton Dexie instance. To be imported wherever your db is needed.
  • AppDB.ts - declaration of database schema
  • Friend.ts - Entity class example. You could have muliple modules like this

db.ts

// db.ts
import AppDB from './AppDB';

export const db = new AppDB();

AppDB.ts

// AppDB.ts
import Dexie, { type EntityTable } from 'dexie';
import Friend from './Friend';

export default class AppDB extends Dexie {
  friends!: EntityTable<Friend, 'id'>;

  constructor() {
    super('FriendsDB');
    this.version(1).stores({
      friends: '++id, name, age'
    });
    this.friends.mapToClass(Friend);
  }
}

Friend.ts

// Friend.ts

import { Entity } from 'dexie';
import type AppDB from './AppDB';

export default class Friend extends Entity<AppDB> {
  id!: number;
  name!: string;
  age!: number;

  // example method that access the DB:
  async birthday() {
    // this.db is inherited from Entity<AppDB>:
    await this.db.friends.update(this.id, (friend) => ++friend.age);
  }
}

Usage examples

React component

import { db } from './db';
import { useLiveQuery } from 'dexie-react-hooks';

function MyReactComponent() {
  const friends = useLiveQuery(() => db.friends.toArray());
  return (
    <ul>
      {friends?.map((f) => (
        <li key={f.id}>
          {f.name}, {f.age}
        </li>
      ))}
    </ul>
  );
}

Plain Typescript

import { db } from './db';

async function addFriend(name: string, age: number) {
  await db.friends.add({ name, age });
}

async function updateFriend(id: number, updates: Partial<Friend>) {
  await db.friends.update(id, { ...updates });
}

async function deleteFriend(id: number) {
  await db.friends.delete(id);
}

Example with Dexie Cloud

Synced tables have a few more properties that, similar to auto-generated primary keys, are optional on insert but “required”/mandatory on all instances returned from db queries: owner and realmId. DexieCloudTable is therefore a better helper type to use for synced tables with dexie-cloud-addon.

AppDB.ts - Dexie Cloud version

import Dexie from 'dexie';
import dexieCloud, { type DexieCloudTable } from 'dexie-cloud-addon';
import Friend from './Friend';

export default class AppDB extends Dexie {
  friends!: DexieCloudTable<Friend, 'id'>;

  constructor() {
    super('FriendsDB', {
      addons: [dexieCloud]
    });

    this.version(1).stores({
      friends: '@id, name, age'
    });

    this.friends.mapToClass(Friend);

    this.cloud.configure({
      databaseUrl: 'https://xxxxxx.dexie.cloud'
      // see https://dexie.org/cloud/docs/db.cloud.configure()
    });
  }
}

Friend.ts - Dexie Cloud version

import type AppDB from './AppDB';

export default class Friend extends Entity<AppDB> {
  id!: string; // Primary key is string
  name!: string;
  age!: number;
  owner!: string; // Dexie Cloud specific property
  realmId!: string; // Dexie Cloud specific property
}

Usage examples - Dexie Cloud version

The usage is almost identical to the plain Dexie version. But this example will show how usePermissions can help to get information about access control.

React component

import { db } from './db';
import { useLiveQuery, usePermissions } from 'dexie-react-hooks';
import type { Friend } from './Friend'; // ...where class Friend is defined (see earlier on this page).

function MyReactComponent() {
  const friends = useLiveQuery(() => db.friends.toArray());
  return (
    <ul>
      {friends?.map((f) => (
        <li key={f.id}>
          <FriendComponent friend={f} />
        </li>
      ))}
    </ul>
  );
}

function FriendComponent({ friend }: { friend: Friend }) {
  const can = usePermissions(friend); // https://dexie.org/docs/dexie-react-hooks/usePermissions()
  // This 'can' instance can be used to check whether access control would permit the current user to
  // perform various actions. This information can be used to disable or hide fields and buttons.
  // (See also https://dexie.org/cloud/docs/access-control)
  return (
    <>
      Name: {friend.name} <br />
      Age: {friend.age} <br />
      Can I edit name?: {can.update('name') ? 'Yes!' : 'No!'} <br />
      Can I edit age?: {can.update('age') ? 'Yes!' : 'No!'} <br />
      Can I delete this friend?: {can.delete() ? 'Yes!' : 'No!'} <br />
    </>
  );
}

Plain Typescript

import { db } from './db';

async function addFriend(name: string, age: number) {
  // Ok to leave out id, realmId and owner as they are all auto-generated:
  await db.friends.add({ name, age });
}

async function updateFriend(id: string, updates: Partial<Friend>) {
  await db.friends.update(id, { ...updates });
}

async function deleteFriend(id: string) {
  await db.friends.delete(id);
}

Table of Contents