Dexie Encryption Middleware
raw JSON →dexie-encrypted provides a robust middleware solution for encrypting and decrypting data stored in Dexie.js (IndexedDB wrapper) databases. It transparently handles cryptographic operations, ensuring sensitive information is stored securely at rest within the browser's persistent storage. The current stable version, 2.0.0, is specifically designed for compatibility with Dexie v3.0.0 and newer versions. This library typically follows the release cadence of major Dexie versions and security patches, focusing on stability and integration. Its primary differentiator is its deep integration with Dexie's hook system, allowing developers to add encryption to their existing Dexie projects with minimal code changes by simply importing the library and configuring the encryption key and tables.
Common errors
error TypeError: Cannot read properties of undefined (reading 'encrypt') or Property 'encryptedTables' does not exist on type 'Dexie'. ↓
import 'dexie-encrypted'; is present at the top level of your application entry file or before any Dexie database instance is created and initialized. error Error: Encryption key missing for table 'yourTableName'. ↓
db.encryptionKey = await getYourSecureKey(); and ensure a valid CryptoKey instance is assigned before any read/write operations on encrypted tables. error Uncaught (in promise) DOMException: The key 'id' in 'yourTableName' is not unique. ↓
id) and indexes are correctly defined. If using client-side generated UUIDs, ensure they are truly unique. Review Dexie's documentation on unique keys and compound indexes. Warnings
breaking Version 2.0.0 of `dexie-encrypted` introduces breaking changes by requiring Dexie.js v3.0.0 or later. Projects using Dexie v2.x must upgrade Dexie before upgrading `dexie-encrypted`. ↓
gotcha Proper encryption key management is critical. Storing the encryption key directly in application source code, local storage, or session storage is generally insecure for production environments. ↓
gotcha Changes to your Dexie database schema, particularly adding or removing encrypted fields, require careful migration planning to avoid data loss or corruption. Existing encrypted data might become unreadable if the schema changes without a proper migration strategy. ↓
gotcha The `db.encryptionKey` must be set *before* performing any database operations on encrypted tables. Forgetting to set the key or setting it too late will result in errors when trying to read or write encrypted data. ↓
Install
npm install dexie-encrypted yarn add dexie-encrypted pnpm add dexie-encrypted Imports
- Dexie Prototype Extension wrong
import { encrypt } from 'dexie-encrypted';correctimport 'dexie-encrypted'; - Dexie wrong
const Dexie = require('dexie');correctimport { Dexie } from 'dexie'; - Table wrong
import { table } from 'dexie';correctimport { Table } from 'dexie';
Quickstart
import { Dexie } from 'dexie';
import 'dexie-encrypted'; // This extends the Dexie prototype
// In a real application, get your key securely, e.g., from a Web Worker or IndexedDB
// DO NOT store it directly in your main application code or local storage.
const getEncryptionKey = async (): Promise<CryptoKey> => {
// For demonstration: generate a new key if not already stored in sessionStorage
let storedKey = sessionStorage.getItem('myAppEncryptionKey');
if (storedKey) {
return crypto.subtle.importKey(
'jwk',
JSON.parse(storedKey),
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
}
const newKey = await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
sessionStorage.setItem(
'myAppEncryptionKey',
JSON.stringify(await crypto.subtle.exportKey('jwk', newKey))
);
return newKey;
};
class MyEncryptedDatabase extends Dexie {
users!: Dexie.Table<{ id: number, name: string, secret: string }, number>;
constructor() {
super('MyEncryptedDatabase');
this.version(1).stores({
users: '++id, name, secret'
});
// Specify which tables contain encrypted data
this.encryptedTables(['users']);
}
}
async function runEncryptionDemo() {
const db = new MyEncryptedDatabase();
try {
// Set the encryption key. This must be done BEFORE any data operations.
db.encryptionKey = await getEncryptionKey();
// Add an encrypted user
const userId = await db.users.add({ id: 1, name: 'Alice', secret: 'Top secret info' });
console.log(`Added user with ID: ${userId}`);
// Retrieve the user. It will be decrypted automatically.
const user = await db.users.get(userId);
console.log('Retrieved user:', user);
console.log('User secret (decrypted):', user?.secret);
// Verify that the retrieved secret matches the original
if (user?.secret === 'Top secret info') {
console.log('Encryption and decryption successful!');
} else {
console.error('Decryption failed or data mismatch.');
}
} catch (error) {
console.error('Error during encryption demo:', error);
} finally {
db.close();
}
}
runEncryptionDemo();