Browser IndexedDB API
What is it?
IndexedDB is persistent storage key-value object-oriented database. It supports transactions. It has asynchronous API and can be used in Web Workers.
Limitations
There is no the internationalized sorting, no synchronizing with server-side database and no full text searching. There are some libraries that can deal with this limitations (here and here). Also, the data could be wiped out any time by user or by end of the session when user uses private browsing mode.
The database could be in two storages modes:
- Persistent – data is evicted only when users say so.
- Temporary – data is cleared when DB reaches browser quota using last recently used policy
Storage limit is browser specific.
Sample application with IndexedDB
Simple application will be storing images in IndexedDB. Also, it will support simple search and exporting IPTC photo tags to the database. Added photos will be stored in the browser database.
I will be using dexie.js as a simple wrapper for the IndexedDB. It has simpler and cleaner API. In sources, there will be flow typing. For UI I used react.
Sources are available at https://github.com/pkasperowicz/indexedDB. To see a demo type npm start.
Opening database
Below you can see opening database in dexie.js. It opens Database images. Database schema contains definitions of tables with primary key and indexed keys. Table images contain primary key id and two indexed keys name and size. Notice that not indexed fields doesn’t have to be defined.
const Database = new Dexie("images");
Database.version(1).stores({
images: '++id,name,size'
});
Database.open().catch(function (e) {
console.error ("Open failed: " + e);
});
Modifying database schema
We could change database schema. To do that we are adding new definition:
Database.version(2).stores({
images: '++id,caption,city,country,*keywords'
}).upgrade(function () {
return db.images.modify(image => {
image.city = null;
image.country = null;
image.caption = image.name;
image.keywords = [];
delete image.name;
delete image.size;
});
});
We are not deleting the old definition, but adding new one, also adding upgrade function which updates schema, to make sure the old data is compatible with old one.
Saving the data
To get images from user File API will be used. Here I used Dropzone component, which returns me list of selected File objects. Than we receive binary data using asynchronous API for reading files and stored in the Uint8Array.
function readFile (file: File) : Promise<Uint8Array> {
return new Promise(function (resolve) {
let reader = new FileReader();
reader.onload = e => resolve(new Uint8Array(e.target.result));
reader.readAsArrayBuffer(file);
});
}
After that we can save the data:
function addFile (file: File) {
readFile(file)
.then(data => Database.images.add({ name: file.name,
size: file.size,
data}));
}
Saving data can be either plain Object or other custom data model.
function addFile (file: File) : Promise<number> {
return readFile(file)
.then(arrayBuffer => Database.images.add(new Image(arrayBuffer)));
}
Reading the data
Database.images.mapToClass (Image);
function getImages () : Promise<Image[]> {
return Database.images
.toCollection()
.toArray();
}
MapToCalass method says to what class model should we map the data. When we don’t specify the class, plain Object will be used to map the data. Database supports data types such as the string, number, Object, boolean and TypedArrays for binary data. To map data to other object for example like Date or use some more complicated class hierarchy Typeson could be used.
There are also more complicated queries:
function searchImages (query: string) : Promise<Image[]> {
return Database.images
.where('keywords').startsWithIgnoreCase(query)
.or('city').startsWithIgnoreCase(query)
.or('country').startsWithIgnoreCase(query)
.or('caption').startsWithIgnoreCase(query)
.toArray();
}
Here we search through Database indexes, note that keywords here are array index, which means that it matches any element of array. It can be useful for implementing full text search, feature which is not backed up by IndexedDB API, look here and here.
—
See also
https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
https://github.com/dfahlander/Dexie.js/wiki/Samples
https://github.com/explorigin/persistent-redux persistent redux store using PouchDB