Typescript SDK
TypeScript / JavaScript Quick Start
WowSQL TypeScript SDK — Get started in minutes
Get your API key: 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 npm →
npm install @wowsql/sdk
# or: yarn add @wowsql/sdk
# or: pnpm add @wowsql/sdk
Initialize Client
import WowSQLClient from '@wowsql/sdk';
// Initialize client — connects to PostgREST at /rest/v1/
const client = new WowSQLClient({
projectUrl: 'myproject', // → https://myproject.wowsqlconnect.com
apiKey: 'wowsql_anon_your_key_here'
});
// Or with full URL
const clientFull = new WowSQLClient({
projectUrl: 'https://myproject.wowsqlconnect.com',
apiKey: 'wowsql_anon_your_key_here'
});
// Query data — uses GET /rest/v1/users?select=*
const users = await client.table('users').select('*').get();
How it works under the hood: The SDK translates your query-builder calls into standard PostgREST HTTP requests. For example, client.table('users').select('id,email').eq('status','active').limit(10).get() becomes GET /rest/v1/users?select=id,email&status=eq.active&limit=10 with the apikey header.
Example API Call (PostgREST)
You can also call the PostgREST API directly with fetch. WowSQL uses standard PostgREST — all filtering, ordering, and pagination is done via query parameters:
const PROJECT_URL = 'https://myproject.wowsqlconnect.com';
const API_KEY = 'wowsql_anon_your_key_here';
// SELECT with filters, ordering, and pagination
const response = await fetch(
`${PROJECT_URL}/rest/v1/users?select=id,email,name&status=eq.active&order=created_at.desc&limit=10`,
{
headers: {
'apikey': API_KEY,
'Content-Type': 'application/json'
}
}
);
const data = await response.json();
console.log(data);
// INSERT a new row
const insertRes = await fetch(`${PROJECT_URL}/rest/v1/users`, {
method: 'POST',
headers: {
'apikey': API_KEY,
'Content-Type': 'application/json',
'Prefer': 'return=representation'
},
body: JSON.stringify({ email: 'john@example.com', name: 'John Doe', status: 'active' })
});
const newUser = await insertRes.json();
// UPDATE a row
await fetch(`${PROJECT_URL}/rest/v1/users?id=eq.123`, {
method: 'PATCH',
headers: {
'apikey': API_KEY,
'Content-Type': 'application/json',
'Prefer': 'return=representation'
},
body: JSON.stringify({ name: 'Updated Name' })
});
// DELETE a row
await fetch(`${PROJECT_URL}/rest/v1/users?id=eq.123`, {
method: 'DELETE',
headers: { 'apikey': API_KEY }
});
PostgREST Filter Syntax (Query Params)
When calling the REST API directly, filters go in the URL as query parameters:
| Operator | PostgREST Syntax | Example |
|---|---|---|
| Equals | col=eq.value | ?status=eq.active |
| Not equals | col=neq.value | ?status=neq.deleted |
| Greater than | col=gt.value | ?age=gt.18 |
| Greater or equal | col=gte.value | ?age=gte.18 |
| Less than | col=lt.value | ?price=lt.100 |
| Less or equal | col=lte.value | ?price=lte.100 |
| Pattern match | col=like.*value* | ?name=like.*john* |
| Case-insensitive | col=ilike.*value* | ?email=ilike.*@gmail.com |
| IS NULL | col=is.null | ?deleted_at=is.null |
| IS NOT NULL | col=not.is.null | ?email=not.is.null |
| IN list | col=in.(a,b,c) | ?role=in.(admin,mod) |
| NOT IN | col=not.in.(a,b) | ?status=not.in.(deleted,banned) |
| Order | order=col.asc | ?order=created_at.desc |
| Pagination | limit=N&offset=M | ?limit=20&offset=40 |
| Select columns | select=a,b,c | ?select=id,email,name |
Query Data
Basic Queries
// Get all records - use .select() before .get()
const users = await client.table('users').select('*').get();
// With filters - use .filter() or helper methods
const adults = await client.table('users')
.select('*')
.filter({ column: 'age', operator: 'gte', value: 18 })
.get();
// Or using filter(column, operator, value)
const adults2 = await client.table('users')
.select('*')
.filter('age', 'gte', 18)
.get();
// Select specific columns
const names = await client.table('users')
.select(['id', 'name'])
.get();
Advanced Queries
// Multiple filters with sorting
const activeUsers = await client.table('users')
.filter({ column: 'status', operator: 'eq', value: 'active' })
.filter({ column: 'age', operator: 'gt', value: 18 })
.order('created_at', 'desc')
.limit(20)
.offset(0)
.get();
console.log(`Found ${activeUsers.count} users`);
activeUsers.data.forEach(user => console.log(user.name));
Filter Helper Methods
// Using helper methods (more readable)
const users = await client.table('users')
.eq('status', 'active')
.gt('age', 18)
.like('email', '%@gmail.com')
.isNotNull('email_verified_at')
.in('role', ['admin', 'moderator'])
.between('created_at', '2024-01-01', '2024-12-31')
.get();
// OR conditions
const usersOr = await client.table('users')
.eq('status', 'active')
.or('role', 'eq', 'admin')
.get();
GROUP BY and Aggregates
Basic GROUP BY
// Group by category with counts
const result = await client.table('products')
.select('category', 'COUNT(*) as count', 'AVG(price) as avg_price')
.groupBy('category')
.get();
// Group by date
const result2 = await client.table('orders')
.select('DATE(created_at) as date', 'COUNT(*) as orders', 'SUM(total) as revenue')
.groupBy('DATE(created_at)')
.orderBy('date', 'desc')
.get();
// Multiple GROUP BY columns
const result3 = await client.table('sales')
.select('region', 'category', 'SUM(amount) as total')
.groupBy(['region', 'category'])
.get();
HAVING Clause
// Filter aggregated results
const result = await client.table('products')
.select('category', 'COUNT(*) as count')
.groupBy('category')
.having('COUNT(*)', 'gt', 10)
.get();
// Multiple HAVING conditions
const result2 = await 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)
.get();
Multiple ORDER BY
// Order by multiple columns
const result = await client.table('products')
.select('*')
.orderByMultiple([
{ column: 'category', direction: 'asc' },
{ column: 'price', direction: 'desc' },
{ column: 'created_at', direction: 'desc' }
])
.get();
CRUD Operations
Create Record
const result = await client.table('users').create({
name: 'John Doe',
email: 'john@example.com',
age: 30
});
console.log(`Created user with ID: ${result.id}`);
Update Record
await client.table('users').update(123, {
email: 'newemail@example.com'
});
console.log('User updated successfully');
Delete Record
await client.table('users').delete(123);
console.log('User deleted successfully');
Get Single Record by ID
const user = await client.table('users').getById(123);
console.log(`User: ${user.name}`);
Get First Record
// Get first matching record
const user = await client.table('users')
.filter({ column: 'status', operator: 'eq', value: 'active' })
.first();
if (user) {
console.log(`First active user: ${user.name}`);
}
Type-safe Queries
Use generics for full type safety and autocomplete:
import { WowSQLClient, QueryResponse } from '@wowsql/sdk';
interface User {
id: number;
name: string;
email: string;
age: number;
}
const client = new WowSQLClient({ projectUrl: 'myproject', apiKey: 'key' });
// Type-safe table access
const users: QueryResponse<User> = await client.table<User>('users').get();
// Full type safety and autocomplete
users.data.forEach((user: User) => {
console.log(user.name); // TypeScript knows this is a string
});
Error Handling
import { WowSQLError } from '@wowsql/sdk';
try {
const users = await client.table('users').select('*').get();
console.log(`Success: ${users.data.length} users`);
} catch (error) {
if (error instanceof WowSQLError) {
if (error.statusCode === 401) console.error('Authentication failed — check your API key');
else if (error.statusCode === 404) console.error('Table or resource not found');
else console.error(`Error (${error.statusCode}): ${error.message}`);
} else throw error;
}
Filter Operators
Available operators for filtering data:
| Operator | Description | Example (TypeScript) |
|---|---|---|
eq | Equals | .filter({ column: 'status', operator: 'eq', value: 'active' }) |
neq | Not equals | .filter({ column: 'status', operator: 'neq', value: 'deleted' }) |
gt | Greater than | .filter({ column: 'age', operator: 'gt', value: 18 }) |
gte | Greater than or equal | .filter({ column: 'age', operator: 'gte', value: 18 }) |
lt | Less than | .filter({ column: 'price', operator: 'lt', value: 100 }) |
lte | Less than or equal | .filter({ column: 'price', operator: 'lte', value: 100 }) |
like | Pattern matching | .filter({ column: 'name', operator: 'like', value: 'john' }) |
is | IS NULL / IS NOT NULL | .filter({ column: 'deleted_at', operator: 'is', value: null }) |
in | IN operator | .filter('category', 'in', ['electronics', 'books']) |
not_in | NOT IN | .filter('status', 'not_in', ['deleted', 'archived']) |
between | BETWEEN | .filter('price', 'between', [10, 100]) |
not_between | NOT BETWEEN | .filter('age', 'not_between', [18, 65]) |
Authentication
Use the same API key for authentication operations. Auth endpoints are at /auth/v1/ on your project URL.
Sign Up & Sign In
import { ProjectAuthClient } from '@wowsql/sdk';
const auth = new ProjectAuthClient({
projectUrl: 'myproject', // → https://myproject.wowsqlconnect.com
apiKey: 'wowsql_anon_your_key_here'
});
// Sign up → POST /auth/v1/signup
const result = await auth.signUp({
email: 'user@example.com',
password: 'SecurePassword123',
full_name: 'John Doe'
});
console.log(`Access token: ${result.session.accessToken}`);
// Sign in → POST /auth/v1/login
const login = await auth.signIn({
email: 'user@example.com',
password: 'SecurePassword123'
});
console.log(`User ID: ${login.user?.id}`);
Direct REST Call (Auth)
// Sign up directly via REST
const res = await fetch('https://myproject.wowsqlconnect.com/auth/v1/signup', {
method: 'POST',
headers: {
'apikey': 'wowsql_anon_your_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'user@example.com',
password: 'SecurePassword123',
full_name: 'John Doe'
})
});
// Sign in directly via REST
const loginRes = await fetch('https://myproject.wowsqlconnect.com/auth/v1/login', {
method: 'POST',
headers: {
'apikey': 'wowsql_anon_your_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'user@example.com',
password: 'SecurePassword123'
})
});
const session = await loginRes.json();
// session.access_token, session.refresh_token, session.user
Password Reset
// Request password reset
await auth.forgotPassword('user@example.com');
// Reset password with token from email
await auth.resetPassword('reset_token_from_email', 'NewSecurePassword123');
OTP Authentication
// Send OTP for login
await auth.sendOtp({ email: 'user@example.com', purpose: 'login' });
// Verify OTP and login
const result = await auth.verifyOtp({
email: 'user@example.com',
otp: '123456',
purpose: 'login'
});
console.log(`Logged in: ${result.user?.id}`);
Magic Link & Email Verification
// Send magic link
await auth.sendMagicLink({ email: 'user@example.com', purpose: 'login' });
// Verify email with token
await auth.verifyEmail('verification_token_from_email');
// Resend verification email
await auth.resendVerification('user@example.com');
OAuth Authentication
Setup checklist — do this once per provider, per project:
1. In your WowSQL dashboard → Project → Auth → Providers, enable the provider (e.g. Google) and paste the
Client ID and Client Secret from your provider's developer console.
2. Copy the Redirect URI shown in the dashboard
(https://<slug>.wowsqlconnect.com/auth/v1/oauth/<provider>/callback)
and add it to your provider's list of Authorised redirect URIs (Google: Cloud Console → Credentials → OAuth 2.0 client → Authorised redirect URIs).
3. Make sure the Client ID / Secret are pasted without leading or trailing spaces.
How the callback works: The OAuth callback URL
(/auth/v1/oauth/<provider>/callback) is handled entirely by the WowSQL
auth service — your application does not need to send an API key on that URL.
The service exchanges the code for tokens, creates/updates the user record, and then
redirects to your frontend_redirect_uri with the session tokens as query
parameters. You only need to handle the final redirect in your frontend.
// ── Step 1: Server-side — get the Google authorization URL ──────────────────
// Call from a server-side route (Next.js API route, Express handler, etc.).
// Never expose your service key client-side.
import { ProjectAuthClient } from '@wowsql/sdk';
const auth = new ProjectAuthClient({
projectUrl: process.env.WOWSQL_PROJECT_URL!, // e.g. https://<slug>.wowsqlconnect.com
apiKey: process.env.WOWSQL_ANON_KEY!, // wowsql_anon_...
});
// frontend_redirect_uri: where YOUR app should land after the OAuth flow finishes
// Internally calls: GET /auth/v1/oauth/google?frontend_redirect_uri=...
const { authorizationUrl } = await auth.getOAuthAuthorizationUrl(
'google',
'https://yourapp.com/auth/callback' // must be an allowed origin in your app
);
// Redirect the browser to authorizationUrl — Google handles login from here.
// Google will call: https://<slug>.wowsqlconnect.com/auth/v1/oauth/google/callback
// WowSQL auth service exchanges the code and redirects to your frontend_redirect_uri.
// ── Step 2: Frontend — handle the redirect from WowSQL ──────────────────────
// WowSQL redirects to https://yourapp.com/auth/callback?access_token=...&refresh_token=...
// Read the tokens from the URL and store them.
// Example (Next.js page / React component):
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
export default function AuthCallbackPage() {
const router = useRouter();
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const accessToken = params.get('access_token');
const refreshToken = params.get('refresh_token');
const error = params.get('error');
if (error) {
console.error('OAuth error:', error);
router.replace('/auth/login?error=' + error);
return;
}
if (accessToken) {
// Persist tokens however your app manages session (cookie, localStorage, etc.)
sessionStorage.setItem('wowsql_access_token', accessToken);
sessionStorage.setItem('wowsql_refresh_token', refreshToken ?? '');
router.replace('/dashboard');
}
}, []);
return <p>Signing you in…</p>;
}
// ── (Advanced) Manual code exchange — only if you handle the callback yourself
const result = await auth.exchangeOAuthCallback(
'google',
'authorization_code_from_callback',
'https://yourapp.com/auth/callback' // must exactly match the redirect_uri used in step 1
);
console.log(`OAuth login successful: ${result.user?.id}`);
Storage Operations
Initialize Storage
import { WowSQLStorage } from '@wowsql/sdk';
const storage = new WowSQLStorage({
projectUrl: 'myproject', // → https://myproject.wowsqlconnect.com
apiKey: 'wowsql_anon_your_key_here'
});
File Operations
// Upload file → POST /storage/v1/object/{bucket}/{path}
const result = await storage.upload(file, 'uploads/document.pdf', {
folder: 'documents'
});
console.log(`Uploaded: ${result.file_key}`);
// Get file URL
const urlData = await storage.getFileUrl('uploads/document.pdf', { expiresIn: 3600 });
console.log(`Download URL: ${urlData.file_url}`);
console.log(`Expires at: ${urlData.expires_at}`);
// Get presigned URL
const presignedUrl = await storage.getPresignedUrl({
objectKey: 'uploads/document.pdf',
expiresIn: 3600,
operation: 'get_object'
});
console.log(`URL: ${presignedUrl}`);
// List files → POST /storage/v1/object/list/{bucket}
const files = await storage.listFiles({ prefix: 'uploads/' });
files.forEach(file => console.log(`${file.key}: ${file.size_mb?.toFixed(2)} MB`));
// Delete file → DELETE /storage/v1/object/{bucket}/{path}
await storage.deleteFile('uploads/document.pdf');
// Check quota
const quota = await storage.getQuota();
console.log(`Used: ${quota.used_gb?.toFixed(2)} GB / ${quota.quota_gb?.toFixed(2)} GB`);
Direct REST Call (Storage)
// Upload via REST
const formData = new FormData();
formData.append('file', fileBlob, 'photo.png');
await fetch('https://myproject.wowsqlconnect.com/storage/v1/object/avatars/photo.png', {
method: 'POST',
headers: { 'apikey': 'wowsql_anon_your_key_here' },
body: formData
});
// Download via REST
const fileRes = await fetch(
'https://myproject.wowsqlconnect.com/storage/v1/object/avatars/photo.png',
{ headers: { 'apikey': 'wowsql_anon_your_key_here' } }
);
// Public file (no auth needed if bucket is public)
// GET https://myproject.wowsqlconnect.com/storage/v1/object/public/{bucket}/{path}
Bucket Management
// Create bucket → POST /storage/v1/bucket
await storage.createBucket({ name: 'avatars', public: true });
// List buckets → GET /storage/v1/bucket
const buckets = await storage.listBuckets();
// Delete bucket → DELETE /storage/v1/bucket/{name}
await storage.deleteBucket('avatars');
Schema DDL (WowSQLSchema)
Requires service role key: Schema operations use wowsql_service_... (not the anon key). POST /api/v2/schema/tables requires a primaryKey column with type UUID. Use auto_increment: true for DEFAULT gen_random_uuid(). Raw DDL via POST /api/v2/schema/execute cannot use SERIAL PRIMARY KEY. Existing tables are unchanged.
import { WowSQLSchema } from '@wowsql/sdk';
const schema = new WowSQLSchema({
projectUrl: 'myproject', // → https://myproject.wowsqlconnect.com
serviceKey: 'wowsql_service_your_key_here'
});
// Create table → POST /api/v2/schema/tables
await schema.createTable({
tableName: 'items',
columns: [
{ name: 'id', type: 'UUID', auto_increment: true },
{ name: 'title', type: 'TEXT' },
{ name: 'price', type: 'NUMERIC' },
{ name: 'created_at', type: 'TIMESTAMPTZ', default: 'NOW()' },
],
primaryKey: 'id',
});
// Execute raw DDL → POST /api/v2/schema/execute
await schema.execute(`
ALTER TABLE items ADD COLUMN description TEXT;
CREATE INDEX idx_items_title ON items (title);
`);