Rust SDK
Rust Quick Start
WoWSQL Rust 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 crates.io →
cargo add wowsql
cargo add tokio --features full
cargo add serde_json
Or add to Cargo.toml:
[dependencies]
wowsql = "3.10"
tokio = { version = "1.0", features = ["full"] }
Initialize Client
The SDK talks to PostgREST at /rest/v1/. Your project URL resolves to https://myproject.wowsqlconnect.com.
use serde_json::Value;
use wowsql::WOWSQLClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize client with your anon key (respects Row Level Security)
let client = WOWSQLClient::new(
"https://myproject.wowsqlconnect.com", // resolves to /rest/v1/
"wowsql_anon_your_key_here", // anon key from Settings → API
)?;
// Always use select() before get()/execute()
let users = client.table("users").select(&["*"]).limit(10).execute::<Value>().await?;
for user in users.data {
println!("{}", user["name"]);
}
Ok(())
}
Query Data
Basic Queries
use serde_json::{json, Value};
let users = client.table("users").select(&["*"]).get::<Value>().await?;
let adults = client.table("users").select(&["*"]).gte("age", json!(18)).get::<Value>().await?;
let names = client.table("users").select(&["id", "name"]).get::<Value>().await?;
Advanced Queries
use wowsql::models::SortDirection;
let active_users = client.table("users")
.select(&["*"])
.eq("status", json!("active"))
.gt("age", json!(18))
.order("created_at", SortDirection::Desc)
.limit(20)
.offset(0)
.execute::<Value>()
.await?;
println!("Found {} users", active_users.count);
Filter Helper Methods
let users = client.table("users")
.eq("status", json!("active"))
.gt("age", json!(18))
.like("email", "%@gmail.com")
.in_("role", vec![json!("admin"), json!("moderator")])
.between("created_at", json!("2024-01-01"), json!("2024-12-31"))
.get::<Value>()
.await?;
GROUP BY and Aggregates
Basic GROUP BY
let result = client.table("products")
.select(&["category", "COUNT(*) as count", "AVG(price) as avg_price"])
.group_by(&["category"])
.get::<Value>()
.await?;
HAVING Clause
let result = client.table("products")
.select(&["category", "COUNT(*) as count"])
.group_by(&["category"])
.having("COUNT(*)", "gt", json!(10))
.get::<Value>()
.await?;
CRUD Operations
Create Record
let result = client.table("users").create(json!({
"name": "John Doe",
"email": "john@example.com",
"age": 30
})).await?;
println!("Created user with ID: {}", result.id);
Update Record
client.table("users").update(json!(123), json!({
"email": "newemail@example.com"
})).await?;
Delete Record
client.table("users").delete(json!(123)).await?;
Get Single Record by ID
let user = client.table("users").get_by_id(json!(123)).await?;
println!("User: {}", user["name"]);
Get First Record
let user = client.table("users").eq("status", json!("active")).first::<Value>().await?;
if let Some(u) = user { println!("First active user: {}", u["name"]); }
Filter Operators
Available operators: eq, neq, gt, gte, lt, lte, like, is, in, not_in, between, not_between. Use .eq("col", value), .filter("col", "op", value), etc.
Error Handling
match client.table("users").select(&["*"]).get::<Value>().await {
Ok(users) => println!("Success: {} users", users.data.len()),
Err(e) => {
if e.is_authentication() { println!("Authentication failed — check your API key"); }
else if e.is_not_found() { println!("Table or resource not found"); }
else { println!("Error: {}", e); }
}
}
Authentication
Auth endpoints are served at /auth/v1/. Use your anon key as the apikey header.
Sign Up & Sign In
use wowsql::AuthClient;
let auth = AuthClient::new(
"myproject",
"wowsql_anon_your_key_here", // anon key — POST /auth/v1/signup, POST /auth/v1/login
)?;
// Sign up (POST /auth/v1/signup): sign_up(email, password, full_name, user_metadata)
let result = auth.sign_up("user@example.com", "SecurePassword123", Some("John Doe"), None).await?;
// Sign in (POST /auth/v1/login): returns a session, or a 2FA challenge
let login = auth.sign_in("user@example.com", "SecurePassword123").await?;
if login.requires_2fa {
// TOTP is on for this user: finish with verify_two_factor_login (see Two-Factor Authentication)
println!("2FA required, method: {:?}", login.method);
} else if let Some(session) = &login.session {
println!("Access token: {}", session.access_token);
}
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.
Existing sign_in / sign_up signatures are unchanged — use the *_with_captcha methods.
auth.sign_up_with_captcha("user@example.com", "SecurePassword123", None, None, Some(&turnstile_token)).await?;
auth.sign_in_with_captcha("user@example.com", "SecurePassword123", Some(&turnstile_token)).await?;
auth.forgot_password_with_captcha("user@example.com", Some(&turnstile_token)).await?;
auth.send_otp_with_captcha("user@example.com", "login", Some(&turnstile_token)).await?;
auth.send_magic_link_with_captcha("user@example.com", "login", Some(&turnstile_token)).await?;
auth.resend_verification_with_captcha("user@example.com", Some(&turnstile_token)).await?;
Password Reset
auth.forgot_password("user@example.com").await?;
auth.reset_password("reset_token", "NewSecurePassword123").await?;
OTP Authentication
Email: send_otp / verify_otp. Phone SMS: send_otp_phone / verify_otp_phone.
Phone OTP requires Auth → SMS / Phone OTP (Twilio, Fast2SMS, or MSG91).
// Email OTP
auth.send_otp("user@example.com", "login").await?;
let email_login = auth.verify_otp("user@example.com", "123456", "login", None).await?;
// Phone OTP (SMS)
auth.send_otp_phone("+15551234567", "login").await?;
let phone_login = auth.verify_otp_phone("+15551234567", "123456", "login", None).await?;
println!("Logged in: {:?}", phone_login.user);
Magic Link & Email Verification
auth.send_magic_link("user@example.com", "login").await?;
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 HashMap<String, Value> with authorization_url, provider, backend_callback_url, frontend_redirect_uri
let oauth = auth.get_oauth_authorization_url(
"google",
Some("https://yourapp.com/auth/callback"),
).await?;
// Redirect the browser to oauth["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
let result = auth.exchange_oauth_callback(
"google",
"authorization_code_from_callback",
Some("https://yourapp.com/auth/callback"), // optional; the server exchanges the code with the project callback URL
).await?;
if let Some(user) = result.user { println!("OAuth login successful: {}", user.id); }
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 (None = use the stored access token)
let me = auth.get_user(None).await?;
// Refresh tokens → POST /auth/v1/refresh-token (None = use the stored refresh token)
let refreshed = auth.refresh_token(None).await?;
// Read or restore the stored session
let session = auth.get_session();
auth.set_session("saved_access_token", "saved_refresh_token");
// Sign out → POST /auth/v1/logout
auth.logout(None).await?;
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.
Not wrapped in this SDK in 3.10.0: disabling 2FA (POST /auth/v1/2fa/disable) and rotating recovery codes (POST /auth/v1/2fa/recovery-codes). Call the REST endpoint directly with the apikey header and Authorization: Bearer <access token>; the body is {"code": "123456"} (disable also accepts {"backup_code": "..."}). Rust 3.10.0 also does not wrap /settings, anonymous sign-in, identity linking or email change.
// 1. Enrol (user is signed in; None = use the stored access token)
let setup = auth.setup_totp(None).await?; // POST /auth/v1/2fa/setup-totp
// Show setup["qr_code_url"] (a PNG data URI when available) or setup["otpauth_uri"]
let enabled = auth.enable_totp("123456", None).await?; // POST /auth/v1/2fa/verify-and-enable
println!("{:?}", enabled.get("backup_codes")); // shown once
// 2. Sign in when 2FA is on
let login = auth.sign_in("user@example.com", "SecurePassword123").await?;
if login.requires_2fa {
let pending = login.pending_token.clone().unwrap_or_default();
// verify_two_factor_login(pending_token, code, backup_code) → POST /auth/v1/2fa/verify-login
let done = auth.verify_two_factor_login(&pending, Some("123456"), None).await?;
}
// 3. Status
let status = auth.get_two_factor_status(None).await?; // enabled, method
Storage Operations
Initialize Storage
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).
use wowsql::StorageClient;
let storage = StorageClient::new(
"myproject",
"wowsql_anon_your_key_here", // anon key
)?;
File Operations
// Upload a local file: upload_from_path(file_path, bucket, folder)
// The third argument is the folder (sent as ?folder=); the file keeps its own name.
let result = storage.upload_from_path("path/to/document.pdf", "documents", Some("uploads")).await?; // → uploads/document.pdf
// Or upload bytes: upload(bucket, data, folder, file_name). Pass file_name, or the object is named "file".
let file_data = std::fs::read("path/to/document.pdf")?;
storage.upload("documents", &file_data, Some("uploads"), Some("document.pdf")).await?;
// Public URL (public buckets)
let url = storage.get_public_url("documents", "uploads/document.pdf");
// Download bytes
let bytes = storage.download("documents", "uploads/document.pdf").await?;
// List files: list_files(bucket, options) — options is an optional HashMap (prefix, limit, offset)
let files = storage.list_files("documents", None).await?;
// Delete file
storage.delete_file("documents", "uploads/document.pdf").await?;
// Check usage (GET .../stats). get_quota() calls .../quota, which returns the same fields as /stats.
let stats = storage.get_stats().await?;
println!("{} files, {:.2} GB used", stats.total_files, stats.total_size_gb);
Download or View File
Via REST: use GET /storage/v1/object/{bucket}/{path} for download, or GET /storage/v1/object/public/{bucket}/{path} for public access.
Realtime
The Rust crate does not yet ship a realtime client. Connect with any WebSocket library using the same anon or service_role key as REST.
WebSocket URL: wss://<project>.wowsqlconnect.com/realtime/v1/websocket?apikey=<wowsql_anon_... or wowsql_service_...>. Enable a table first with POST /realtime/v1/enable and body {"schema_name":"public","table_name":"messages"}. Channel broadcast / 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. Official clients exist in TypeScript, Python, Go, Java, Kotlin, C#, PHP, Ruby, Swift, and Flutter.
{
"type": "subscribe",
"schema": "public",
"table": "messages",
"event": "*"
}
{"type":"join","channel":"chat"}
{"type":"broadcast","channel":"chat","event":"typing","payload":{"user":"alice"}}
{"type":"presence","event":"track","channel":"chat","payload":{"user":"alice"}}
Schema Management (Creating Tables)
Requires service role key. POST /api/v2/schema/tables requires the primary key column to be PostgreSQL UUID. Use ColumnDefinition::new("id", "UUID").auto_increment(true) for DEFAULT gen_random_uuid(). Raw CREATE TABLE via execute_sql must not use SERIAL PRIMARY KEY (or similar integer PK).
Use SchemaClient with a service role key:
use wowsql::{ColumnDefinition, SchemaClient};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let schema = SchemaClient::new(
"https://your-project.wowsqlconnect.com",
"wowsql_service_your_key_here", // service role key — bypasses RLS
)?;
schema
.create_table(
"products",
vec![
ColumnDefinition::new("id", "UUID").auto_increment(true),
ColumnDefinition::new("name", "VARCHAR(255)").not_null(true),
ColumnDefinition::new("price", "DECIMAL(10,2)").not_null(true),
],
"id",
None,
)
.await?;
Ok(())
}