Skip to content

brainplusplus/supa-rs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

110 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

⚑ SupaRust

A Supabase-compatible backend in a single Rust binary.

Rust Axum SQLx License

Drop-in replacement for Supabase's REST, Auth, and Storage APIs β€” no Docker, no containers, one binary.


✨ Features

Feature Details
πŸ”‹ Single binary One suparust executable β€” no containers, no sidecars
🐘 Embedded PostgreSQL Auto-managed via pg-embed, data in ./data/postgres
πŸ”Œ supabase-js compatible Drop-in for createClient('http://localhost:3000', ANON_KEY)
πŸ” PostgREST REST API Filter, select, order, limit, offset, upsert via URL params
πŸ” Auth Signup, login, JWT sessions, Argon2 password hashing
πŸ—‚οΈ Storage Multipart upload/download, bucket management, RLS-gated access
πŸ›‘οΈ Row-Level Security Enforced via SET LOCAL ROLE + JWT claims per request
πŸ—οΈ SeaQuery SQL builder Injection-safe AST-based query construction for the REST layer
πŸ“Š Structured logging JSON logs with request ID correlation β€” plug into any log pipeline
πŸ”€ Multi-instance safe Per-env PID files β€” production and test can run concurrently

πŸš€ Quick Start

Build

cargo build --release
# Binary: ./target/release/suparust

Run

# Foreground (logs to stdout, Ctrl+C to stop)
suparust start

# Background daemon (logs to app.log)
suparust start --daemon

First run β€” do not interrupt. SupaRust downloads the embedded PostgreSQL binary (~50 MB) and initializes the database cluster. This takes 2–5 minutes depending on network speed. You will see:

Setting up embedded PostgreSQL (first run downloads binary ~50MB)...

Once ready, the log shows Listening on 0.0.0.0:3000. Subsequent runs start in seconds with no download.

On first run, SupaRust also auto-generates a JWT secret and writes it to .env:

SUPARUST_JWT_SECRET=...
SUPARUST_ANON_KEY=eyJ...
SUPARUST_SERVICE_KEY=eyJ...

Connect

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  'http://localhost:3000',
  process.env.SUPARUST_ANON_KEY
)

πŸ–₯️ CLI

suparust start              # Start server in foreground
suparust start --daemon     # Start as background daemon
suparust stop               # Stop server
suparust restart            # Stop + restart daemon
suparust status             # Show status, endpoints, and API keys
suparust logs               # Tail app.log (daemon mode)
suparust logs --lines 100   # Tail last N lines

Environment Profiles

Isolate environments with --profile:

suparust start                    # .env β†’ identity: local
suparust start --profile test     # .env.test only β†’ identity: profile.test
suparust start --env-file dev.env # dev.env only   β†’ identity: env.dev_env

The default identity local reflects the universal convention that .env = local development (Supabase, Next.js, Vite, docker-compose all use this). Every instance β€” including default β€” gets a deterministic PID file: .suparust.<identity>.<port>.pid.

All subcommands (stop, status, restart) accept --profile too.

β†’ See docs/migrating-from-supabase.md for full profile reference.

suparust status

Status:      RUNNING  (PID 12345, uptime 2h 14m 33s)
API URL:     http://localhost:3000/rest/v1
Auth URL:    http://localhost:3000/auth/v1
Storage URL: http://localhost:3000/storage/v1
Anon key:    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Service key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

βš™οΈ Configuration

All config via .env or environment variables:

Variable Default Description
SUPARUST_PORT 3000 HTTP listen port
SUPARUST_ENV local Environment name β€” used in PID filename
SUPARUST_JWT_SECRET auto-generated HS256 signing secret
SUPARUST_ANON_KEY auto-generated JWT for anon role
SUPARUST_SERVICE_KEY auto-generated JWT for service_role
SUPARUST_DB_DATA_DIR ./data/postgres Embedded PG data directory
SUPARUST_STORAGE_ROOT ./data/storage File storage root
SUPARUST_DB_URL (unset) External PG URL (disables embedded PG)
SUPARUST_LOG_LEVEL info trace | debug | info | warn | error
SUPARUST_LOG_FORMAT pretty pretty (dev) | json (production)
SUPARUST_PID_FILE (derived) Override PID file path (Docker / systemd)

Multi-Instance PID Isolation

SupaRust derives a unique PID filename per instance from the profile identity + port β€” so multiple environments coexist without collision:

Command PID file
suparust start .suparust.local.3000.pid
suparust start --profile test .suparust.profile.test.53001.pid
suparust start --profile staging .suparust.profile.staging.8080.pid
suparust start --env-file prod.env .suparust.env.prod_env.3000.pid

SUPARUST_PID_FILE overrides the derived path entirely (useful for Docker, systemd socket activation, etc.).


πŸ“‘ API Reference

Auth /auth/v1

Method Endpoint Description
POST /auth/v1/signup Register with email + password
POST /auth/v1/token?grant_type=password Login, returns JWT session
GET /auth/v1/user Get current user (requires Bearer token)
GET /auth/v1/health Health check β€” confirms DB + migrations ready

REST /rest/v1

Follows PostgREST conventions:

GET  /rest/v1/users?select=id,email&role=eq.admin   # Filter + select
POST /rest/v1/users                                  # Insert
PATCH /rest/v1/users?id=eq.1                         # Update
DELETE /rest/v1/users?id=eq.1                        # Delete

Supported operators: eq, neq, lt, lte, gt, gte, like, ilike, is, in, not.in, cs, cd, fts, and(), or()

Storage /storage/v1

GET    /storage/v1/bucket              # List buckets
POST   /storage/v1/bucket             # Create bucket
POST   /storage/v1/object/{bucket}/*  # Upload file (multipart)
GET    /storage/v1/object/{bucket}/*  # Download file
DELETE /storage/v1/object/{bucket}    # Delete files (JSON body: {prefixes:[...]})

πŸ“Š Observability

SupaRust emits structured JSON logs with request ID correlation β€” no embedded log shipper required:

{"timestamp":"...","level":"INFO","target":"suparust::api::rest","req_id":"a1b2c3","method":"GET","path":"/rest/v1/users","message":"..."}

Every HTTP request gets a unique req_id that propagates through all log lines for that request β€” making concurrent request logs trivially correlatable.

# Development β€” human-readable, file + line numbers
SUPARUST_LOG_LEVEL=debug SUPARUST_LOG_FORMAT=pretty suparust start

# Production β€” JSON for log aggregators
SUPARUST_LOG_LEVEL=info SUPARUST_LOG_FORMAT=json suparust start --daemon

# Fine-grained override
RUST_LOG=suparust=debug,sqlx=debug suparust start

β†’ See docs/observability.md for integration guides: Vector, Grafana Loki + Promtail, Datadog, and systemd journald.


πŸ§ͺ Integration Tests

21 Vitest tests covering Auth, REST API, Storage, and RLS β€” server starts and stops automatically.

Prerequisites

  • Rust toolchain (cargo)
  • Node.js 18+

First-Time Setup

Generate isolated test environment (separate port + database):

node scripts/gen-env-test.mjs

This creates:

  • .env.test β€” server config (port 53001, isolated pg-embed at data/pg-test/)
  • test-client/.env.test β€” client config with matching JWT keys
  • .env.supabase.test β€” compat server config (port 53002, Supabase alias vars)
  • test-client/.env.supabase.test β€” compat client config

Run Tests

cd test-client && npm test             # SUPARUST_* style (port 53001)
cd test-client && npm run test:compat  # Supabase alias style (port 53002)

Server starts automatically, all 21 tests run, server stops on completion. Both suites can run concurrently β€” each uses a separate PostgreSQL port.

First run: pg-embed downloads its binary (~50MB). This takes 2–5 minutes. Subsequent runs start in seconds.

Tests  21 passed (21)

Reset Test Environment

# Regenerate JWT secret + wipe test database + wipe pg-embed system cache
node scripts/gen-env-test.mjs --regen

Use --regen when:

  • Auth tests fail unexpectedly (JWT secret drift)
  • Test database is in a bad state
  • Switching branches with schema changes
  • pg-embed fails with Failed to unpack PostgreSQL binaries (corrupt cache)

πŸ›οΈ Stack

Layer Library
HTTP server axum 0.8
Async runtime tokio 1
Database driver sqlx 0.8 β€” async, compile-time checked queries
SQL builder sea-query 0.32 + sea-query-binder 0.7
Filter parser nom 7 β€” PostgREST filter/select/order syntax
Embedded PG pg-embed 1.0
Auth jsonwebtoken 9, argon2 0.5
Logging tracing 0.1, tracing-subscriber 0.3, tower-http 0.6
CLI clap 4

πŸ“ Project Structure

src/
  main.rs          β€” CLI dispatch
  config.rs        β€” Config::from_env(), env + pid_file derivation
  tracing.rs       β€” init_tracing(), TracingWriter enum
  cli/
    start.rs       β€” foreground + daemon start, HTTP router setup
    stop.rs        β€” stop via PID file or port scan fallback
    status.rs      β€” status with endpoints + key display
    logs.rs        β€” tail app.log
  api/
    rest.rs        β€” PostgREST-compatible CRUD handlers
    auth.rs        β€” signup / login / getUser / health
    storage.rs     β€” bucket + object management
  parser/
    filter.rs      β€” nom parser for col.op.val filter syntax
    select.rs      β€” nom parser for select= column list
    order.rs       β€” order= parser
  sql/
    ast.rs         β€” QueryAst, Operation, CountMethod
    builder.rs     β€” SeaQuery AST builders
    rls.rs         β€” RlsContext β†’ SET LOCAL statements
  db/
    embed.rs       β€” EmbeddedPostgres via pg-embed
    pool.rs        β€” sqlx PgPool creation
    execute.rs     β€” execute_query() with RLS context injection
migrations/        β€” 6 SQL migration files (roles, auth, storage, RLS, grants)
scripts/
  gen-env-test.mjs β€” generate 4 env files (2 pairs: SUPARUST_* + Supabase compat)
  start-test-server.sh β€” manual test server bootstrap helper
docs/
  migrating-from-supabase.md β€” Supabase β†’ SupaRust env var mapping + profile guide
  observability.md β€” Log forwarding guide (Vector, Loki, Datadog, journald)
  plans/           β€” Implementation design docs
test-client/       β€” Vitest integration test suite (21 tests, 2 modes)

πŸ—ΊοΈ Roadmap

  • REST API (PostgREST-compatible)
  • Auth (JWT + Argon2)
  • Storage (multipart, RLS-gated)
  • Row-Level Security
  • Structured logging with request ID correlation
  • Multi-instance PID isolation (env + port based)
  • Integration test suite (21 Vitest tests, auto start/stop)
  • Realtime WebSockets (logical replication β†’ axum::ws)
  • Edge Functions (wasmtime or V8 isolate)
  • Local Studio UI dashboard

πŸ“„ License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors