Skip to main content
TursoDb provides a powerful, type-safe wrapper around libSQL for Blazor WebAssembly applications. It enables local-first development with SQLite stored in the browser, with optional synchronization to Turso Cloud.

Local-First

Data is stored locally in the browser using SQLite, ensuring your app works offline.

Cloud Sync

Optionally sync with Turso Cloud for multi-device and real-time collaboration.

Type-Safe

Entity attributes and strongly-typed DbSets provide compile-time safety.

Auto Schema

Tables and indexes are automatically created from your entity definitions.

Installation

1

Install the NuGet package

dotnet add package BlazorEssentials.TursoDb
2

Install the Server package (required)

TursoDb requires Cross-Origin Isolation headers for SharedArrayBuffer support.
dotnet add package BlazorEssentials.Server
3

Configure middleware

Add the Cross-Origin Isolation middleware to your server’s Program.cs:
Program.cs
var app = builder.Build();

// Add before other middleware
app.UseCrossOriginIsolation();

app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
// ... rest of your middleware
Cross-Origin Isolation may break third-party scripts, iframes, and CDN resources that aren’t configured for isolated contexts. Test thoroughly before deploying.

Quick Start

Define Your Entities

Use attributes to define how your C# classes map to SQLite tables:
Models/User.cs
using CloudNimble.BlazorEssentials.TursoDb;

[Table("users")]
public class User
{
    [PrimaryKey(AutoIncrement = true)]
    public long Id { get; set; }

    [Column("name")]
    public string Name { get; set; } = "";

    [Column("email")]
    [Index(Unique = true)]
    public string Email { get; set; } = "";

    [Column("age")]
    public int Age { get; set; }

    [Column("is_active")]
    public bool IsActive { get; set; } = true;

    [Column("created_at")]
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
Use [NotMapped] on properties you don’t want persisted to the database.

Create Your Database

Inherit from TursoDatabase and add TursoDbSet<T> properties for each entity:
Data/AppDatabase.cs
using CloudNimble.BlazorEssentials.TursoDb;
using Microsoft.JSInterop;

public class AppDatabase : TursoDatabase
{
    public TursoDbSet<User> Users { get; }
    public TursoDbSet<Post> Posts { get; }

    public AppDatabase(IJSRuntime jsRuntime) : base(jsRuntime)
    {
        Name = "myapp.db";
        Users = new TursoDbSet<User>(this);
        Posts = new TursoDbSet<Post>(this);
    }
}

Register and Use

builder.Services.AddScoped<AppDatabase>();

Entity Attributes

Maps a class to a specific table name in SQLite.
[Table("users")]
public class User { }
If omitted, the class name is used as the table name.
Marks a property as the primary key. Use AutoIncrement = true for auto-generated IDs.
[PrimaryKey(AutoIncrement = true)]
public long Id { get; set; }
Maps a property to a specific column name. Use snake_case for SQLite conventions.
[Column("created_at")]
public DateTime CreatedAt { get; set; }
Creates an index on the column for faster queries. Use Unique = true for unique constraints.
[Column("email")]
[Index(Unique = true)]
public string Email { get; set; }
Excludes a property from database persistence.
[NotMapped]
public string FullName => $"{FirstName} {LastName}";

CRUD Operations

Create

var user = new User
{
    Name = "John Doe",
    Email = "[email protected]",
    Age = 30
};

await Database.Users.AddAsync(user);
// user.Id is now populated with the auto-generated ID

Read

var allUsers = await Database.Users.ToListAsync();

Update

var user = await Database.Users.FindAsync(1);
user.Name = "Jane Doe";
user.Age = 31;

await Database.Users.UpdateAsync(user);

Delete

var user = await Database.Users.FindAsync(1);
await Database.Users.RemoveAsync(user);

Query Builder

Build complex queries with the fluent query API:
var activeAdults = await Database.Users
    .Where("age >= ? AND is_active = ?", 18, true)
    .OrderBy("name")
    .Take(10)
    .ToListAsync();

Available Methods

MethodDescription
Where(clause, params)Filter results with a SQL WHERE clause
OrderBy(column)Sort ascending by column
OrderByDescending(column)Sort descending by column
Take(count)Limit results to count
Skip(count)Skip first count results
ToListAsync()Execute and return all results
FirstOrDefaultAsync()Execute and return first result or null
CountAsync()Return count of matching results
Use ? placeholders for parameters to prevent SQL injection. Parameters are passed in order after the SQL clause.

Transactions

Use transactions for atomic operations that should succeed or fail together:
await using var transaction = await Database.BeginTransactionAsync();
try
{
    await Database.Users.AddAsync(new User { Name = "User 1" });
    await Database.Users.AddAsync(new User { Name = "User 2" });

    await transaction.CommitAsync();
}
catch
{
    await transaction.RollbackAsync();
    throw;
}
If you dispose the transaction without calling CommitAsync(), it automatically rolls back.

Prepared Statements

For frequently executed queries, use prepared statements for better performance:
var statement = Database.Prepare<User>("SELECT * FROM users WHERE age > ?");

var youngUsers = await statement.QueryAsync(18);
var olderUsers = await statement.QueryAsync(65);

await statement.DisposeAsync();

Raw SQL

Execute raw SQL when you need full control:
var result = await Database.ExecuteAsync(
    "INSERT INTO users (name, email) VALUES (?, ?)",
    "John", "[email protected]"
);

Console.WriteLine($"Rows affected: {result.RowsAffected}");
Console.WriteLine($"Last insert ID: {result.LastInsertRowId}");

Turso Cloud Sync

For multi-device sync and cloud backup, use TursoSyncDatabase:

Setup

Data/SyncedAppDatabase.cs
using CloudNimble.BlazorEssentials.TursoDb;
using Microsoft.JSInterop;

public class SyncedAppDatabase : TursoSyncDatabase
{
    public TursoDbSet<User> Users { get; }

    public SyncedAppDatabase(IJSRuntime jsRuntime) : base(jsRuntime)
    {
        Name = "myapp.db";
        SyncUrl = "libsql://mydb-myorg.turso.io";
        SyncAuthToken = "your-auth-token";
        Users = new TursoDbSet<User>(this);
    }
}
Never hardcode auth tokens in production code. Use configuration or secure storage.

Sync Operations

// Download changes from Turso Cloud
var result = await Database.PullAsync();
Console.WriteLine($"Synced {result.FramesSynced} frames in {result.DurationMs}ms");

Sync Events

Subscribe to sync lifecycle events:
Database.SyncStarted += (sender, e) =>
{
    Console.WriteLine("Sync started...");
};

Database.SyncCompleted += (sender, result) =>
{
    Console.WriteLine($"Sync completed: {result.FramesSynced} frames");
};

Database.SyncFailed += (sender, ex) =>
{
    Console.WriteLine($"Sync failed: {ex.Message}");
};

Sync Options

PropertyDescriptionDefault
SyncUrlTurso Cloud database URLRequired
SyncAuthTokenAuthentication tokenRequired
SyncOnConnectSync automatically on connecttrue
SyncIntervalMsAuto-sync interval (0 = disabled)0
EncryptionEnabledEnable local database encryptionfalse
EncryptionKeyEncryption key for local databasenull

Cross-Origin Isolation

TursoDb requires Cross-Origin Isolation headers for SharedArrayBuffer support. The BlazorEssentials.Server package provides middleware to add these headers.

Basic Configuration

app.UseCrossOriginIsolation();

Advanced Configuration

app.UseCrossOriginIsolation(options =>
{
    // Use less strict policy for better compatibility
    options.CoepPolicy = "credentialless";

    // Exclude paths that need third-party resources
    options.ExcludePaths.Add("/api/");
    options.ExcludePaths.Add("/external/");

    // Disable CORP header if causing issues
    options.IncludeCorpHeader = false;
});

Headers Added

HeaderValuePurpose
Cross-Origin-Opener-Policysame-originIsolates browsing context
Cross-Origin-Embedder-Policyrequire-corpRequires resources to grant permission
Cross-Origin-Resource-Policysame-originControls resource sharing

Type Mapping

TursoDb automatically maps C# types to SQLite types:
C# TypeSQLite Type
int, long, short, byteINTEGER
float, double, decimalREAL
stringTEXT
boolINTEGER (0/1)
DateTime, DateTimeOffsetTEXT (ISO 8601)
GuidTEXT
byte[]BLOB

Best Practices

Register your database as a scoped service for proper lifecycle management:
builder.Services.AddScoped<AppDatabase>();
Connect to the database early in your app lifecycle, such as in App.razor or a layout component:
protected override async Task OnInitializedAsync()
{
    await Database.ConnectAsync();
}
When using cloud sync, implement conflict resolution strategies for your use case. Consider using timestamps or version numbers to detect conflicts.

Troubleshooting

This error indicates Cross-Origin Isolation is not configured. Ensure:
  1. BlazorEssentials.Server is installed
  2. app.UseCrossOriginIsolation() is called before other middleware
  3. Headers are not being stripped by a proxy or CDN
Cross-Origin Isolation may break external scripts. Options:
  1. Use options.CoepPolicy = "credentialless" for less strict isolation
  2. Add script paths to options.ExcludePaths
  3. Ensure external resources have Cross-Origin-Resource-Policy headers
Verify your Turso Cloud credentials:
  1. Check SyncUrl format: libsql://[db-name]-[org].turso.io
  2. Ensure SyncAuthToken is valid and not expired
  3. Verify the database exists in your Turso dashboard

API Reference