API Records - Go SDK Documentation
Overview
The Records API provides comprehensive CRUD (Create, Read, Update, Delete) operations for collection records, along with powerful search, filtering, and authentication capabilities.
Key Features:
- Paginated list and search with filtering and sorting
- Single record retrieval with expand support
- Create, update, and delete operations
- Batch operations for multiple records
- Authentication methods (password, OAuth2, OTP)
- Email verification and password reset
- Relation expansion up to 6 levels deep
- Field selection and excerpt modifiers
Backend Endpoints:
GET /api/collections/{collection}/records- List recordsGET /api/collections/{collection}/records/{id}- View recordPOST /api/collections/{collection}/records- Create recordPATCH /api/collections/{collection}/records/{id}- Update recordDELETE /api/collections/{collection}/records/{id}- Delete recordPOST /api/batch- Batch operations
CRUD Operations
List/Search Records
Returns a paginated records list with support for sorting, filtering, and expansion.
package main
import (
"fmt"
"log"
bosbase "github.com/bosbase/go-sdk"
)
func main() {
client := bosbase.New("http://127.0.0.1:8090")
defer client.Close()
// Basic list with pagination
result, err := client.Collection("posts").GetList(&bosbase.CrudListOptions{
Page: 1,
PerPage: 50,
})
if err != nil {
log.Fatal(err)
}
page, _ := result["page"].(float64)
perPage, _ := result["perPage"].(float64)
totalItems, _ := result["totalItems"].(float64)
totalPages, _ := result["totalPages"].(float64)
items, _ := result["items"].([]interface{})
fmt.Printf("Page: %.0f, PerPage: %.0f, TotalItems: %.0f, TotalPages: %.0f\n",
page, perPage, totalItems, totalPages)
fmt.Printf("Items: %d\n", len(items))
}
Advanced List with Filtering and Sorting
// Filter and sort
result, err := client.Collection("posts").GetList(&bosbase.CrudListOptions{
Page: 1,
PerPage: 50,
Filter: `created >= "2022-01-01 00:00:00" && status = "published"`,
Sort: "-created,title", // DESC by created, ASC by title
Expand: "author,categories",
})
// Filter with operators
result2, err := client.Collection("posts").GetList(&bosbase.CrudListOptions{
Page: 1,
PerPage: 50,
Filter: `title ~ "javascript" && views > 100`,
Sort: "-views",
})
Get Full List
Fetch all records at once (useful for small collections):
// Get all records
allPosts, err := client.Collection("posts").GetFullList(500, &bosbase.CrudListOptions{
Sort: "-created",
Filter: `status = "published"`,
})
// With batch size for large collections
allPosts, err := client.Collection("posts").GetFullList(200, &bosbase.CrudListOptions{
Sort: "-created",
})
Get First Matching Record
Get only the first record that matches a filter:
post, err := client.Collection("posts").GetFirstListItem(
`slug = "my-post-slug"`,
&bosbase.CrudViewOptions{
Expand: "author,categories.tags",
},
)
View Record
Retrieve a single record by ID:
// Basic retrieval
record, err := client.Collection("posts").GetOne("RECORD_ID", nil)
// With expanded relations
record, err := client.Collection("posts").GetOne("RECORD_ID", &bosbase.CrudViewOptions{
Expand: "author,categories,tags",
})
// Nested expand
record, err := client.Collection("comments").GetOne("COMMENT_ID", &bosbase.CrudViewOptions{
Expand: "post.author,user",
})
// Field selection
record, err := client.Collection("posts").GetOne("RECORD_ID", &bosbase.CrudViewOptions{
Fields: "id,title,content,author.name",
})
Create Record
Create a new record:
// Simple create
record, err := client.Collection("posts").Create(&bosbase.CrudMutateOptions{
Body: map[string]interface{}{
"title": "My First Post",
"content": "Lorem ipsum...",
"status": "draft",
},
})
// Create with relations
record, err := client.Collection("posts").Create(&bosbase.CrudMutateOptions{
Body: map[string]interface{}{
"title": "My Post",
"author": "AUTHOR_ID", // Single relation
"categories": []string{"cat1", "cat2"}, // Multiple relation
},
})
// Create with file upload
import (
"os"
)
file, err := os.Open("image.jpg")
if err != nil {
log.Fatal(err)
}
defer file.Close()
record, err := client.Collection("posts").Create(&bosbase.CrudMutateOptions{
Body: map[string]interface{}{
"title": "My Post",
},
Files: map[string]bosbase.FileParam{
"image": {
Filename: "image.jpg",
Reader: file,
ContentType: "image/jpeg",
},
},
})
// Create with expand to get related data immediately
record, err := client.Collection("posts").Create(&bosbase.CrudMutateOptions{
Body: map[string]interface{}{
"title": "My Post",
"author": "AUTHOR_ID",
},
Expand: "author",
})
Update Record
Update an existing record:
// Simple update
record, err := client.Collection("posts").Update("RECORD_ID", &bosbase.CrudMutateOptions{
Body: map[string]interface{}{
"title": "Updated Title",
"status": "published",
},
})
// Update with relations
_, err = client.Collection("posts").Update("RECORD_ID", &bosbase.CrudMutateOptions{
Body: map[string]interface{}{
"categories+": "NEW_CATEGORY_ID", // Append
"tags-": "OLD_TAG_ID", // Remove
},
})
// Update with file upload
file, err := os.Open("newimage.jpg")
if err != nil {
log.Fatal(err)
}
defer file.Close()
record, err := client.Collection("posts").Update("RECORD_ID", &bosbase.CrudMutateOptions{
Body: map[string]interface{}{
"title": "Updated Title",
},
Files: map[string]bosbase.FileParam{
"image": {
Filename: "newimage.jpg",
Reader: file,
ContentType: "image/jpeg",
},
},
})
// Update with expand
record, err := client.Collection("posts").Update("RECORD_ID", &bosbase.CrudMutateOptions{
Body: map[string]interface{}{
"title": "Updated",
},
Expand: "author,categories",
})
Delete Record
Delete a record:
// Simple delete
err := client.Collection("posts").Delete("RECORD_ID", nil)
if err != nil {
log.Fatal(err)
}
// Note: Returns 204 No Content on success
// Returns error if record doesn't exist or permission denied
Filter Syntax
The filter parameter supports a powerful query syntax:
Comparison Operators
// Equal
filter: `status = "published"`
// Not equal
filter: `status != "draft"`
// Greater than / Less than
filter: `views > 100`
filter: `created < "2023-01-01"`
// Greater/Less than or equal
filter: `age >= 18`
filter: `price <= 99.99`
String Operators
// Contains (like)
filter: `title ~ "javascript"`
// Equivalent to: title LIKE "%javascript%"
// Not contains
filter: `title !~ "deprecated"`
// Exact match (case-sensitive)
filter: `email = "user@example.com"`
Array Operators (for multiple relations/files)
// Any of / At least one
filter: `tags.id ?= "TAG_ID"` // Any tag matches
filter: `tags.name ?~ "important"` // Any tag name contains "important"
// All must match
filter: `tags.id = "TAG_ID" && tags.id = "TAG_ID2"`
Logical Operators
// AND
filter: `status = "published" && views > 100`
// OR
filter: `status = "published" || status = "featured"`
// Parentheses for grouping
filter: `(status = "published" || featured = true) && views > 50`
Special Identifiers
// Request context (only in API rules, not client filters)
// @request.auth.id, @request.query.*, etc.
// Collection joins
filter: `@collection.users.email = "test@example.com"`
// Record fields
filter: `author.id = @request.auth.id`
Comments
// Single-line comments are supported
filter: `status = "published" // Only published posts`
Sorting
Sort records using the sort parameter:
// Single field (ASC)
sort: "created"
// Single field (DESC)
sort: "-created"
// Multiple fields
sort: "-created,title" // DESC by created, then ASC by title
// Supported fields
sort: "@random" // Random order
sort: "@rowid" // Internal row ID
sort: "id" // Record ID
sort: "fieldName" // Any collection field
// Relation field sorting
sort: "author.name" // Sort by related author's name
Field Selection
Control which fields are returned:
// Specific fields
fields: "id,title,content"
// All fields at level
fields: "*"
// Nested field selection
fields: "*,author.name,author.email"
// Excerpt modifier for text fields
fields: "*,content:excerpt(200,true)"
// Returns first 200 characters with ellipsis if truncated
// Combined
fields: "*,content:excerpt(200),author.name,author.email"
Expanding Relations
Expand related records without additional API calls:
// Single relation
expand: "author"
// Multiple relations
expand: "author,categories,tags"
// Nested relations (up to 6 levels)
expand: "author.profile,categories.tags"
// Back-relations
expand: "comments_via_post.user"
See Relations Documentation for detailed information.
Pagination Options
// Skip total count (faster queries)
result, err := client.Collection("posts").GetList(&bosbase.CrudListOptions{
Page: 1,
PerPage: 50,
SkipTotal: true, // totalItems and totalPages will be -1
Filter: `status = "published"`,
})
// Get Full List with batch processing
allPosts, err := client.Collection("posts").GetFullList(200, &bosbase.CrudListOptions{
Sort: "-created",
})
// Processes in batches of 200 to avoid memory issues
Batch Operations
Execute multiple operations in a single transaction:
// Create a batch
batch := client.CreateBatch()
// Add operations
batch.Collection("posts").Create(map[string]interface{}{
"title": "Post 1",
"author": "AUTHOR_ID",
})
batch.Collection("posts").Create(map[string]interface{}{
"title": "Post 2",
"author": "AUTHOR_ID",
})
batch.Collection("tags").Update("TAG_ID", map[string]interface{}{
"name": "Updated Tag",
})
batch.Collection("categories").Delete("CAT_ID")
// Upsert (create or update based on id)
batch.Collection("posts").Upsert(map[string]interface{}{
"id": "EXISTING_ID",
"title": "Updated Post",
})
// Send batch request
results, err := batch.Send()
if err != nil {
log.Fatal(err)
}
// Results is an array matching the order of operations
for i, result := range results {
if result.Status >= 400 {
fmt.Printf("Operation %d failed: %v\n", i, result.Body)
} else {
fmt.Printf("Operation %d succeeded: %v\n", i, result.Body)
}
}
Note: Batch operations must be enabled in Dashboard > Settings > Application.
Authentication Actions
List Auth Methods
Get available authentication methods for a collection:
methods, err := client.Collection("users").ListAuthMethods("", nil, nil)
if err != nil {
log.Fatal(err)
}
password, _ := methods["password"].(map[string]interface{})
oauth2, _ := methods["oauth2"].(map[string]interface{})
otp, _ := methods["otp"].(map[string]interface{})
mfa, _ := methods["mfa"].(map[string]interface{})
fmt.Printf("Password enabled: %v\n", password["enabled"])
fmt.Printf("OAuth2 enabled: %v\n", oauth2["enabled"])
Auth with Password
authData, err := client.Collection("users").AuthWithPassword(
"user@example.com", // username or email
"password123",
"", // expand
"", // fields
nil, // body
nil, // query
nil, // headers
)
if err != nil {
log.Fatal(err)
}
// Auth data is automatically stored in client.AuthStore
fmt.Printf("Is valid: %v\n", client.AuthStore.IsValid())
fmt.Printf("Token: %s\n", client.AuthStore.Token())
// Access the returned data
token, _ := authData["token"].(string)
record, _ := authData["record"].(map[string]interface{})
// With expand
authData, err := client.Collection("users").AuthWithPassword(
"user@example.com",
"password123",
"profile", // expand
"", // fields
nil, // body
nil, // query
nil, // headers
)
Auth with OAuth2
// Step 1: Get OAuth2 URL (usually done in UI)
methods, err := client.Collection("users").ListAuthMethods("", nil, nil)
if err != nil {
log.Fatal(err)
}
oauth2, _ := methods["oauth2"].(map[string]interface{})
providers, _ := oauth2["providers"].([]interface{})
var provider map[string]interface{}
for _, p := range providers {
if pMap, ok := p.(map[string]interface{}); ok {
if name, _ := pMap["name"].(string); name == "google" {
provider = pMap
break
}
}
}
// Step 2: After redirect, exchange code for token
authData, err := client.Collection("users").AuthWithOAuth2Code(
"google", // Provider name
"AUTHORIZATION_CODE", // From redirect URL
fmt.Sprint(provider["codeVerifier"]), // From step 1
"https://yourapp.com/callback", // Redirect URL
map[string]interface{}{ // Optional data for new accounts
"name": "John Doe",
},
nil, // body
nil, // query
nil, // headers
"", // expand
"", // fields
)
Auth with OTP (One-Time Password)
// Step 1: Request OTP
otpRequest, err := client.Collection("users").RequestOTP("user@example.com", nil, nil, nil)
if err != nil {
log.Fatal(err)
}
otpID, _ := otpRequest["otpId"].(string)
// Step 2: User enters OTP from email
// Step 3: Authenticate with OTP
authData, err := client.Collection("users").AuthWithOTP(
otpID,
"123456", // OTP from email
"", // expand
"", // fields
nil, // body
nil, // query
nil, // headers
)
Auth Refresh
Refresh the current auth token and get updated user data:
// Refresh auth (useful on app restart)
authData, err := client.Collection("users").AuthRefresh("", "", nil, nil, nil)
if err != nil {
log.Fatal(err)
}
// Check if still valid
if client.AuthStore.IsValid() {
fmt.Println("User is authenticated")
} else {
fmt.Println("Token expired or invalid")
}
Email Verification
// Request verification email
err := client.Collection("users").RequestVerification("user@example.com", nil, nil, nil)
if err != nil {
log.Fatal(err)
}
// Confirm verification (on verification page)
err = client.Collection("users").ConfirmVerification("VERIFICATION_TOKEN", nil, nil, nil)
if err != nil {
log.Fatal(err)
}
Password Reset
// Request password reset email
err := client.Collection("users").RequestPasswordReset("user@example.com", nil, nil, nil)
if err != nil {
log.Fatal(err)
}
// Confirm password reset (on reset page)
// Note: This invalidates all previous auth tokens
err = client.Collection("users").ConfirmPasswordReset(
"RESET_TOKEN",
"newpassword123",
"newpassword123", // Confirm
nil, // body
nil, // query
nil, // headers
)
Email Change
// Must be authenticated first
_, err := client.Collection("users").AuthWithPassword("user@example.com", "password", "", "", nil, nil, nil)
if err != nil {
log.Fatal(err)
}
// Request email change
err = client.Collection("users").RequestEmailChange("newemail@example.com", nil, nil, nil)
if err != nil {
log.Fatal(err)
}
// Confirm email change (on confirmation page)
// Note: This invalidates all previous auth tokens
err = client.Collection("users").ConfirmEmailChange(
"EMAIL_CHANGE_TOKEN",
"currentpassword",
nil, // body
nil, // query
nil, // headers
)
Impersonate (Superuser Only)
Generate a token to authenticate as another user:
// Must be authenticated as superuser
_, err := client.Collection("_superusers").AuthWithPassword("admin@example.com", "password", "", "", nil, nil, nil)
if err != nil {
log.Fatal(err)
}
// Impersonate a user
impersonateClient, err := client.Collection("users").Impersonate("USER_ID", 3600, "", "", nil, nil, nil)
if err != nil {
log.Fatal(err)
}
// Use the impersonated client
posts, err := impersonateClient.Collection("posts").GetFullList(500, nil)
if err != nil {
log.Fatal(err)
}
// Access the token
fmt.Println("Token:", impersonateClient.AuthStore.Token())
Complete Examples
Example 1: Blog Post Search with Filters
func searchPosts(client *bosbase.BosBase, query, categoryID string, minViews int) ([]interface{}, error) {
filter := fmt.Sprintf(`title ~ "%s" || content ~ "%s"`, query, query)
if categoryID != "" {
filter += fmt.Sprintf(` && categories.id ?= "%s"`, categoryID)
}
if minViews > 0 {
filter += fmt.Sprintf(` && views >= %d`, minViews)
}
result, err := client.Collection("posts").GetList(&bosbase.CrudListOptions{
Page: 1,
PerPage: 20,
Filter: filter,
Sort: "-created",
Expand: "author,categories",
})
if err != nil {
return nil, err
}
items, _ := result["items"].([]interface{})
return items, nil
}
Example 2: User Dashboard with Related Content
func getUserDashboard(client *bosbase.BosBase, userID string) (map[string]interface{}, error) {
// Get user's posts
postsResult, err := client.Collection("posts").GetList(&bosbase.CrudListOptions{
Page: 1,
PerPage: 10,
Filter: fmt.Sprintf(`author = "%s"`, userID),
Sort: "-created",
Expand: "categories",
})
if err != nil {
return nil, err
}
// Get user's comments
commentsResult, err := client.Collection("comments").GetList(&bosbase.CrudListOptions{
Page: 1,
PerPage: 10,
Filter: fmt.Sprintf(`user = "%s"`, userID),
Sort: "-created",
Expand: "post",
})
if err != nil {
return nil, err
}
posts, _ := postsResult["items"].([]interface{})
comments, _ := commentsResult["items"].([]interface{})
return map[string]interface{}{
"posts": posts,
"comments": comments,
}, nil
}
Example 3: Advanced Filtering
result, err := client.Collection("posts").GetList(&bosbase.CrudListOptions{
Page: 1,
PerPage: 50,
Filter: `(status = "published" || featured = true) &&
created >= "2023-01-01" &&
(tags.id ?= "important" || categories.id = "news") &&
views > 100 &&
author.email != ""`,
Sort: "-views,created",
Expand: "author.profile,tags,categories",
Fields: "*,content:excerpt(300),author.name,author.email",
})
Example 4: Batch Create Posts
func createMultiplePosts(client *bosbase.BosBase, postsData []map[string]interface{}) ([]interface{}, error) {
batch := client.CreateBatch()
for _, postData := range postsData {
batch.Collection("posts").Create(postData)
}
results, err := batch.Send()
if err != nil {
return nil, err
}
// Check for failures
var failures []int
for i, result := range results {
if result.Status >= 400 {
failures = append(failures, i)
}
}
if len(failures) > 0 {
fmt.Printf("Some posts failed to create: %v\n", failures)
}
var bodies []interface{}
for _, result := range results {
bodies = append(bodies, result.Body)
}
return bodies, nil
}
Example 5: Pagination Helper
func getAllRecordsPaginated(client *bosbase.BosBase, collectionName string, options *bosbase.CrudListOptions) ([]interface{}, error) {
var allRecords []interface{}
page := 1
for {
if options == nil {
options = &bosbase.CrudListOptions{}
}
options.Page = page
options.PerPage = 500
options.SkipTotal = true // Skip count for performance
result, err := client.Collection(collectionName).GetList(options)
if err != nil {
return nil, err
}
items, _ := result["items"].([]interface{})
allRecords = append(allRecords, items...)
perPage, _ := result["perPage"].(float64)
if len(items) < int(perPage) {
break
}
page++
}
return allRecords, nil
}
Error Handling
record, err := client.Collection("posts").Create(&bosbase.CrudMutateOptions{
Body: map[string]interface{}{
"title": "My Post",
},
})
if err != nil {
if clientErr, ok := err.(*bosbase.ClientResponseError); ok {
switch clientErr.Status {
case 400:
fmt.Println("Validation errors:", clientErr.Response)
case 403:
fmt.Println("Access denied")
case 404:
fmt.Println("Collection or record not found")
default:
fmt.Printf("Unexpected error: %v\n", err)
}
} else {
fmt.Printf("Error: %v\n", err)
}
}
Best Practices
- Use Pagination: Always use pagination for large datasets
- Skip Total When Possible: Use
SkipTotal: truefor better performance when you don’t need counts - Batch Operations: Use batch for multiple operations to reduce round trips
- Field Selection: Only request fields you need to reduce payload size
- Expand Wisely: Only expand relations you actually use
- Filter Before Sort: Apply filters before sorting for better performance
- Cache Auth Tokens: Auth tokens are automatically stored in
AuthStore, no need to manually cache - Handle Errors: Always handle authentication and permission errors gracefully
Related Documentation
- Collections - Collection configuration
- Relations - Working with relations
- API Rules and Filters - Filter syntax details
- Authentication - Detailed authentication guide
- Files - File uploads and handling