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:
| Operator | Description | Example (Go) |
|---|---|---|
eq | Equals | .Eq("status", "active") |
neq | Not equals | .Neq("status", "deleted") |
gt | Greater than | .Gt("age", 18) |
gte | Greater than or equal | .Gte("age", 18) |
lt | Less than | .Lt("price", 100) |
lte | Less than or equal | .Lte("price", 100) |
like | Pattern matching | .Like("name", "john") |
is | IS NULL / IS NOT NULL | .IsNull("deleted_at") |
in | IN operator | .In("category", []interface{}{"electronics", "books"}) |
not_in | NOT IN | .NotIn("status", []interface{}{"deleted", "archived"}) |
between | BETWEEN | .Between("price", 10, 100) |
not_between | NOT 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, ¬FoundErr):
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"))