Convert Figma logo to code with AI

jakearchibald logoidb

IndexedDB, but with promises

6,357
359
6,357
49

Top Related Projects

11,786

A Minimalistic Wrapper for IndexedDB

💾 Offline storage, improved. Wraps IndexedDB, WebSQL, or localStorage using a simple but powerful API.

21,439

Simple and fast JSON database

:nut_and_bolt: A relentless key-value store for the browser.

Client-side in-memory mongodb backed by localstorage with server sync over http

21,703

A fast, local first, reactive Database for JavaScript Applications https://rxdb.info/

Quick Overview

idb is a tiny (~1.15k brotli'd) library that provides a wrapper around IndexedDB, making it easier to work with by providing a promise-based API. It simplifies common IndexedDB operations and offers a more intuitive interface for developers working with client-side storage in web applications.

Pros

  • Lightweight and minimal, with a small footprint
  • Simplifies IndexedDB API with a promise-based interface
  • TypeScript support for improved type safety and developer experience
  • Works in both browser and service worker environments

Cons

  • Limited features compared to more comprehensive IndexedDB libraries
  • May require additional polyfills for older browsers
  • Documentation could be more extensive for advanced use cases

Code Examples

  1. Opening a database and creating an object store:
import { openDB } from 'idb';

const db = await openDB('myDatabase', 1, {
  upgrade(db) {
    db.createObjectStore('myStore');
  },
});
  1. Adding an item to the object store:
await db.add('myStore', { id: 1, name: 'John Doe' });
  1. Retrieving an item from the object store:
const item = await db.get('myStore', 1);
console.log(item.name); // 'John Doe'
  1. Deleting an item from the object store:
await db.delete('myStore', 1);

Getting Started

To use idb in your project, first install it via npm:

npm install idb

Then, import and use it in your JavaScript/TypeScript file:

import { openDB } from 'idb';

async function databaseExample() {
  const db = await openDB('myDatabase', 1, {
    upgrade(db) {
      db.createObjectStore('myStore');
    },
  });

  await db.add('myStore', { id: 1, name: 'John Doe' });
  const item = await db.get('myStore', 1);
  console.log(item.name);
}

databaseExample();

This example opens a database, creates an object store, adds an item, and then retrieves it.

Competitor Comparisons

11,786

A Minimalistic Wrapper for IndexedDB

Pros of Dexie.js

  • Higher-level API with more abstraction, making it easier to use for complex operations
  • Built-in support for indexing and advanced querying
  • Comprehensive documentation and active community support

Cons of Dexie.js

  • Larger file size and potentially higher overhead
  • Steeper learning curve for developers familiar with raw IndexedDB

Code Comparison

Dexie.js:

const db = new Dexie('MyDatabase');
db.version(1).stores({
  friends: '++id, name, age'
});

await db.friends.add({ name: 'John', age: 30 });
const youngFriends = await db.friends.where('age').below(25).toArray();

idb:

const db = await openDB('MyDatabase', 1, {
  upgrade(db) {
    db.createObjectStore('friends', { keyPath: 'id', autoIncrement: true });
  }
});

await db.add('friends', { name: 'John', age: 30 });
const tx = db.transaction('friends', 'readonly');
const youngFriends = await tx.store.index('age').getAll(IDBKeyRange.upperBound(25));

The Dexie.js example demonstrates its more concise syntax and built-in querying capabilities, while the idb example shows a lower-level approach closer to raw IndexedDB operations.

💾 Offline storage, improved. Wraps IndexedDB, WebSQL, or localStorage using a simple but powerful API.

Pros of localForage

  • Provides a simple, localStorage-like API for easier adoption
  • Offers fallback mechanisms to other storage options (WebSQL, localStorage)
  • Supports multiple storage backends with a unified interface

Cons of localForage

  • Larger bundle size due to additional abstraction layers
  • Slightly slower performance compared to direct IndexedDB usage
  • May not expose all advanced IndexedDB features

Code Comparison

localForage:

localforage.setItem('key', 'value').then(function() {
  return localforage.getItem('key');
}).then(function(value) {
  console.log(value);
}).catch(function(err) {
  console.log(err);
});

idb:

const dbPromise = idb.open('myDatabase', 1, upgradeDB => {
  upgradeDB.createObjectStore('keyval');
});
dbPromise.then(db => {
  const tx = db.transaction('keyval', 'readwrite');
  tx.objectStore('keyval').put('value', 'key');
  return tx.complete;
}).then(() => console.log('Added item to database'));

Summary

localForage offers a more user-friendly API with fallback options, making it easier for developers to adopt. However, it comes with a larger bundle size and slightly reduced performance compared to idb. idb provides a more direct interface to IndexedDB, offering better performance but requiring more setup and familiarity with IndexedDB concepts. The choice between the two depends on the specific needs of the project and the developer's comfort level with IndexedDB.

21,439

Simple and fast JSON database

Pros of lowdb

  • Simple and lightweight JSON database
  • Easy to use with a familiar API inspired by Lodash
  • Works in both Node.js and browser environments

Cons of lowdb

  • Limited scalability for large datasets
  • Lacks advanced querying capabilities
  • No built-in support for complex data types or relationships

Code Comparison

lowdb:

const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')

const adapter = new FileSync('db.json')
const db = low(adapter)

db.defaults({ posts: [] })
  .write()

db.get('posts')
  .push({ id: 1, title: 'lowdb is awesome' })
  .write()

idb:

import { openDB } from 'idb'

const db = await openDB('myDatabase', 1, {
  upgrade(db) {
    db.createObjectStore('posts', { keyPath: 'id' })
  },
})

await db.add('posts', { id: 1, title: 'idb is powerful' })

Key Differences

  • lowdb uses a simple JSON file for storage, while idb utilizes IndexedDB
  • idb offers better performance and scalability for larger datasets
  • lowdb has a more straightforward API, making it easier for beginners
  • idb provides more advanced features like indexes and complex querying

:nut_and_bolt: A relentless key-value store for the browser.

Pros of ImmortalDB

  • Provides a unified API for multiple storage mechanisms (LocalStorage, IndexedDB, Cookies)
  • Automatic fallback between storage types for better cross-browser compatibility
  • Supports both synchronous and asynchronous operations

Cons of ImmortalDB

  • Less focused on IndexedDB specifically, which may result in reduced performance for complex IndexedDB operations
  • Smaller community and fewer updates compared to idb
  • May introduce overhead due to its multi-storage approach

Code Comparison

ImmortalDB:

const db = new ImmortalDB();
await db.set('key', 'value');
const value = await db.get('key');

idb:

const db = await openDB('myDB', 1);
await db.put('store', 'value', 'key');
const value = await db.get('store', 'key');

Summary

ImmortalDB offers a more versatile storage solution with built-in fallbacks, while idb focuses specifically on IndexedDB with a streamlined API. ImmortalDB may be better for projects requiring broad browser support and simple key-value storage, whereas idb is more suitable for complex IndexedDB operations and performance-critical applications.

Client-side in-memory mongodb backed by localstorage with server sync over http

Pros of minimongo

  • Provides a MongoDB-like API for client-side storage
  • Supports both in-memory and IndexedDB backends
  • Offers synchronization capabilities with server-side MongoDB

Cons of minimongo

  • Limited to MongoDB-style operations and queries
  • May have a steeper learning curve for developers not familiar with MongoDB
  • Less actively maintained compared to idb

Code Comparison

minimongo:

const LocalDb = require('minimongo').MemoryDb;
const db = new LocalDb();
db.addCollection('users');
db.users.insert({ name: 'John', age: 30 });
db.users.findOne({ name: 'John' }, (err, doc) => {
  console.log(doc);
});

idb:

import { openDB } from 'idb';

const db = await openDB('myDB', 1, {
  upgrade(db) {
    db.createObjectStore('users');
  },
});
await db.put('users', { name: 'John', age: 30 }, 'john');
const john = await db.get('users', 'john');
console.log(john);

Summary

minimongo offers a MongoDB-like interface for client-side storage with synchronization capabilities, while idb provides a more low-level, Promise-based API for working directly with IndexedDB. minimongo may be preferable for developers familiar with MongoDB, while idb offers more flexibility and is more actively maintained.

21,703

A fast, local first, reactive Database for JavaScript Applications https://rxdb.info/

Pros of RxDB

  • Offers real-time synchronization and multi-tab support
  • Provides a more comprehensive database solution with advanced querying capabilities
  • Supports multiple storage adapters (IndexedDB, WebSQL, LocalStorage)

Cons of RxDB

  • Larger bundle size and more complex API compared to IDB
  • Steeper learning curve due to RxJS integration
  • May be overkill for simple storage needs

Code Comparison

RxDB:

const db = await createRxDatabase({
  name: 'mydb',
  adapter: 'idb'
});
const collection = await db.addCollections({
  users: {
    schema: mySchema
  }
});
await collection.users.insert({ id: 'foo', name: 'bar' });

IDB:

const db = await idb.open('mydb', 1, upgradeDB => {
  upgradeDB.createObjectStore('users', { keyPath: 'id' });
});
const tx = db.transaction('users', 'readwrite');
await tx.store.put({ id: 'foo', name: 'bar' });
await tx.done;

RxDB provides a more feature-rich solution with real-time capabilities and advanced querying, but comes with increased complexity. IDB offers a simpler, lightweight approach for basic IndexedDB operations. Choose based on your project's specific requirements and complexity.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

IndexedDB with usability.

This is a tiny (~1.19kB brotli'd) library that mostly mirrors the IndexedDB API, but with small improvements that make a big difference to usability.

  1. Installation
  2. Changes
  3. Browser support
  4. API
    1. openDB
    2. deleteDB
    3. unwrap
    4. wrap
    5. General enhancements
    6. IDBDatabase enhancements
    7. IDBTransaction enhancements
    8. IDBCursor enhancements
    9. Async iterators
  5. Examples
  6. TypeScript

Installation

Using npm

npm install idb

Then, assuming you're using a module-compatible system (like webpack, Rollup etc):

import { openDB, deleteDB, wrap, unwrap } from 'idb';

async function doDatabaseStuff() {
  const db = await openDB(…);
}

Directly in a browser

Using the modules method directly via jsdelivr:

<script type="module">
  import { openDB, deleteDB, wrap, unwrap } from 'https://cdn.jsdelivr.net/npm/idb@8/+esm';

  async function doDatabaseStuff() {
    const db = await openDB(…);
  }
</script>

Using external script reference

<script src="https://cdn.jsdelivr.net/npm/idb@8/build/umd.js"></script>
<script>
  async function doDatabaseStuff() {
    const db = await idb.openDB(…);
  }
</script>

A global, idb, will be created, containing all exports of the module version.

Changes

See details of (potentially) breaking changes.

Browser support

This library targets modern browsers, as in Chrome, Firefox, Safari, and other browsers that use those engines, such as Edge. IE is not supported.

API

openDB

This method opens a database, and returns a promise for an enhanced IDBDatabase.

const db = await openDB(name, version, {
  upgrade(db, oldVersion, newVersion, transaction, event) {
    // …
  },
  blocked(currentVersion, blockedVersion, event) {
    // …
  },
  blocking(currentVersion, blockedVersion, event) {
    // …
  },
  terminated() {
    // …
  },
});
  • name: Name of the database.
  • version (optional): Schema version, or undefined to open the current version.
  • upgrade (optional): Called if this version of the database has never been opened before. Use it to specify the schema for the database. This is similar to the upgradeneeded event in plain IndexedDB.
    • db: An enhanced IDBDatabase.
    • oldVersion: Last version of the database opened by the user.
    • newVersion: Whatever new version you provided.
    • transaction: An enhanced transaction for this upgrade. This is useful if you need to get data from other stores as part of a migration.
    • event: The event object for the associated upgradeneeded event.
  • blocked (optional): Called if there are older versions of the database open on the origin, so this version cannot open. This is similar to the blocked event in plain IndexedDB.
    • currentVersion: Version of the database that's blocking this one.
    • blockedVersion: The version of the database being blocked (whatever version you provided to openDB).
    • event: The event object for the associated blocked event.
  • blocking (optional): Called if this connection is blocking a future version of the database from opening. This is similar to the versionchange event in plain IndexedDB.
    • currentVersion: Version of the open database (whatever version you provided to openDB).
    • blockedVersion: The version of the database that's being blocked.
    • event: The event object for the associated versionchange event.
  • terminated (optional): Called if the browser abnormally terminates the connection, but not on regular closures like calling db.close(). This is similar to the close event in plain IndexedDB.

deleteDB

Deletes a database.

await deleteDB(name, {
  blocked() {
    // …
  },
});
  • name: Name of the database.
  • blocked (optional): Called if the database already exists and there are open connections that don’t close in response to a versionchange event, the request will be blocked until they all close.
    • currentVersion: Version of the database that's blocking the delete operation.
    • event: The event object for the associated 'versionchange' event.

unwrap

Takes an enhanced IndexedDB object and returns the plain unmodified one.

const unwrapped = unwrap(wrapped);

This is useful if, for some reason, you want to drop back into plain IndexedDB. Promises will also be converted back into IDBRequest objects.

wrap

Takes an IDB object and returns a version enhanced by this library.

const wrapped = wrap(unwrapped);

This is useful if some third party code gives you an IDBDatabase object and you want it to have the features of this library.

General enhancements

Once you've opened the database the API is the same as IndexedDB, except for a few changes to make things easier.

Firstly, any method that usually returns an IDBRequest object will now return a promise for the result.

const store = db.transaction(storeName).objectStore(storeName);
const value = await store.get(key);

Promises & throwing

The library turns all IDBRequest objects into promises, but it doesn't know in advance which methods may return promises.

As a result, methods such as store.put may throw instead of returning a promise.

If you're using async functions, there's no observable difference.

Transaction lifetime

TL;DR: Do not await other things between the start and end of your transaction, otherwise the transaction will close before you're done.

An IDB transaction auto-closes if it doesn't have anything left do once microtasks have been processed. As a result, this works fine:

const tx = db.transaction('keyval', 'readwrite');
const store = tx.objectStore('keyval');
const val = (await store.get('counter')) || 0;
await store.put(val + 1, 'counter');
await tx.done;

But this doesn't:

const tx = db.transaction('keyval', 'readwrite');
const store = tx.objectStore('keyval');
const val = (await store.get('counter')) || 0;
// This is where things go wrong:
const newVal = await fetch('/increment?val=' + val);
// And this throws an error:
await store.put(newVal, 'counter');
await tx.done;

In this case, the transaction closes while the browser is fetching, so store.put fails.

IDBDatabase enhancements

Shortcuts to get/set from an object store

It's common to create a transaction for a single action, so helper methods are included for this:

// Get a value from a store:
const value = await db.get(storeName, key);
// Set a value in a store:
await db.put(storeName, value, key);

The shortcuts are: get, getKey, getAll, getAllKeys, count, put, add, delete, and clear. Each method takes a storeName argument, the name of the object store, and the rest of the arguments are the same as the equivalent IDBObjectStore method.

Shortcuts to get from an index

The shortcuts are: getFromIndex, getKeyFromIndex, getAllFromIndex, getAllKeysFromIndex, and countFromIndex.

// Get a value from an index:
const value = await db.getFromIndex(storeName, indexName, key);

Each method takes storeName and indexName arguments, followed by the rest of the arguments from the equivalent IDBIndex method.

IDBTransaction enhancements

tx.store

If a transaction involves a single store, the store property will reference that store.

const tx = db.transaction('whatever');
const store = tx.store;

If a transaction involves multiple stores, tx.store is undefined, you need to use tx.objectStore(storeName) to get the stores.

tx.done

Transactions have a .done promise which resolves when the transaction completes successfully, and otherwise rejects with the transaction error.

const tx = db.transaction(storeName, 'readwrite');
await Promise.all([
  tx.store.put('bar', 'foo'),
  tx.store.put('world', 'hello'),
  tx.done,
]);

If you're writing to the database, tx.done is the signal that everything was successfully committed to the database. However, it's still beneficial to await the individual operations, as you'll see the error that caused the transaction to fail.

IDBCursor enhancements

Cursor advance methods (advance, continue, continuePrimaryKey) return a promise for the cursor, or null if there are no further values to provide.

let cursor = await db.transaction(storeName).store.openCursor();

while (cursor) {
  console.log(cursor.key, cursor.value);
  cursor = await cursor.continue();
}

Async iterators

You can iterate over stores, indexes, and cursors:

const tx = db.transaction(storeName);

for await (const cursor of tx.store) {
  // …
}

Each yielded object is an IDBCursor. You can optionally use the advance methods to skip items (within an async iterator they return void):

const tx = db.transaction(storeName);

for await (const cursor of tx.store) {
  console.log(cursor.value);
  // Skip the next item
  cursor.advance(2);
}

If you don't manually advance the cursor, cursor.continue() is called for you.

Stores and indexes also have an iterate method which has the same signature as openCursor, but returns an async iterator:

const index = db.transaction('books').store.index('author');

for await (const cursor of index.iterate('Douglas Adams')) {
  console.log(cursor.value);
}

Examples

Keyval store

This is very similar to localStorage, but async. If this is all you need, you may be interested in idb-keyval. You can always upgrade to this library later.

import { openDB } from 'idb';

const dbPromise = openDB('keyval-store', 1, {
  upgrade(db) {
    db.createObjectStore('keyval');
  },
});

export async function get(key) {
  return (await dbPromise).get('keyval', key);
}
export async function set(key, val) {
  return (await dbPromise).put('keyval', val, key);
}
export async function del(key) {
  return (await dbPromise).delete('keyval', key);
}
export async function clear() {
  return (await dbPromise).clear('keyval');
}
export async function keys() {
  return (await dbPromise).getAllKeys('keyval');
}

Article store

import { openDB } from 'idb/with-async-ittr.js';

async function demo() {
  const db = await openDB('Articles', 1, {
    upgrade(db) {
      // Create a store of objects
      const store = db.createObjectStore('articles', {
        // The 'id' property of the object will be the key.
        keyPath: 'id',
        // If it isn't explicitly set, create a value by auto incrementing.
        autoIncrement: true,
      });
      // Create an index on the 'date' property of the objects.
      store.createIndex('date', 'date');
    },
  });

  // Add an article:
  await db.add('articles', {
    title: 'Article 1',
    date: new Date('2019-01-01'),
    body: '…',
  });

  // Add multiple articles in one transaction:
  {
    const tx = db.transaction('articles', 'readwrite');
    await Promise.all([
      tx.store.add({
        title: 'Article 2',
        date: new Date('2019-01-01'),
        body: '…',
      }),
      tx.store.add({
        title: 'Article 3',
        date: new Date('2019-01-02'),
        body: '…',
      }),
      tx.done,
    ]);
  }

  // Get all the articles in date order:
  console.log(await db.getAllFromIndex('articles', 'date'));

  // Add 'And, happy new year!' to all articles on 2019-01-01:
  {
    const tx = db.transaction('articles', 'readwrite');
    const index = tx.store.index('date');

    for await (const cursor of index.iterate(new Date('2019-01-01'))) {
      const article = { ...cursor.value };
      article.body += ' And, happy new year!';
      cursor.update(article);
    }

    await tx.done;
  }
}

TypeScript

This library is fully typed, and you can improve things by providing types for your database:

import { openDB, DBSchema } from 'idb';

interface MyDB extends DBSchema {
  'favourite-number': {
    key: string;
    value: number;
  };
  products: {
    value: {
      name: string;
      price: number;
      productCode: string;
    };
    key: string;
    indexes: { 'by-price': number };
  };
}

async function demo() {
  const db = await openDB<MyDB>('my-db', 1, {
    upgrade(db) {
      db.createObjectStore('favourite-number');

      const productStore = db.createObjectStore('products', {
        keyPath: 'productCode',
      });
      productStore.createIndex('by-price', 'price');
    },
  });

  // This works
  await db.put('favourite-number', 7, 'Jen');
  // This fails at compile time, as the 'favourite-number' store expects a number.
  await db.put('favourite-number', 'Twelve', 'Jake');
}

To define types for your database, extend DBSchema with an interface where the keys are the names of your object stores.

For each value, provide an object where value is the type of values within the store, and key is the type of keys within the store.

Optionally, indexes can contain a map of index names, to the type of key within that index.

Provide this interface when calling openDB, and from then on your database will be strongly typed. This also allows your IDE to autocomplete the names of stores and indexes.

Opting out of types

If you call openDB without providing types, your database will use basic types. However, sometimes you'll need to interact with stores that aren't in your schema, perhaps during upgrades. In that case you can cast.

Let's say we were renaming the 'favourite-number' store to 'fave-nums':

import { openDB, DBSchema, IDBPDatabase } from 'idb';

interface MyDBV1 extends DBSchema {
  'favourite-number': { key: string; value: number };
}

interface MyDBV2 extends DBSchema {
  'fave-num': { key: string; value: number };
}

const db = await openDB<MyDBV2>('my-db', 2, {
  async upgrade(db, oldVersion) {
    // Cast a reference of the database to the old schema.
    const v1Db = db as unknown as IDBPDatabase<MyDBV1>;

    if (oldVersion < 1) {
      v1Db.createObjectStore('favourite-number');
    }
    if (oldVersion < 2) {
      const store = v1Db.createObjectStore('favourite-number');
      store.name = 'fave-num';
    }
  },
});

You can also cast to a typeless database by omitting the type, eg db as IDBPDatabase.

Note: Types like IDBPDatabase are used by TypeScript only. The implementation uses proxies under the hood.

Developing

pnpm run dev

This will also perform type testing.

To test, navigate to build/test/ in a browser. You'll need to set up a basic web server for this.

NPM DownloadsLast 30 Days