diff --git a/web/database/utils/constants.js b/web/database/utils/constants.js
--- a/web/database/utils/constants.js
+++ b/web/database/utils/constants.js
@@ -4,3 +4,5 @@
 export const SQLITE_ENCRYPTION_KEY = 'encryptionKey';
 
 export const CURRENT_USER_ID_KEY = 'current_user_id';
+
+export const DB_PERSIST_THROTTLE_WAIT_MS = 300;
diff --git a/web/database/worker/db-worker.js b/web/database/worker/db-worker.js
--- a/web/database/worker/db-worker.js
+++ b/web/database/worker/db-worker.js
@@ -1,6 +1,7 @@
 // @flow
 
 import localforage from 'localforage';
+import _throttle from 'lodash/throttle.js';
 import initSqlJs, { type SqliteDatabase } from 'sql.js';
 
 import type {
@@ -32,10 +33,15 @@
 } from '../queries/storage-engine-queries.js';
 import {
   CURRENT_USER_ID_KEY,
+  DB_PERSIST_THROTTLE_WAIT_MS,
   SQLITE_CONTENT,
   SQLITE_ENCRYPTION_KEY,
 } from '../utils/constants.js';
-import { generateDatabaseCryptoKey } from '../utils/worker-crypto-utils.js';
+import {
+  decryptDatabaseFile,
+  encryptDatabaseFile,
+  generateDatabaseCryptoKey,
+} from '../utils/worker-crypto-utils.js';
 
 const localforageConfig: PartialConfig = {
   driver: localforage.INDEXEDDB,
@@ -47,9 +53,26 @@
 localforage.config(localforageConfig);
 
 let sqliteDb: ?SqliteDatabase = null;
+let encryptionKey: ?CryptoKey = null;
 
 async function initDatabase(sqljsFilePath: string, sqljsFilename: ?string) {
-  const content = await localforage.getItem(SQLITE_CONTENT);
+  encryptionKey = await localforage.getItem(SQLITE_ENCRYPTION_KEY);
+  if (!encryptionKey) {
+    const cryptoKey = await generateDatabaseCryptoKey();
+    await localforage.setItem(SQLITE_ENCRYPTION_KEY, cryptoKey);
+  }
+
+  const encryptedContent = await localforage.getItem(SQLITE_CONTENT);
+
+  let dbContent = null;
+  try {
+    if (encryptionKey && encryptedContent) {
+      dbContent = await decryptDatabaseFile(encryptedContent, encryptionKey);
+    }
+  } catch (e) {
+    console.error('Error while decrypting content, clearing database content');
+    await localforage.removeItem(SQLITE_CONTENT);
+  }
 
   const locateFile = defaultFilename => {
     if (sqljsFilename) {
@@ -61,11 +84,15 @@
     locateFile,
   });
 
-  if (content) {
-    sqliteDb = new SQL.Database(new Uint8Array(content));
+  if (dbContent) {
+    sqliteDb = new SQL.Database(dbContent);
+    console.info(
+      'Database exists and is properly encrypted, using persisted data',
+    );
   } else {
     sqliteDb = new SQL.Database();
     setupSQLiteDB(sqliteDb);
+    console.info('Creating fresh database');
   }
 
   const dbVersion = getSQLiteDBVersion(sqliteDb);
@@ -104,6 +131,25 @@
   };
 }
 
+async function persist() {
+  if (!sqliteDb) {
+    throw new Error('Database not initialized');
+  }
+
+  if (!encryptionKey) {
+    encryptionKey = await localforage.getItem(SQLITE_ENCRYPTION_KEY);
+  }
+
+  const dbData = sqliteDb.export();
+  if (!encryptionKey) {
+    throw new Error('Encryption key is missing');
+  }
+  const encryptedData = await encryptDatabaseFile(dbData, encryptionKey);
+  await localforage.setItem(SQLITE_CONTENT, encryptedData);
+}
+
+const throttledPersist = _throttle(persist, DB_PERSIST_THROTTLE_WAIT_MS);
+
 async function processAppRequest(
   message: WorkerRequestMessage,
 ): Promise<?WorkerResponseMessage> {
@@ -157,19 +203,23 @@
     if (draftStoreOperations) {
       processDraftStoreOperations(draftStoreOperations);
     }
+    throttledPersist();
     return;
   } else if (message.type === workerRequestMessageTypes.SET_CURRENT_USER_ID) {
     setMetadata(sqliteDb, CURRENT_USER_ID_KEY, message.userID);
+    throttledPersist();
     return;
   } else if (
     message.type === workerRequestMessageTypes.SET_PERSIST_STORAGE_ITEM
   ) {
     setPersistStorageItem(sqliteDb, message.key, message.item);
+    throttledPersist();
     return;
   } else if (
     message.type === workerRequestMessageTypes.REMOVE_PERSIST_STORAGE_ITEM
   ) {
     removePersistStorageItem(sqliteDb, message.key);
+    throttledPersist();
     return;
   }