Go SDK

Go Quick Start

WoWSQL Go SDK — Get started in minutes

Get your API keys: In your WoWSQL project dashboard, go to Settings → API to find your anon key (wowsql_anon_...) and service role key (wowsql_service_...). The anon key is safe for client-side use (respects Row Level Security). The service role key bypasses RLS and should only be used server-side.

Installation

Official package: View on pkg.go.dev → · GitHub

go get github.com/WoWSQL/WoWSQL-go/v3
# pin this release: go get github.com/WoWSQL/WoWSQL-go/v3@v3.10.0

Initialize Client

The SDK talks to PostgREST at /rest/v1/ under the hood. Your project URL resolves to https://myproject.wowsqlconnect.com.

package main

import wowsql "github.com/WoWSQL/WoWSQL-go/v3/wowsql"

func main() {
    // Initialize client — URL resolves to https://myproject.wowsqlconnect.com
    // SDK communicates with PostgREST at /rest/v1/
    client := wowsql.NewClient(
        "https://myproject.wowsqlconnect.com",
        "wowsql_anon_your_key_here",
    )

    // Or with project subdomain
    client2 := wowsql.NewClient(
        "myproject",
        "wowsql_anon_your_key_here",
    )

    // Always use Select() before Execute()
    users, err := client.Table("users").Select("*").Execute()
    if err != nil {
        panic(err)
    }
}

Query Data

Basic Queries

// Get all records
users, err := client.Table("users").Select("*").Execute()

// With filters - use Eq, Gte, or Filter
adults, err := client.Table("users").
    Select("*").
    Gte("age", 18).
    Execute()

// Or using Filter(column, operator, value)
adults2, err := client.Table("users").
    Select("*").
    Filter("age", "gte", 18).
    Execute()

// Select specific columns
names, err := client.Table("users").
    Select("id", "name").
    Execute()

Advanced Queries

// Multiple filters with sorting
activeUsers, err := client.Table("users").
    Select("*").
    Eq("status", "active").
    Gt("age", 18).
    OrderBy("created_at", wowsql.SortDesc).
    Limit(20).
    Offset(0).
    Execute()

fmt.Printf("Found %d users\n", activeUsers.Count)
for _, user := range activeUsers.Data {
    fmt.Println(user["name"])
}

Filter Helper Methods

// Using helper methods
users, err := client.Table("users").
    Eq("status", "active").
    Gt("age", 18).
    Like("email", "%@gmail.com").
    IsNotNull("email_verified_at").
    In("role", []interface{}{"admin", "moderator"}).
    Between("created_at", "2024-01-01", "2024-12-31").
    Execute()

// OR conditions
usersOr, err := client.Table("users").
    Eq("status", "active").
    Or("role", "eq", "admin").
    Execute()

GROUP BY and Aggregates

Basic GROUP BY

// Group by category with counts
result, err := client.Table("products").
    Select("category", "COUNT(*) as count", "AVG(price) as avg_price").
    GroupBy("category").
    Execute()

// Group by date
result2, err := client.Table("orders").
    Select("DATE(created_at) as date", "COUNT(*) as orders", "SUM(total) as revenue").
    GroupBy("DATE(created_at)").
    OrderBy("date", wowsql.SortDesc).
    Execute()

// Multiple GROUP BY columns
result3, err := client.Table("sales").
    Select("region", "category", "SUM(amount) as total").
    GroupBy("region", "category").
    Execute()

HAVING Clause

// Filter aggregated results
result, err := client.Table("products").
    Select("category", "COUNT(*) as count").
    GroupBy("category").
    Having("COUNT(*)", "gt", 10).
    Execute()

// Multiple HAVING conditions
result2, err := client.Table("orders").
    Select("DATE(created_at) as date", "SUM(total) as revenue").
    GroupBy("DATE(created_at)").
    Having("SUM(total)", "gt", 1000).
    Having("COUNT(*)", "gte", 5).
    Execute()

Multiple ORDER BY

// Order by multiple columns — each OrderBy call appends a sort key
result, err := client.Table("products").
    Select("*").
    OrderBy("category", wowsql.SortAsc).
    OrderBy("price", wowsql.SortDesc).
    OrderBy("created_at", wowsql.SortDesc).
    Execute()

CRUD Operations

Create Record

result, err := client.Table("users").Insert(map[string]interface{}{
    "name":  "John Doe",
    "email": "john@example.com",
    "age":   30,
})
fmt.Printf("Created user with ID: %v\n", result.ID)

Update Record

res, err := client.Table("users").Update(123, map[string]interface{}{
    "email": "newemail@example.com",
})
fmt.Println("User updated successfully")

Delete Record

_, err := client.Table("users").Delete(123)
fmt.Println("User deleted successfully")

Get Single Record by ID

user, err := client.Table("users").GetByID(123)
fmt.Printf("User: %v\n", user["name"])

Get First Record

user, err := client.Table("users").
    Eq("status", "active").
    First()

if err == nil && user != nil {
    fmt.Printf("First active user: %v\n", user["name"])
}

Filter Operators

Available operators for filtering data:

OperatorDescriptionExample (Go)
eqEquals.Eq("status", "active")
neqNot equals.Neq("status", "deleted")
gtGreater than.Gt("age", 18)
gteGreater than or equal.Gte("age", 18)
ltLess than.Lt("price", 100)
lteLess than or equal.Lte("price", 100)
likePattern matching.Like("name", "john")
isIS NULL / IS NOT NULL.IsNull("deleted_at")
inIN operator.In("category", []interface{}{"electronics", "books"})
not_inNOT IN.NotIn("status", []interface{}{"deleted", "archived"})
betweenBETWEEN.Between("price", 10, 100)
not_betweenNOT BETWEEN.NotBetween("age", 18, 65)

Error Handling

users, err := client.Table("users").Select("*").Execute()
if err != nil {
    var authErr *wowsql.AuthenticationError
    var notFoundErr *wowsql.NotFoundError
    switch {
    case errors.As(err, &authErr):
        fmt.Println("Authentication failed — check your API key")
    case errors.As(err, &notFoundErr):
        fmt.Println("Table or resource not found")
    default:
        fmt.Printf("Error: %v\n", err)
    }
    return
}
fmt.Printf("Success: %d users\n", users.Count)

Authentication

Auth endpoints are served at /auth/v1/. Use the anon key for client-side auth operations.

Sign Up & Sign In

auth := wowsql.NewAuthClient(
    "myproject",
    "wowsql_anon_your_key_here",
)

// Sign up — POST /auth/v1/signup
result, err := auth.SignUp("user@example.com", "SecurePassword123",
    wowsql.WithFullName("John Doe"))
fmt.Printf("Access token: %s\n", result.Session.AccessToken)

// Sign in — POST /auth/v1/login (returns a session, or a 2FA challenge; User is nil here)
login, err := auth.SignIn("user@example.com", "SecurePassword123")
if err != nil {
    log.Fatal(err)
}
if login.Requires2FA {
    // TOTP is on for this user: finish with VerifyTwoFactorLogin (see Two-Factor Authentication)
    fmt.Printf("2FA required, method: %s\n", login.Method)
} else {
    fmt.Printf("Access token: %s\n", login.Session.AccessToken)
}

CAPTCHA (Turnstile)

Optional. Only required when dashboard Attack Protection → Turnstile is enabled. Create a Cloudflare Turnstile widget for your app domain and paste the site key and secret in Attack Protection (WoWSQL does not share a platform widget). GET /auth/v1/settings returns your captcha.site_key. Render Turnstile with that key, then pass the token. Omit it when captcha is off — existing calls are unchanged.

resp, err := auth.SignUp("user@example.com", "SecurePassword123",
    wowsql.WithCaptchaToken(turnstileToken))
resp, err = auth.SignIn("user@example.com", "SecurePassword123", turnstileToken)
auth.ForgotPassword("user@example.com", turnstileToken)
auth.SendOTP("user@example.com", "login", turnstileToken)
auth.SendMagicLink("user@example.com", "login", turnstileToken)
auth.ResendVerification("user@example.com", turnstileToken)

Password Reset

auth.ForgotPassword("user@example.com")

auth.ResetPassword("reset_token_from_email", "NewSecurePassword123")

OTP Authentication

Email: SendOTP / VerifyOTP. Phone SMS: SendOTPPhone / VerifyOTPPhone. Provide exactly one channel. Phone OTP requires Auth → SMS / Phone OTP (Twilio, Fast2SMS, or MSG91).

// Email OTP
auth.SendOTP("user@example.com", "login")
emailLogin, err := auth.VerifyOTP("user@example.com", "123456", "login")

// Phone OTP (SMS) — E.164
auth.SendOTPPhone("+15551234567", "login")
phoneLogin, err := auth.VerifyOTPPhone("+15551234567", "123456", "login")
fmt.Printf("Logged in: %v\n", phoneLogin.User)

OAuth Authentication

Setup checklist — do this once per provider, per project:
1. Dashboard → Project → Auth → Providers: enable the provider, paste Client ID and Client Secret (no leading/trailing spaces).
2. Copy the Redirect URI shown: https://<slug>.wowsqlconnect.com/auth/v1/oauth/<provider>/callback and register it in your provider's developer console.
3. The callback URL is handled entirely by WoWSQL — your app does not send an API key on it. WoWSQL exchanges the code and redirects to your frontend_redirect_uri with ?access_token=&refresh_token=.

// ── Step 1: Server-side — get the authorization URL ─────────────────────────
// Returns map[string]string with keys: authorization_url, provider, backend_callback_url, frontend_redirect_uri
oauthResp, err := auth.GetOAuthAuthorizationURL(
    "google",
    "https://yourapp.com/auth/callback",
)
if err != nil { /* handle */ }
// Redirect the browser to oauthResp["authorization_url"]
// Google calls: https://<slug>.wowsqlconnect.com/auth/v1/oauth/google/callback
// WoWSQL exchanges the code and redirects to your frontend_redirect_uri.


// ── Step 2: Frontend — read tokens from the redirect ────────────────────────
// WoWSQL redirects to: https://yourapp.com/auth/callback?access_token=...&refresh_token=...
// Parse them from the URL in your frontend.


// ── (Advanced) Manual code exchange — only if you handle the callback yourself
oauthResult, err := auth.ExchangeOAuthCallback("google", "authorization_code_from_callback",
    "https://yourapp.com/auth/callback") // optional; the server exchanges the code with the project callback URL
fmt.Printf("OAuth login successful: %s\n", oauthResult.User.Email)

Session and Token Refresh

Token refresh calls POST /auth/v1/refresh-token with the stored refresh token and stores the new token pair. From 3.10.0 every SDK uses this route; before 3.10.0 the TypeScript and Java SDKs called routes that do not exist, and Flutter and Rust had no refresh method.

// Current user → GET /auth/v1/me (uses the stored access token)
user, err := auth.GetUser()

// Refresh tokens → POST /auth/v1/refresh-token (uses the stored refresh token)
refreshed, err := auth.RefreshToken()
fmt.Println(refreshed.Session.AccessToken)

// Read or restore the stored session
session := auth.GetSession()
auth.SetSession("saved_access_token", "saved_refresh_token")

// Sign out → POST /auth/v1/logout
_, err = auth.Logout()

Two-Factor Authentication (TOTP)

Added in 3.10.0. When a user has TOTP two-factor authentication enabled, sign-in does not return a session. It returns a challenge instead: requires_2fa is true and a short-lived pending_token is set (SDK field names are in the example). Finish the sign-in with the verify call, using a 6-digit code from the authenticator app or one of the user's backup codes. Status, setup, enable, disable and recovery-code calls run as the signed-in user: the SDK sends your API key and the stored access token. Enabling 2FA and rotating recovery codes return new backup codes once; show them to the user and do not store them in plain text.

Go 3.10.0 does not wrap /settings, anonymous sign-in, identity linking or email change; only the TypeScript, Python and Java SDKs do.

// 1. Enrol (user is signed in; the stored access token is used)
setup, err := auth.SetupTOTP()             // POST /auth/v1/2fa/setup-totp
// Show setup["qr_code_url"] (a PNG data URI when available) or setup["otpauth_uri"]
enabled, err := auth.EnableTOTP("123456")  // POST /auth/v1/2fa/verify-and-enable
fmt.Println(enabled["backup_codes"])       // shown once

// 2. Sign in when 2FA is on
login, err := auth.SignIn("user@example.com", "SecurePassword123")
if login.Requires2FA {
    // VerifyTwoFactorLogin(pendingToken, code, backupCode) → POST /auth/v1/2fa/verify-login
    done, err := auth.VerifyTwoFactorLogin(login.PendingToken, "123456", "")
    if err == nil {
        fmt.Println(done.Session.AccessToken)
    }
}

// 3. Manage
status, err := auth.GetTwoFactorStatus()            // enabled, method
rotated, err := auth.RegenerateRecoveryCodes("123456")
_, err = auth.DisableTOTP("123456", "")             // or DisableTOTP("", "backup-code")

Storage Operations

Storage endpoints are served at /storage/v1/. The SDK storage client itself calls /api/v1/storage/projects/{project_slug}/… on your project host (buckets, files, stats).

Initialize Storage

storage := wowsql.NewStorageClient(
    "myproject",
    "wowsql_anon_your_key_here",
)

File Operations

// Upload a local file: UploadFromPath(filePath, bucketName, remotePath)
file, err := storage.UploadFromPath("path/to/document.pdf", "documents", "uploads/document.pdf")
fmt.Printf("Uploaded: %s\n", file.Path)

// Or upload from any io.Reader
f, _ := os.Open("path/to/document.pdf")
defer f.Close()
_, err = storage.Upload("documents", f, wowsql.UploadPath("uploads/document.pdf"))

// Public URL (public buckets)
url := storage.GetPublicURL("documents", "uploads/document.pdf")
fmt.Printf("Public URL: %s\n", url)

// Download bytes
data, err := storage.Download("documents", "uploads/document.pdf")
fmt.Printf("Downloaded %d bytes\n", len(data))

// List files
files, err := storage.ListFiles("documents", wowsql.ListFilesPrefix("uploads/"), wowsql.ListFilesLimit(100))
for _, file := range files {
    fmt.Printf("%s: %d bytes\n", file.Path, file.Size)
}

// Delete file
_, err = storage.DeleteFile("documents", "uploads/document.pdf")

// Check usage (GET .../stats; GetQuota is an alias of GetStats)
stats, err := storage.GetStats()
fmt.Printf("%d files, %.2f GB used\n", stats.TotalFiles, stats.TotalSizeGB)

Download or View File

Via REST:

  • Upload: POST /storage/v1/object/{bucket}/{path}
  • Download: GET /storage/v1/object/{bucket}/{path}
  • List: POST /storage/v1/object/list/{bucket}
  • Public URL: GET /storage/v1/object/public/{bucket}/{path}

Realtime

Subscribe to INSERT / UPDATE / DELETE, broadcast ephemeral events, and track presence. Uses the same anon or service_role key as REST.

WebSocket URL: wss://<project>.wowsqlconnect.com/realtime/v1/websocket?apikey=<wowsql_anon_... or wowsql_service_...>. Auth is always the query param. Enable a table first with POST /realtime/v1/enable and body {"schema_name":"public","table_name":"messages"}. Channel Send / presence do not need a Postgres trigger. Payloads are capped at 64 KiB. Missing key closes 4001; invalid key closes 4003 (do not reconnect). Multi-replica deployments need REDIS_URL.

unsub := client.Realtime().Subscribe("messages", func(change wowsql.RealtimeChange) {
    fmt.Println(change.Event, change.New)
}, "public", "*")

ch := client.Realtime().Channel("chat")
ch.OnBroadcast("typing", func(msg map[string]interface{}) {
    fmt.Println("typing", msg["payload"])
})
ch.OnPresence(func(msg map[string]interface{}) {
    fmt.Println("online", ch.PresenceState())
})
ch.Subscribe(nil)
ch.Track(map[string]interface{}{"user": "alice"})
ch.Send("typing", map[string]interface{}{"user": "alice"})

unsub()
ch.Unsubscribe()
client.Realtime().Close()

Schema DDL (SchemaClient)

Requires service role key. CreateTable requires TablePrimaryKey to name a column whose type is UUID. Use AutoIncrement: true on that column for DEFAULT gen_random_uuid(). Raw CREATE TABLE via ExecuteSQL cannot use SERIAL PRIMARY KEY (or similar integer PK). Existing tables are unchanged.

schema := wowsql.NewSchemaClient("myproject", "wowsql_service_your_key_here")
_, err := schema.CreateTable("items", []wowsql.ColumnDefinition{
    {Name: "id", Type: "UUID", AutoIncrement: true},
    {Name: "title", Type: "TEXT"},
}, wowsql.TablePrimaryKey("id"))