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.
Related Articles
If you’re building PWA features, you might also want to learn how to add PWA support in Vue for complete offline functionality.



