How to use Vue with IndexedDB

IndexedDB provides powerful client-side storage for Vue applications, enabling offline functionality and large data caching. As the creator of CoreUI with 12 years of Vue development experience, I’ve built Vue PWAs that serve millions of users with reliable offline support.

The most maintainable approach is to create a composable that wraps IndexedDB operations with Vue’s reactivity system.

Install Idb Library

For easier IndexedDB usage, install the idb wrapper:

npm install idb

Create IndexedDB Composable

Create src/composables/useIndexedDB.js:

import { ref } from 'vue'
import { openDB } from 'idb'

export const useIndexedDB = (dbName, storeName) => {
  const data = ref([])
  const loading = ref(false)
  const error = ref(null)

  const initDB = async () => {
    return await openDB(dbName, 1, {
      upgrade(db) {
        if (!db.objectStoreNames.contains(storeName)) {
          db.createObjectStore(storeName, { keyPath: 'id', autoIncrement: true })
        }
      }
    })
  }

  const add = async (item) => {
    loading.value = true
    try {
      const db = await initDB()
      await db.add(storeName, item)
      await getAll()
    } catch (err) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }

  const getAll = async () => {
    loading.value = true
    try {
      const db = await initDB()
      data.value = await db.getAll(storeName)
    } catch (err) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }

  const update = async (id, updates) => {
    loading.value = true
    try {
      const db = await initDB()
      const item = await db.get(storeName, id)
      await db.put(storeName, { ...item, ...updates })
      await getAll()
    } catch (err) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }

  const remove = async (id) => {
    loading.value = true
    try {
      const db = await initDB()
      await db.delete(storeName, id)
      await getAll()
    } catch (err) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }

  return {
    data,
    loading,
    error,
    add,
    getAll,
    update,
    remove
  }
}

Use in Component

<script setup>
import { onMounted } from 'vue'
import { useIndexedDB } from '@/composables/useIndexedDB'

const { data, loading, error, add, getAll, remove } = useIndexedDB('myApp', 'todos')

onMounted(async () => {
  await getAll()
})

const addTodo = async () => {
  await add({ title: 'New todo', completed: false })
}

const deleteTodo = async (id) => {
  await remove(id)
}
</script>

<template>
  <div>
    <h2>Todos</h2>

    <div v-if="loading">Loading...</div>
    <div v-if="error" class="error">{{ error }}</div>

    <button @click="addTodo">Add Todo</button>

    <ul>
      <li v-for="item in data" :key="item.id">
        {{ item.title }}
        <button @click="deleteTodo(item.id)">Delete</button>
      </li>
    </ul>
  </div>
</template>

Best Practice Note

This is the same IndexedDB pattern we use in CoreUI’s Vue admin templates for offline-first applications. The composable approach keeps your components clean while providing full reactivity with Vue’s ref system.

For production applications, consider using CoreUI’s Vue Admin Template which includes pre-built IndexedDB integration, PWA support, and offline sync capabilities.

If you’re building PWA features, you might also want to learn how to add PWA support in Vue for complete offline functionality.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.

Answers by CoreUI Core Team