Dexie.js is an easy-to-install JavaScript library. The most common way is via npm or yarn:
npm install dexie
# or
yarn add dexie
Alternatively, you can include it directly from a CDN, though this is less ideal for larger projects due to version management challenges:
<script src="https://unpkg.com/dexie"></script> </html>
Remember to check the latest version number on the Dexie.js website or npm registry and update the URL or package version accordingly.
Dexie.js provides an intuitive, Promise-based API for interacting with IndexedDB. At its core, you define a database schema, open the database, and then use methods to perform CRUD (Create, Read, Update, Delete) operations.
Let’s create a simple database to store a list of names.
// Import Dexie
import Dexie from 'dexie';
// Define the database schema
const db = new Dexie('myDatabase');
.version(1).stores({
dbnames: '++id, name' // '++id' creates an auto-incrementing primary key
;
})
// Add data
.names.add({ name: 'John Doe' }).then(() => {
dbconsole.log('Data added successfully!');
;
})
// Retrieve data
.names.toArray().then((names) => {
dbconsole.log('Retrieved names:', names);
; })
This code snippet first defines a database named ‘myDatabase’ with a single table named ‘names’. The ++id
in the stores
definition creates an auto-incrementing primary key for each record. We then add a name and retrieve all names from the database using promises. Remember to handle potential errors using .catch()
if needed.
Dexie.js handles database opening asynchronously. The constructor (new Dexie('myDatabase')
) doesn’t immediately open the database; it creates a Dexie
object that represents the database. The database is only opened when you perform an operation that requires accessing the database (e.g., adding, getting, or updating data). This happens implicitly when you invoke methods like db.names.add()
, or explicitly when you call db.open()
. While db.open()
is rarely needed, it can be useful for pre-emptive opening in specific scenarios like before the main application logic starts, or to explicitly handle database opening errors before other actions are taken. For example:
import Dexie from 'dexie';
const db = new Dexie('myDatabase');
.version(1).stores({
dbnames: '++id, name'
;
})
.open().then(() => {
dbconsole.log("Database opened successfully!");
// Continue with other operations here.
.catch(error => {
})console.error("Error opening database:", error);
// Handle database opening errors appropriately, perhaps by displaying an error message to the user.
; })
Error handling via .catch()
is crucial for robust application behaviour. Always handle potential errors when interacting with the database.
Dexie.js tables are defined within the stores
object of a database version. The stores
object maps table names to their schema definitions. The schema specifies the table’s columns and their properties, primarily the primary key. It uses a string-based syntax where column names are separated by commas.
A simple schema might look like this:
.version(1).stores({
dbusers: 'id, name, email'
; })
This defines a table named users
with three columns: id
, name
, and email
. Note that no data types are explicitly specified; Dexie.js handles data type inference.
A primary key uniquely identifies each record in a table. Dexie.js supports auto-incrementing primary keys and custom primary keys.
Auto-Incrementing Primary Keys:
Use ++id
to create an auto-incrementing integer primary key named id
. This is often the simplest and most convenient approach.
.version(1).stores({
dbproducts: '++id, name, price'
; })
Custom Primary Keys:
For custom primary keys, simply list the column name(s) that will serve as the primary key. Dexie.js will use these columns to ensure uniqueness. You can have a composite primary key consisting of multiple columns.
.version(1).stores({
dbitems: 'orderId, itemId, description' // orderId and itemId together form the primary key
; })
Indexes speed up queries on specific columns. Indexes are defined by adding +
before a column name in the stores
definition. Multiple indexes can be created per table.
.version(1).stores({
dbproducts: '++id, name, +price, +category'
; })
This creates indexes on the price
and category
columns, enabling faster searches based on these fields. Note that the primary key is always indexed implicitly.
While Dexie.js doesn’t explicitly use the term “collections” in the same way as some other databases, the concept is embodied by its tables. Each table in Dexie.js acts as a collection of objects. The stores
object definition in the db.version()
method is what defines the structure and schema for these collections/tables. Therefore, defining a table automatically defines a collection. All interactions with the table—adding, retrieving, updating, deleting—are effectively operations on the collection represented by that table.
add
, put
, bulkAdd
, bulkPut
)add(obj)
: Adds a new object to the table. Dexie.js automatically assigns the primary key if one is auto-incrementing. Returns a Promise resolving to the generated primary key.
put(obj)
: Adds or updates an object. If an object with the same primary key already exists, it’s updated; otherwise, it’s added. Returns a Promise resolving to the primary key.
bulkAdd(arrayOfObjs)
: Adds multiple objects in a single transaction. More efficient than calling add()
repeatedly. Returns a Promise resolving to an array of the generated primary keys (in the same order as the input array).
bulkPut(arrayOfObjs)
: Similar to bulkAdd()
, but updates existing objects if they have matching primary keys. Returns a Promise resolving to an array of primary keys.
get
, getAll
, toCollection
)get(primaryKey)
: Retrieves a single object by its primary key. Returns a Promise resolving to the object or undefined
if not found.
getAll(primaryKey)
: Retrieves all objects matching specified primary key(s). If no arguments are given, it retrieves all objects in the table. Returns a Promise resolving to an array of objects.
toCollection()
: Transforms a query into a Collection
object, providing more advanced querying capabilities chained before using toArray()
, each()
, etc. Useful for building complex queries.
update
, put
)update(primaryKey, modifications)
: Updates a single object identified by its primary key. modifications
is an object containing the fields to update. Returns a Promise resolving to the number of updated objects (0 or 1).
put(obj)
: As mentioned before, put()
can also be used for updating. If an object with the same primary key already exists, it will be updated with the values provided in the argument obj
.
delete
, clear
)delete(primaryKey)
: Deletes a single object identified by its primary key. Returns a Promise resolving to the number of deleted objects (0 or 1).
clear()
: Deletes all objects from the table. Returns a Promise.
Dexie.js supports transactions to ensure data integrity. Use db.transaction()
to wrap multiple database operations within a single transaction. If any operation fails within the transaction, the entire transaction is rolled back.
.transaction('rw', db.users, db.products, function* () {
dbyield db.users.add({ name: 'Jane Doe' });
yield db.products.add({ name: 'Widget', price: 10 });
; })
Dexie.js uses .where()
to filter results based on various conditions:
.users.where('age').above(25).toArray(); // Get all users older than 25
db.products.where('price').between(10, 20).toArray(); //Get products with price between 10 and 20
db.users.where('name').startsWith('J').toArray(); // Get all users whose name starts with 'J' db
You can chain multiple .where()
clauses, though this is more efficient with indexes on the relevant fields.
Use .orderBy()
to sort the results of a query:
.products.orderBy('price').toArray(); // Order products by price (ascending)
db.products.orderBy('price').reverse().toArray(); // Order products by price (descending)
db.products.orderBy('category', 'price').toArray();// Order by category, then price. db
Multiple columns can be used for multi-level sorting.
The .filter()
method allows you to filter the results based on a custom function:
.products.where('price').above(10).filter(product => product.category === 'Electronics').toArray(); db
Use .limit()
to restrict the number of results returned:
.products.orderBy('price').limit(5).toArray(); // Get the 5 cheapest products db
.offset()
can be used in conjunction with .limit()
to skip a certain number of results:
.products.orderBy('price').offset(5).limit(5).toArray();// Get products ranked 6-10 by price db
Remember to chain these methods appropriately for complex queries. Using indexes whenever possible significantly improves query performance.
Dexie.js emits several events that allow you to react to database changes and lifecycle events. These events are typically handled using the .on()
method. Common events include:
'populate'
: Fired when the database is populated from IndexedDB.'ready'
: Fired when the database is ready for use after opening.'blocked'
: Fired when another tab or window is blocking database access.'versionchange'
: Fired when a version change is requested. This is useful for handling schema migrations smoothly across different browser tabs or windows.'upgrade'
: Fired during database upgrades, allowing custom upgrade logic.Example:
.on('ready', () => console.log('Database is ready'));
db.on('blocked', () => console.warn('Database is blocked')); db
Dexie Collections, returned by db.table.toCollection()
, offer a fluent API for building complex queries and chaining operations before executing them with methods like toArray()
, each()
, count()
, etc. They act as a sort of deferred execution mechanism, giving more control over how and when queries are executed. Efficient for complex queries or scenarios requiring multiple operations on the same subset of data.
When upgrading database versions, you must handle schema changes within the upgrade
event handler of the Dexie.version()
method. This ensures data integrity during schema migrations. Use transactions within the upgrade
function to ensure atomicity of operations.
Dexie.js allows defining hooks to run specific code before or after database operations. These hooks provide opportunities for logging, validation, or other custom logic:
before
hooks: Run before an operation (e.g., add
, put
, update
, delete
).after
hooks: Run after an operation.Hooks are defined using the hook()
method. For instance, creating a before
hook that logs operations:
.users.hook('beforeAdd', (primKey, obj, transaction) => {
dbconsole.log("Adding user:", obj);
; })
You can add custom methods to your tables to encapsulate frequently used operations or create reusable logic. This helps to enhance code readability and maintainability:
.users.method('findByEmail', function(email) {
dbreturn this.where('email').equals(email).first();
;
})
// Usage:
.users.findByEmail('test@example.com').then(user => { /* ... */ }); db
Dexie.js uses versions to manage schema changes. Each version is defined using db.version(number)
, and the upgrade
function within this definition contains the necessary upgrade logic. For example:
.version(2).stores({
dbusers: '++id, name, email, address'
.upgrade(function (trans) {
})if (trans.db.version === 1) {
// upgrade logic from version 1 to version 2
.db.users.toCollection().modify({address: null}); //add a new field `address`
trans
}; })
Dexie.js handles Blobs and Files seamlessly. You can store and retrieve them just like any other data type, using methods like add()
and get()
. The key is that the database will store references to the blobs, not the entire blob data directly in the table itself.
Always use .catch()
to handle potential errors during database operations. Dexie.js throws errors for various reasons, including invalid queries, database failures, and schema mismatches. Appropriate error handling is crucial for application robustness. Check for specific error types (e.g., Dexie.NoSuchPrimaryKey
, Dexie.SchemaError
, etc.) to provide user-friendly error messages.
getAll()
if possible when only one or a few specific records are needed; instead use get()
or where()
for more targeted retrievals.bulkAdd()
and bulkPut()
for adding or updating multiple records efficiently.Dexie.js handles various JavaScript data types with minimal friction. However, understanding how these types are stored and retrieved can lead to more efficient and robust code.
Numbers are stored as their native JavaScript numeric representations (both integers and floating-point numbers). Dexie.js automatically handles type inference and conversion. No special handling is usually needed.
.myTable.add({ count: 10, price: 99.99 }); db
Strings are stored as native JavaScript strings. No special encoding or escaping is typically required, though you might need to handle encoding/decoding if you’re interacting with external systems that might use different character encodings.
.myTable.add({ name: 'John Doe', description: 'A long string with various characters.' }); db
Dates are stored as timestamps (the number of milliseconds since the Unix epoch). When retrieving a Date object from the database, Dexie.js automatically converts the timestamp back into a Date
object.
const now = new Date();
.myTable.add({ created: now });
db
// ... later ...
.myTable.get(1).then(item => {
dbconsole.log(item.created instanceof Date); // true
; })
Arrays are stored as JavaScript arrays. There are no special constraints on the data types within the array; they can be a mix of numbers, strings, objects, etc. However, for better queryability, consider structuring your data to avoid deeply nested arrays if possible.
.myTable.add({ tags: ['javascript', 'indexeddb', 'dexie'] }); db
Objects are stored as JavaScript objects. They are generally handled without special treatment, but remember that you cannot directly query the properties of nested objects unless you design your schema to reflect the necessary structure for querying.
.myTable.add({ user: { name: 'Jane', age: 30 } }); db
To efficiently query nested data, flatten your object structure where needed for indexing or pre-process the data for easier querying.
While Dexie.js doesn’t have a specific JSON type, you can store JSON data as strings. You will need to explicitly parse the JSON strings using JSON.parse()
when retrieving and JSON.stringify()
when storing.
const jsonData = { name: 'Test', data: [1, 2, 3] };
.myTable.add({ data: JSON.stringify(jsonData) }).then(() => {
db// ...
;
})
// Retrieving and parsing:
.myTable.get(1).then(item => {
dbconst parsedData = JSON.parse(item.data);
// Use parsedData
; })
Blobs (Binary Large Objects) are stored as Blobs. Dexie.js handles them efficiently, storing references to the actual blob data within IndexedDB. When retrieving a Blob, you receive a new Blob object representing the data. For large Blobs, consider optimizing your data handling to avoid loading unnecessary data into memory.
const myBlob = new Blob(['Hello, world!'], { type: 'text/plain' });
.myTable.add({ myBlob: myBlob }); db
Remember that efficient data modeling and indexing are critical for optimal performance when working with large datasets or complex data structures in Dexie.js. Consider optimizing your data structure to support efficient querying.
Careful database design is crucial for performance and maintainability. Consider these points:
where
clauses, orderBy
clauses, and joins. Experiment with different indexes to find the optimal configuration.getAll()
: Use getAll()
only when necessary to retrieve all data. For specific records, prefer get()
or queries using .where()
.limit()
and offset()
to reduce the amount of data retrieved, improving query performance, especially for paginated results.catch()
Blocks: Always include .catch()
blocks in your asynchronous database operations to handle potential errors gracefully.Dexie.NoSuchPrimaryKey
, Dexie.TransactionError
) to provide more informative error messages and error recovery strategies.Dexie.debug = true;
) to get additional logging information which can be helpful during development and debugging.This section provides a concise overview of the core Dexie.js API objects. For complete and up-to-date details, refer to the official Dexie.js documentation.
The Dexie
object represents the entire database. It’s created using the new Dexie(dbName)
constructor. Key methods include:
version(number)
: Defines a database version and its schema. Used for schema migrations.open()
: Opens the database (usually handled implicitly).close()
: Closes the database.table(tableName)
: Returns a Table
object for a specific table.transaction(mode, tables, func)
: Creates a transaction.on(eventName, listener)
: Attaches an event listener.delete()
: Deletes the entire database.The Table
object represents a single table within the database. It’s obtained via db.table(tableName)
. Key methods include:
add(obj)
: Adds a new object.put(obj)
: Adds or updates an object.bulkAdd(arrayOfObjs)
: Adds multiple objects.bulkPut(arrayOfObjs)
: Adds or updates multiple objects.get(primaryKey)
: Retrieves an object by primary key.getAll(primaryKey)
: Retrieves objects by primary keys or all objects if no keys are provided.delete(primaryKey)
: Deletes an object by primary key.clear()
: Deletes all objects from the table.where(indexName)
: Starts a where clause.count()
: Counts the number of objects in the table or matching a query.orderBy(indexName)
: Orders the results of a query.limit(n)
: Limits the number of results.offset(n)
: Skips the first n
results.filter(filterFunction)
: Filters the results of a query.toCollection()
: Returns a Collection
object for chained query building.hook(hookName, callback)
: Attaches a hook (before/after operations).method(methodName, methodFunction)
: Adds a custom method to the table.The Collection
object represents a query that hasn’t been executed yet. It’s obtained via table.toCollection()
. It allows for building complex queries by chaining multiple methods before finally executing the query with methods like:
toArray()
: Executes the query and returns a Promise resolving to an array.each(callback)
: Executes the query and iterates through the results, calling the callback for each.count()
: Counts the number of results.offset(n)
: Skips the first n
results.limit(n)
: Limits the number of results.filter(filterFunction)
: Filters the results.modify(modifier)
: Modifies the objects in the collection directly in the database.delete()
: Deletes the objects in the collection.The Transaction
object represents a database transaction. It’s created via db.transaction()
. Key properties and methods:
tables
: An array of the tables involved in the transaction.mode
: The transaction mode (‘rw’ for read-write, ‘r’ for read-only).table(tableName)
: Returns the Table
object for a table within this transaction.The KeyRange
object is used to specify a range of keys in where
clauses. It’s not directly created by the user but is used by functions such as .where(...).between(a, b)
.
Dexie.js provides several helper functions:
Dexie.Promise.resolve()
/Dexie.Promise.reject()
: Promise creation methods.Dexie.liveQuery()
: Creates a live query, reacting to changes. Useful for real-time updates.This API reference provides a high-level overview. Refer to the official documentation for detailed descriptions, examples, and exhaustive coverage of all options and methods. The official documentation is the most accurate and updated source for Dexie.js’s API.
This section covers common issues and strategies for resolving them when working with Dexie.js.
Dexie.NoSuchPrimaryKey
: This error occurs when trying to access a record using get()
or delete()
with a primary key that doesn’t exist. Double-check the primary key value and ensure it’s correct.
Dexie.SchemaError
: This indicates a schema mismatch, typically occurring during database upgrades. Review your upgrade logic in the upgrade()
function within db.version()
. Ensure that all schema changes are handled correctly and that transactions are used within the upgrade function.
Dexie.QuotaExceededError
: This error indicates that the browser’s storage quota has been exceeded. Consider optimizing your database design, removing unnecessary data, or implementing strategies for managing storage limits.
TypeError: this.transaction is undefined
: This often happens when calling database methods outside of a transaction or outside an async function that is part of a transaction. Ensure that all database operations are within an appropriate transaction context.
Promise Rejection: If your database operations fail silently, make sure you have properly handled the Promise rejections using .catch()
. Check the error object provided in the catch
block for details on what went wrong.
Error opening database
: This can be caused by multiple things: permission problems, browser restrictions, a database with the same name already open (check for this in other tabs).
Browser Developer Tools: Use your browser’s developer tools (especially the console) to examine errors, inspect network requests, and step through your code.
Dexie.debug = true;
: Setting Dexie.debug
to true
enables verbose logging that can be very helpful in identifying issues. This provides detailed information about queries and database operations.
Logging: Add console.log()
statements at strategic points in your code to track data and identify where errors might be originating. Log the data being added to the database to see if it is what you expected.
Simplify Your Code: Isolate problematic sections of code by temporarily removing or commenting out parts to identify the root cause.
Check for Race Conditions: Be aware of potential race conditions if multiple parts of your application access the database concurrently. Use transactions to ensure data integrity.
Dexie.js generally supports modern browsers. However, ensure that you’re targeting browsers with adequate IndexedDB support. Older browsers might not support all Dexie.js features or might have different IndexedDB implementations that behave slightly differently.
Dexie.js Documentation: The official Dexie.js documentation is an excellent resource, providing detailed explanations, examples, and API references.
GitHub Issues: Check the Dexie.js GitHub repository for existing issues. If you can’t find a solution, create a new issue, providing detailed information about the problem, your code, and the steps to reproduce it.
Online Forums and Communities: Search online forums and communities (e.g., Stack Overflow) for solutions to common problems.
Dexie.js Author: Contact the Dexie.js author through appropriate channels (e.g., GitHub) if you need direct assistance.
Remember to always provide a minimal, reproducible example when seeking help online. This makes it easier for others to understand your problem and assist you in finding a solution.