Storing Data Locally with IndexedDB: Overview and ExamplessteemCreated with Sketch.

in #coding2 months ago

IndexedDB

IndexedDB is basically a built-in database inside your browser — and honestly, it’s pretty simple once you poke around a bit. Anyone who’s ever used LocalStorage knows how fast it hits its limits. It’s dead simple and kinda restricted: a key, a value, and about 5 MB of space. If you just need to save a token or a couple of settings — fine. But if you want to store something actually useful, like client lists, API responses, images, or anything bigger — LocalStorage just can’t handle that. So… IndexedDB.

IndexedDB works asynchronously and can store objects, arrays, files — pretty much anything. It’s kinda like NoSQL, but running right inside your browser. You can use it for offline mode, caching, storing user data — and everything stays local. Nothing leaves the user’s device unless you decide to send it somewhere yourself.

The structure is simple, there’s a database , inside it there are object stores and those contain records with keys. You can create indexes to speed up lookups.It’s like tables, but without the strict SQL-style structure. Each store is like a MongoDB collection full of objects.

When you open a database, you specify it’s name and version. Example:

const request = indexedDB.open('MyDatabase', 1);

request.onupgradeneeded = (event) => {
  const db = event.target.result;
  const store = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
  store.createIndex('name', 'name', { unique: false });
};

request.onsuccess = () => console.log('Database ready');

 
Once the database is ready you can start adding data. All operations go through transactions. For example to add users you’d do:

const db = request.result;
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
store.add({ name: 'Max', role: 'Worker' });
tx.oncomplete = () => console.log('User added');

 
To fetch data you also open a transaction, but in readonly mode. For example:

const tx = db.transaction('users', 'readonly');
const store = tx.objectStore('users');
const req = store.get(1);

req.onsuccess = () => {
  console.log(req.result);
};

 
If you’ve already created an index you can query by it. For instance get all users with same role:

const index = store.index('name');
const req = index.getAll('Max');

req.onsuccess = () => {
  console.log(req.result);
};

 
Updating and deleting are easy. To update use put(), if a record with that key already exists it’ll get replaced:

store.put({ id: 1, name: 'Max', role: 'Updated' });

 
To delete:

store.delete(1);  // delete record by key

 
If you want to clear the whole store, just call store.clear().

When you need to loop through all records, you use cursors .They act like iterators you move through all keys in the store, grab objects, and do whatever you need.

const store = db.transaction('users').objectStore('users');
store.openCursor().onsuccess = (e) => {
  const cursor = e.target.result;
  if (cursor) {
    console.log(cursor.key, cursor.value);
    cursor.continue();
  }
};

 
Cursors are handy when you have hundreds of records and don’t want to pull everything at once with getAll().

Now about temporary data. IndexedDB doesn’t have a built-in “time-to-live” feature, but you can fake it easily. Just store an expires field and check it when reading. Example:

store.put({ key: 'token', value: 'abcd', expires: Date.now() + 2 * 60 * 1000 });

 
Then when reading check if it’s expired, and if so delete it:

if (Date.now() > record.expires) store.delete(record.key);

 
That’s perfect for temporary notifications, caching or short-lived tokens. Simple and reliable.

IndexedDB is often used for offline mode. For instance if a user opens your app with no internet you can load the last saved data from IndexedDB, show it, and once the connection’s back sync with the server. The app keeps running just fine.

And... and IndexedDB can store not only text, but also files. You can write Blob objects, for example, images and then open them later:

const blob = new Blob(["Hello world!"], { type: "text/plain" });
store.add({ id: 1, file: blob });

 
Then retrieve it:

const record = await store.get(1);
const url = URL.createObjectURL(record.file);
window.open(url);

 
Example of saving and loading files through IndexedDB:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>IndexedDB File Storage Example</title>
  <style>
    body {
      font-family: sans-serif;
      padding: 20px;
      background: #f8f8f8;
    }
    input, button {
      margin-top: 10px;
      padding: 6px 12px;
      font-size: 14px;
    }
    .file-item {
      margin-top: 10px;
      background: #fff;
      border-radius: 8px;
      padding: 10px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
    .file-item a {
      margin-left: 10px;
      text-decoration: none;
      color: #007bff;
    }
  </style>
</head>
<body>

  <h2>IndexedDB File Storage</h2>
  <p>Select a file to store it in IndexedDB, then reload the page — it will still be there.</p>

  <input type="file" id="fileInput" />
  <button id="saveBtn">Save to IndexedDB</button>

  <h3>Stored files:</h3>
  <div id="fileList"></div>

<script>
  // --- Open (or create) the database ---
  function openDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open('FileDB', 1);
      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        db.createObjectStore('files', { keyPath: 'id', autoIncrement: true });
      };
      request.onsuccess = (event) => resolve(event.target.result);
      request.onerror = (event) => reject(event.target.error);
    });
  }

  // --- Save file to DB ---
  async function saveFile(file) {
    const db = await openDB();
    const tx = db.transaction('files', 'readwrite');
    const store = tx.objectStore('files');

    const record = {
      name: file.name,
      type: file.type,
      blob: file,
      savedAt: new Date().toISOString()
    };

    store.add(record);

    return new Promise((resolve) => {
      tx.oncomplete = () => resolve(true);
    });
  }

  // --- Get all files from DB ---
  async function getFiles() {
    const db = await openDB();
    const tx = db.transaction('files', 'readonly');
    const store = tx.objectStore('files');
    return new Promise((resolve) => {
      const req = store.getAll();
      req.onsuccess = () => resolve(req.result);
    });
  }

  // --- Display stored files ---
  async function showFiles() {
    const files = await getFiles();
    const container = document.getElementById('fileList');
    container.innerHTML = '';

    if (!files.length) {
      container.innerHTML = '<p>No files stored.</p>';
      return;
    }

    files.forEach((file) => {
      const div = document.createElement('div');
      div.className = 'file-item';
      const url = URL.createObjectURL(file.blob);

      div.innerHTML = `
        <strong>${file.name}</strong>
        <a href="${url}" download="${file.name}">Download</a>
        <a href="${url}" target="_blank">Open</a>
      `;

      container.appendChild(div);
    });
  }

  // --- Event handlers ---
  document.getElementById('saveBtn').addEventListener('click', async () => {
    const fileInput = document.getElementById('fileInput');
    const file = fileInput.files[0];
    if (!file) return alert('Please choose a file first.');
    await saveFile(file);
    alert('File saved to IndexedDB!');
    fileInput.value = '';
    showFiles();
  });

  // --- On page load ---
  showFiles();

</script>

</body>
</html>

 
When user picks a file through <input type="file"> that file comes in as a 'File object' and gets stored in IndexedDB as a Blob. Along with it you can save the file’s name, type, and upload time. Super convenient — you can easily restore everything even after reloading the page. Each time the page loads, the script just opens the DB, reads all records, and lists them. Each file gets two links — one to download, another to open right in the browser (if it supports the file type).

Of course, IndexedDB has its quirks. Data is only available within the same domain, users can clear their storage,and browsers limit it heavily in private mode. If you’re storing personal or sensitive data, you should encrypt it.

Check if the browser supports IndexedDB:

if (!('indexedDB' in window)) alert('No IndexedDB support!');

 
Delete database:

indexedDB.deleteDatabase('MyDatabase');

 
Clean up expired records:

const tx = db.transaction('cache', 'readwrite');
const store = tx.objectStore('cache');
store.openCursor().onsuccess = e => {
  const c = e.target.result;
  if (c && Date.now() > c.value.expires) c.delete();
  if (c) c.continue();
};

 
IndexedDB is a solid frontend tool that lets you store large amounts of data right in browser without relying on a server.

And finally a last example showing how to temporarily hide a block element (div) — like a popup notification. When you hit “Hide” the popup disappears and IndexedDB saves a flag saying it shouldn’t be shown for the next two minutes. After that even if you refresh the page, it stays hidden until the timer’s up. Once those two minutes are over, the record expires and the popup reappears on the next load:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>IndexedDB Popup Example</title>
  <style>
    #popup {
      position: fixed;
      top: 20px;
      right: 20px;
      background: #2b2b2b;
      color: white;
      padding: 20px;
      border-radius: 10px;
      box-shadow: 0 4px 12px rgba(0,0,0,0.3);
      display: none;
      max-width: 300px;
      font-family: sans-serif;
      z-index: 9999;
    }
    #popup button {
      background: #ff4c4c;
      color: white;
      border: none;
      padding: 8px 12px;
      margin-top: 10px;
      border-radius: 5px;
      cursor: pointer;
    }
    #popup button:hover {
      background: #ff7070;
    }
  </style>
</head>
<body>

<div id="popup">
  <p><b>Message:</b> You can hide this popup for 2 minutes.</p>
  <button id="closeBtn">Hide</button>
</div>

<script>
  // === IndexedDB Helper Functions ===
  function openDB() {
    return new Promise((resolve, reject) => {
      const req = indexedDB.open('PopupDB', 1);
      req.onupgradeneeded = e => {
        const db = e.target.result;
        db.createObjectStore('popup', { keyPath: 'key' });
      };
      req.onsuccess = e => resolve(e.target.result);
      req.onerror = e => reject(e);
    });
  }

  async function saveHideFlag() {
    const db = await openDB();
    const tx = db.transaction('popup', 'readwrite');
    const store = tx.objectStore('popup');
    const expires = Date.now() + 2 * 60 * 1000; // 2 minutes
    store.put({ key: 'hideUntil', expires });
    return tx.complete;
  }

  async function getHideFlag() {
    const db = await openDB();
    const tx = db.transaction('popup', 'readonly');
    const store = tx.objectStore('popup');
    return new Promise((resolve) => {
      const req = store.get('hideUntil');
      req.onsuccess = () => {
        const record = req.result;
        if (!record) return resolve(false);
        if (Date.now() > record.expires) {
          // Expired — delete it
          const delTx = db.transaction('popup', 'readwrite');
          delTx.objectStore('popup').delete('hideUntil');
          resolve(false);
        } else {
          resolve(true);
        }
      };
    });
  }

  // === Main Logic ===
  const popup = document.getElementById('popup');
  const closeBtn = document.getElementById('closeBtn');

  async function checkPopup() {
    const hidden = await getHideFlag();
    if (!hidden) popup.style.display = 'block';
  }

  closeBtn.addEventListener('click', async () => {
    popup.style.display = 'none';
    await saveHideFlag();
  });

  checkPopup(); // Run on page load
</script>

</body>
</html>

 

  • Image source: AI generated, Gemini.
Sort:  

Upvoted! Thank you for supporting witness @jswit.