Skip to main content
The official C#/.NET SDK for TrailBase provides a fully-typed, async client for accessing your TrailBase backend from .NET applications.

Installation

Install the TrailBase NuGet package:
dotnet add package TrailBase
Or via Package Manager Console:
Install-Package TrailBase

Requirements

  • .NET 8.0 or .NET 9.0
  • Compatible with Native AOT compilation

Initialization

Basic Client

using TrailBase;

var client = new Client("https://your-server.trailbase.io", null);

Client with Tokens

using TrailBase;

var tokens = new Tokens(
    auth_token: "your-auth-token",
    refresh_token: "your-refresh-token",
    csrf_token: "your-csrf-token"
);

var client = new Client("https://your-server.trailbase.io", tokens);

Authentication

Login

try
{
    var tokens = await client.Login("[email protected]", "password");
    Console.WriteLine($"Auth token: {tokens.auth_token}");
    
    var user = client.User();
    if (user != null)
    {
        Console.WriteLine($"Logged in as: {user.email}");
    }
}
catch (Exception ex)
{
    Console.WriteLine($"Login failed: {ex.Message}");
}

Logout

await client.Logout();

Current User

var user = client.User();
if (user != null)
{
    Console.WriteLine($"User ID: {user.sub}");
    Console.WriteLine($"Email: {user.email}");
}

Access Tokens

var tokens = client.Tokens();
if (tokens != null)
{
    // Persist tokens for later use
    var json = System.Text.Json.JsonSerializer.Serialize(tokens);
    await File.WriteAllTextAsync("tokens.json", json);
}

Refresh Token

await client.RefreshAuthToken();

Record API

Define Your Record Types

using System.Text.Json.Serialization;

public class Post
{
    [JsonPropertyName("id")]
    public string Id { get; set; } = "";
    
    [JsonPropertyName("title")]
    public string Title { get; set; } = "";
    
    [JsonPropertyName("content")]
    public string Content { get; set; } = "";
    
    [JsonPropertyName("author_id")]
    public string AuthorId { get; set; } = "";
    
    [JsonPropertyName("created_at")]
    public long CreatedAt { get; set; }
}

public class NewPost
{
    [JsonPropertyName("title")]
    public string Title { get; set; } = "";
    
    [JsonPropertyName("content")]
    public string Content { get; set; } = "";
}

List Records

var posts = client.Records("posts");

var response = await posts.List<Post>(
    limit: 10,
    offset: 0,
    order: new[] { "-created_at" },
    count: true
);

Console.WriteLine($"Records: {response.records.Count}");
Console.WriteLine($"Total count: {response.total_count}");
Console.WriteLine($"Next cursor: {response.cursor}");

Read a Record

// String ID
var post = await posts.Read<Post>("post-id");

// Integer ID
var post = await posts.Read<Post>(123);

// With expanded relationships
var postWithAuthor = await posts.Read<Post>(
    "post-id",
    expand: new[] { "author" }
);

Console.WriteLine($"Title: {post.Title}");

Create a Record

var newPost = new NewPost
{
    Title = "Hello World",
    Content = "My first post from C#"
};

var postId = await posts.Create(newPost);
Console.WriteLine($"Created post with ID: {postId}");

Create Multiple Records

var newPosts = new[]
{
    new NewPost { Title = "Post 1", Content = "Content 1" },
    new NewPost { Title = "Post 2", Content = "Content 2" }
};

var ids = await posts.CreateBulk(newPosts);
Console.WriteLine($"Created {ids.Count} posts");

Update a Record

var updates = new Dictionary<string, object>
{
    { "title", "Updated Title" }
};

await posts.Update("post-id", updates);

Delete a Record

await posts.Delete("post-id");

Filtering

using TrailBase;

// Simple equality filter
var filters = new[]
{
    new Filter(column: "author_id", value: userId)
};

var response = await posts.List<Post>(filters: filters);

// With comparison operators
var weekAgo = DateTimeOffset.UtcNow.AddDays(-7).ToUnixTimeSeconds();
var recentPosts = await posts.List<Post>(
    filters: new[]
    {
        new Filter(
            column: "created_at",
            op: CompareOp.GreaterThan,
            value: weekAgo.ToString()
        )
    }
);

// LIKE operator for text search
var searchResults = await posts.List<Post>(
    filters: new[]
    {
        new Filter(
            column: "title",
            op: CompareOp.Like,
            value: "%search%"
        )
    }
);

// AND composite filter
var filtered = await posts.List<Post>(
    filters: new[]
    {
        new And(new[]
        {
            new Filter(column: "status", value: "published"),
            new Filter(column: "author_id", value: userId)
        })
    }
);

// OR composite filter
var filtered = await posts.List<Post>(
    filters: new[]
    {
        new Or(new[]
        {
            new Filter(column: "category", value: "tech"),
            new Filter(column: "category", value: "science")
        })
    }
);

Available Comparison Operators

public enum CompareOp
{
    Equal,
    NotEqual,
    LessThan,
    LessThanEqual,
    GreaterThan,
    GreaterThanEqual,
    Like,
    Regexp,
    StWithin,      // Geospatial
    StIntersects,  // Geospatial
    StContains     // Geospatial
}

Real-time Subscriptions

Subscribe to record changes using Server-Sent Events:
// Subscribe to a single record
await foreach (var @event in posts.Subscribe("post-id"))
{
    if (@event.Insert != null)
    {
        Console.WriteLine($"Record inserted: {@event.Insert}");
    }
    else if (@event.Update != null)
    {
        Console.WriteLine($"Record updated: {@event.Update}");
    }
    else if (@event.Delete != null)
    {
        Console.WriteLine($"Record deleted: {@event.Delete}");
    }
    else if (@event.Error != null)
    {
        Console.WriteLine($"Error: {@event.Error}");
    }
}

Error Handling

using System.Net.Http;

try
{
    var post = await posts.Read<Post>("post-id");
}
catch (HttpRequestException ex)
{
    Console.WriteLine($"HTTP error: {ex.StatusCode}");
    Console.WriteLine($"Message: {ex.Message}");
}
catch (Exception ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}

Advanced Usage

Custom Fetch

var response = await client.Fetch(
    "api/custom/endpoint",
    HttpMethod.Post,
    new StringContent(
        System.Text.Json.JsonSerializer.Serialize(new { key = "value" }),
        System.Text.Encoding.UTF8,
        "application/json"
    )
);

var data = await response.Content.ReadAsStringAsync();

Pagination

var pagination = new Pagination
{
    Limit = 20,
    Offset = 0
};

var response = await posts.List<Post>(pagination: pagination);

// Cursor-based pagination
var nextPage = await posts.List<Post>(
    pagination: new Pagination { Cursor = response.cursor }
);

Type Definitions

User

public class User
{
    public string sub { get; }
    public string email { get; }
    
    public User(string sub, string email)
    {
        this.sub = sub;
        this.email = email;
    }
}

Tokens

public class Tokens
{
    public string auth_token { get; }
    public string? refresh_token { get; }
    public string? csrf_token { get; }
    
    public Tokens(string auth_token, string? refresh_token, string? csrf_token)
    {
        this.auth_token = auth_token;
        this.refresh_token = refresh_token;
        this.csrf_token = csrf_token;
    }
}

ListResponse

public class ListResponse<T>
{
    public string? cursor { get; set; }
    public int? total_count { get; set; }
    public List<T> records { get; set; }
}

Pagination

public class Pagination
{
    public string? Cursor { get; set; }
    public int? Limit { get; set; }
    public int? Offset { get; set; }
}

Best Practices

Use nullable reference types (enabled by default in modern .NET) for better null safety.
Store tokens securely using Windows Credential Manager, Azure Key Vault, or similar secure storage.
The SDK is AOT-compatible and works with Native AOT compilation for improved startup time and reduced memory usage.

Example Application

using TrailBase;
using System.Text.Json.Serialization;

public class Post
{
    [JsonPropertyName("id")]
    public string Id { get; set; } = "";
    
    [JsonPropertyName("title")]
    public string Title { get; set; } = "";
    
    [JsonPropertyName("content")]
    public string Content { get; set; } = "";
    
    [JsonPropertyName("published")]
    public bool Published { get; set; }
}

class Program
{
    static async Task Main(string[] args)
    {
        // Initialize client
        var url = Environment.GetEnvironmentVariable("TRAILBASE_URL") 
            ?? "http://localhost:4000";
        var client = new Client(url, null);
        
        // Login
        try
        {
            await client.Login(
                Environment.GetEnvironmentVariable("TRAILBASE_EMAIL")!,
                Environment.GetEnvironmentVariable("TRAILBASE_PASSWORD")!
            );
            
            var user = client.User();
            Console.WriteLine($"Logged in as: {user?.email}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Login failed: {ex.Message}");
            return;
        }
        
        // List posts
        var posts = client.Records("posts");
        var response = await posts.List<Post>(
            order: new[] { "-created_at" },
            limit: 10,
            filters: new[]
            {
                new Filter(column: "published", value: "true")
            }
        );
        
        Console.WriteLine($"\nFound {response.records.Count} posts:");
        foreach (var post in response.records)
        {
            Console.WriteLine($"- {post.Title}");
        }
        
        // Create a new post
        var newPost = new Dictionary<string, object>
        {
            { "title", "Hello from C#" },
            { "content", "This post was created using the TrailBase C# SDK" },
            { "published", true }
        };
        
        var newPostId = await posts.Create(newPost);
        Console.WriteLine($"\nCreated new post with ID: {newPostId}");
        
        // Read the post
        var post = await posts.Read<Post>(newPostId);
        Console.WriteLine($"Post title: {post.Title}");
        
        // Logout
        await client.Logout();
        Console.WriteLine("\nLogged out");
    }
}

Build docs developers (and LLMs) love