Skip to main content
The official Go SDK for TrailBase provides a type-safe client for accessing your TrailBase backend from Go applications.

Installation

Install using go get:
go get github.com/trailbaseio/trailbase/client/go/trailbase

Requirements

  • Go 1.24.4+

Initialization

Basic Client

package main

import (
    "github.com/trailbaseio/trailbase/client/go/trailbase"
)

func main() {
    client, err := trailbase.NewClient("https://your-server.trailbase.io")
    if err != nil {
        panic(err)
    }
    defer client.Close()
}

Client with Tokens

import "github.com/trailbaseio/trailbase/client/go/trailbase"

refreshToken := "your-refresh-token"
tokens := &trailbase.Tokens{
    AuthToken:    "your-auth-token",
    RefreshToken: &refreshToken,
    CsrfToken:    nil,
}

client, err := trailbase.NewClientWithTokens(
    "https://your-server.trailbase.io",
    tokens,
)
if err != nil {
    panic(err)
}
defer client.Close()

Authentication

Login

tokens, err := client.Login("[email protected]", "password")
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Auth token: %s\n", tokens.AuthToken)

user := client.User()
if user != nil {
    fmt.Printf("Logged in as: %s\n", user.Email)
}

Logout

err := client.Logout()
if err != nil {
    log.Fatal(err)
}

Current User

user := client.User()
if user != nil {
    fmt.Printf("User ID: %s\n", user.Sub)
    fmt.Printf("Email: %s\n", user.Email)
}

Access Tokens

tokens := client.Tokens()
if tokens != nil {
    // Persist tokens for later use
    data, _ := json.Marshal(tokens)
    os.WriteFile("tokens.json", data, 0600)
}

Refresh Token

err := client.Refresh()
if err != nil {
    log.Fatal(err)
}

Record API

Define Your Record Types

type Post struct {
    ID        string `json:"id"`
    Title     string `json:"title"`
    Content   string `json:"content"`
    AuthorID  string `json:"author_id"`
    CreatedAt int64  `json:"created_at"`
}

type NewPost struct {
    Title   string `json:"title"`
    Content string `json:"content"`
}

List Records

import "github.com/trailbaseio/trailbase/client/go/trailbase"

posts := trailbase.NewRecordApi(client, "posts")

response, err := posts.List(
    &trailbase.ListOptions{
        Limit:  10,
        Offset: 0,
        Order:  []string{"-created_at"},
        Count:  true,
    },
)
if err != nil {
    log.Fatal(err)
}

var postList []Post
err = json.Unmarshal(response.Records, &postList)

fmt.Printf("Records: %d\n", len(postList))
fmt.Printf("Total count: %v\n", response.TotalCount)
fmt.Printf("Next cursor: %v\n", response.Cursor)

Read a Record

// String ID
data, err := posts.Read("post-id", nil)
if err != nil {
    log.Fatal(err)
}

var post Post
err = json.Unmarshal(data, &post)

fmt.Printf("Title: %s\n", post.Title)

Read with Expansion

data, err := posts.Read("post-id", &trailbase.ReadOptions{
    Expand: []string{"author"},
})
if err != nil {
    log.Fatal(err)
}

var post Post
json.Unmarshal(data, &post)

Create a Record

newPost := NewPost{
    Title:   "Hello World",
    Content: "My first post from Go",
}

postID, err := posts.Create(&newPost)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Created post with ID: %s\n", postID)

Create Multiple Records

newPosts := []NewPost{
    {Title: "Post 1", Content: "Content 1"},
    {Title: "Post 2", Content: "Content 2"},
}

ids, err := posts.CreateBulk(&newPosts)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Created %d posts\n", len(ids))

Update a Record

update := map[string]interface{}{
    "title": "Updated Title",
}

err := posts.Update("post-id", &update)
if err != nil {
    log.Fatal(err)
}

Delete a Record

err := posts.Delete("post-id")
if err != nil {
    log.Fatal(err)
}

Filtering

import "github.com/trailbaseio/trailbase/client/go/trailbase"

// Simple equality filter
filters := []trailbase.FilterOrComposite{
    trailbase.Filter{
        Column: "author_id",
        Value:  userID,
    },
}

response, err := posts.List(&trailbase.ListOptions{
    Filters: filters,
})

// With comparison operators
weekAgo := time.Now().AddDate(0, 0, -7).Unix()
filters = []trailbase.FilterOrComposite{
    trailbase.Filter{
        Column: "created_at",
        Op:     trailbase.CompareOpGreaterThan,
        Value:  fmt.Sprintf("%d", weekAgo),
    },
}

// LIKE operator for text search
filters = []trailbase.FilterOrComposite{
    trailbase.Filter{
        Column: "title",
        Op:     trailbase.CompareOpLike,
        Value:  "%search%",
    },
}

// AND composite filter
filters = []trailbase.FilterOrComposite{
    trailbase.And{
        Filters: []trailbase.FilterOrComposite{
            trailbase.Filter{Column: "status", Value: "published"},
            trailbase.Filter{Column: "author_id", Value: userID},
        },
    },
}

// OR composite filter
filters = []trailbase.FilterOrComposite{
    trailbase.Or{
        Filters: []trailbase.FilterOrComposite{
            trailbase.Filter{Column: "category", Value: "tech"},
            trailbase.Filter{Column: "category", Value: "science"},
        },
    },
}

Available Comparison Operators

const (
    CompareOpEqual              CompareOp = "$eq"
    CompareOpNotEqual           CompareOp = "$ne"
    CompareOpLessThan           CompareOp = "$lt"
    CompareOpLessThanEqual      CompareOp = "$lte"
    CompareOpGreaterThan        CompareOp = "$gt"
    CompareOpGreaterThanEqual   CompareOp = "$gte"
    CompareOpLike               CompareOp = "$like"
    CompareOpRegexp             CompareOp = "$re"
    CompareOpStWithin           CompareOp = "@within"      // Geospatial
    CompareOpStIntersects       CompareOp = "@intersects"  // Geospatial
    CompareOpStContains         CompareOp = "@contains"    // Geospatial
)

Error Handling

data, err := posts.Read("post-id", nil)
if err != nil {
    if httpErr, ok := err.(*trailbase.HttpError); ok {
        fmt.Printf("HTTP %d: %s\n", httpErr.StatusCode, httpErr.Message)
    } else {
        fmt.Printf("Error: %v\n", err)
    }
    return
}

var post Post
json.Unmarshal(data, &post)

Type Definitions

User

type User struct {
    Sub   string
    Email string
}

Tokens

type Tokens struct {
    AuthToken    string  `json:"auth_token"`
    RefreshToken *string `json:"refresh_token,omitempty"`
    CsrfToken    *string `json:"csrf_token,omitempty"`
}

ListResponse

type ListResponse struct {
    Cursor     *string         `json:"cursor,omitempty"`
    TotalCount *int            `json:"total_count,omitempty"`
    Records    json.RawMessage `json:"records"`
}

ListOptions

type ListOptions struct {
    Pagination *Pagination
    Order      []string
    Filters    []FilterOrComposite
    Count      bool
    Expand     []string
}

Pagination

type Pagination struct {
    Cursor *string
    Limit  *int
    Offset *int
}

Best Practices

Always check errors returned by SDK methods. Go’s explicit error handling ensures you don’t miss failure cases.
Store tokens securely and never commit them to version control. Consider using environment variables or secure credential storage.
The client automatically refreshes auth tokens before they expire through concurrent-safe token management.

Example Application

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "os"
    
    "github.com/trailbaseio/trailbase/client/go/trailbase"
)

type Post struct {
    ID        string `json:"id"`
    Title     string `json:"title"`
    Content   string `json:"content"`
    Published bool   `json:"published"`
}

func main() {
    // Initialize client
    url := os.Getenv("TRAILBASE_URL")
    if url == "" {
        url = "http://localhost:4000"
    }
    
    client, err := trailbase.NewClient(url)
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()
    
    // Login
    _, err = client.Login(
        os.Getenv("TRAILBASE_EMAIL"),
        os.Getenv("TRAILBASE_PASSWORD"),
    )
    if err != nil {
        log.Fatal("Login failed:", err)
    }
    
    if user := client.User(); user != nil {
        fmt.Printf("Logged in as: %s\n", user.Email)
    }
    
    // List posts
    posts := trailbase.NewRecordApi(client, "posts")
    
    response, err := posts.List(&trailbase.ListOptions{
        Order: []string{"-created_at"},
        Pagination: &trailbase.Pagination{
            Limit: intPtr(10),
        },
        Filters: []trailbase.FilterOrComposite{
            trailbase.Filter{
                Column: "published",
                Value:  "true",
            },
        },
    })
    if err != nil {
        log.Fatal(err)
    }
    
    var postList []Post
    json.Unmarshal(response.Records, &postList)
    
    fmt.Printf("\nFound %d posts:\n", len(postList))
    for _, post := range postList {
        fmt.Printf("- %s\n", post.Title)
    }
    
    // Create a new post
    newPost := map[string]interface{}{
        "title":     "Hello from Go",
        "content":   "This post was created using the TrailBase Go SDK",
        "published": true,
    }
    
    newPostID, err := posts.Create(&newPost)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("\nCreated new post with ID: %s\n", newPostID)
    
    // Read the post
    data, err := posts.Read(newPostID, nil)
    if err != nil {
        log.Fatal(err)
    }
    
    var post Post
    json.Unmarshal(data, &post)
    fmt.Printf("Post title: %s\n", post.Title)
    
    // Logout
    err = client.Logout()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("\nLogged out")
}

func intPtr(i int) *int {
    return &i
}

Build docs developers (and LLMs) love