IndexedDB storage

IndexedDB is a powerful NoSQL database embedded in the modern browsers. Using IndexedDB can improve your website performance vastly. In this tutorial, you will discover:

  • What is IndexedDB storage?
  • Set up the base code.
  • Add common operations.
  • Use the IndexedDB storage.
You can download the example code used in this topic on GitHub.

What is IndexedDB storage?

IndexedDB as its name suggested, uses indexes to enable high performance queries. IndexedDB also allows you to store various data type as key and value. IndexedDB provides many useful features:

  • Transaction.
  • Data migration.
  • Data versioning.
  • Indexing.
  • Asynchronous.

IndexedDB can also help you to improve your website performance by caching files, large set of data and so on.

Understanding IndexedDB

IndexedDB isolates data by the domain. That being said, one domain cannot access another domain's resources. For example, data created by blazorschool.com cannot be accessed by google.com and vice versa.

IndexedDB manages data like folders, but with a fixed level. There are 3 levels in IndexedDB:

  1. Domain: A domain can contain one or more database.
  2. Database: A database can contain one or more Object Store.
  3. Object Store: An Object Store is where your data is actually stored under key and value pair.

In this tutorial, we only provide a basic example on how to store and get the value from the IndexedDB and the example will not demonstrate all the possibilities of the IndexedDB.

How to access the IndexedDB storage in the browser?

You can access the IndexedDB storage in the browser by opening the Developer Tools (F12 for most browsers).

For Firefox, the IndexedDB storage is located at the Storage tab as the following image:

firefox-indexeddb-storage.png

For Chrome and Edge, the IndexedDB storage is located at the Application tab as the following images:

chrome-indexeddb-storage.png

edge-indexeddb-storage.png


Set up the base code

To use the IndexedDB storage, you first need to create a JavaScript module, then you will use C# code to call the exported functions of this module.

  1. Create a new JavaScript file under the wwwroot folder. In this example, we will create a /js/IndexedDbAccessor.js file.
  2. Add some base methods:
export function initialize()
{
    let blazorSchoolIndexedDb = indexedDB.open(DATABASE_NAME, CURRENT_VERSION);
    blazorSchoolIndexedDb.onupgradeneeded = function ()
    {
        let db = blazorSchoolIndexedDb.result;
        db.createObjectStore("books", { keyPath: "id" });
    }
}

let CURRENT_VERSION = 1;
let DATABASE_NAME = "Blazor School";
You can change the value of DATABASE_NAME, you need to increase the CURRENT_VERSION whenever you change your data structure, you can create one or more Object Store with different names than "books" as in the example.
  1. Create a C# class with the following base implementation:
public class IndexedDbAccessor : IAsyncDisposable
{
    private Lazy<IJSObjectReference> _accessorJsRef = new();
    private readonly IJSRuntime _jsRuntime;

    public IndexedDbAccessor(IJSRuntime jsRuntime)
    {
        _jsRuntime = jsRuntime;
    }

    public async Task InitializeAsync()
    {
        await WaitForReference();
        await _accessorJsRef.Value.InvokeVoidAsync("initialize");
    }

    private async Task WaitForReference()
    {
        if (_accessorJsRef.IsValueCreated is false)
        {
            _accessorJsRef = new(await _jsRuntime.InvokeAsync<IJSObjectReference>("import", "/js/IndexedDbAccessor.js"));
        }
    }

    public async ValueTask DisposeAsync()
    {
        if (_accessorJsRef.IsValueCreated)
        {
            await _accessorJsRef.Value.DisposeAsync();
        }
    }
}
Always remember to dispose the JavaScript module.
  1. Register the C# class at Program.cs
builder.Services.AddScoped<IndexedDbAccessor>();
  1. Initialize the IndexedDB at App.razor
@inject IndexedDbAccessor IndexedDbAccessor

...

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await IndexedDbAccessor.InitializeAsync();
    }
}

Add common operations

In this section, we will demonstrate how to add some basic operations with the IndexedDB storage. You can also add your own operations with this method too.

  1. In your JavaScript module, add functions to store and get the data:
export function set(collectionName, value)
{
    let blazorSchoolIndexedDb = indexedDB.open(DATABASE_NAME, CURRENT_VERSION);

    blazorSchoolIndexedDb.onsuccess = function ()
    {
        let transaction = blazorSchoolIndexedDb.result.transaction(collectionName, "readwrite");
        let collection = transaction.objectStore(collectionName)
        collection.put(value);
    }
}

export async function get(collectionName, id)
{
    let request = new Promise((resolve) =>
    {
        let blazorSchoolIndexedDb = indexedDB.open(DATABASE_NAME, CURRENT_VERSION);
        blazorSchoolIndexedDb.onsuccess = function ()
        {
            let transaction = blazorSchoolIndexedDb.result.transaction(collectionName, "readonly");
            let collection = transaction.objectStore(collectionName);
            let result = collection.get(id);

            result.onsuccess = function (e)
            {
                resolve(result.result);
            }
        }
    });

    let result = await request;

    return result;
}
  1. In your C# class, create a new method for each operation. You will need to call WaitForReference() in all methods.
public class IndexedDbAccessor : IAsyncDisposable
{
    ...
    public async Task<T> GetValueAsync<T>(string collectionName, int id)
    {
        await WaitForReference();
        var result = await _accessorJsRef.Value.InvokeAsync<T>("get", collectionName, id);

        return result;
    }

    public async Task SetValueAsync<T>(string collectionName, T value)
    {
        await WaitForReference();
        await _accessorJsRef.Value.InvokeVoidAsync("set", collectionName, value);
    }
}

Use the IndexedDB storage

Once you have the complete all the previous steps, you can use it as follows:

@using System.Text.Json
@inject IndexedDbAccessor IndexedDbAccessor

<form>
    <label class="form-label">
        Book ID:
        <input class="form-control" type="text" @bind-value="BookId" />
    </label>
    <label class="form-label">
        Book Name:
        <input class="form-control" type="text" @bind-value="BookName" />
    </label>
    <button class="btn btn-primary" type="button" @onclick="SetValueAsync">Set Value</button>
</form>
<div>Stored Value: @StoredValue</div>
<button class="btn btn-primary" type="button" @onclick="GetValueAsync">Get Value</button>

@code {
    public string BookId { get; set; } = "";
    public string BookName { get; set; } = "";
    public string StoredValue { get; set; } = "";

    public async Task SetValueAsync()
    {
        await IndexedDbAccessor.SetValueAsync("books", new { Id = Convert.ToInt32(BookId), Name = BookName });
    }

    public async Task GetValueAsync()
    {
        JsonDocument storedBook = await IndexedDbAccessor.GetValueAsync<JsonDocument>("books", Convert.ToInt32(BookId));
        StoredValue = storedBook.RootElement.GetProperty("name").GetString() ?? "";
    }
}
BLAZOR SCHOOL
Designed and built with care by our dedicated team, with contributions from a supportive community. We strive to provide the best learning experience for our users.
Docs licensed CC-BY-SA-4.0
Copyright © 2021-2025 Blazor School
An unhandled error has occurred. Reload 🗙