Overview
Drizzle ORM for the frontend — real Drizzle with a proxy driver that returns permission-filtered data.
@superapp/db gives your frontend real Drizzle ORM connected to any database through the superapp backend. Same API you already know — the data you get back is already filtered, restricted, and validated by the backend's permission engine.
How It Works
The client is built on Drizzle Proxy — Drizzle ORM builds parameterized SQL on the client, and the proxy driver sends SQL + params to the superapp backend over HTTP. The server validates your JWT, applies row-level permissions to the SQL, and executes against your database via DuckDB. The data returned to the client has already been filtered and restricted by the permission engine.
Your Frontend superapp Backend Database
───────────── ──────────────── ────────
db.select(...) Drizzle builds SQL
│ on the client
▼
Proxy sends ── SQL ───▶ Verify JWT
SQL + params + params Who is this user?
│
▼
Apply permissions
• Inject WHERE user_id = ?
• Restrict visible columns
• Validate write operations
│
▼
Execute modified ─── query ────▶ Postgres
SQL via DuckDB MySQL
Return filtered ◀── results ── SQLite
results CSV
│
Receive scoped ◀── JSON ── │
data onlyYou write normal Drizzle queries. The backend ensures each user only sees their own data.
Quick Example
import { drizzle } from '@superapp/db'
import { eq, desc } from 'drizzle-orm'
import * as schema from './generated/schema'
// The token ties this Drizzle instance to a specific user.
// Every query through this `db` will only return that user's data.
const db = drizzle({
connection: 'http://localhost:3001',
token: session.token, // ← JWT identifies the logged-in user
schema,
})
// This looks like a normal Drizzle query — no user_id filter needed.
// The backend reads the JWT, resolves the user, and automatically
// scopes results so this user can only see their own orders.
const orders = await db.select()
.from(schema.orders)
.where(eq(schema.orders.status, 'active'))
.orderBy(desc(schema.orders.createdAt))
.limit(10)
// If Alice is logged in: returns only Alice's active orders
// If Bob is logged in: returns only Bob's active orders
// Same code, different data — enforced by the backend, not the clientAvailable Methods
| Method | Description |
|---|---|
db.select() | Select rows with filtering, sorting, joins, and pagination |
db.query.*.findMany() | Relational queries with eager loading |
db.query.*.findFirst() | Find a single record with relations |
db.insert() | Insert one or more rows |
db.update() | Update rows matching a condition |
db.delete() | Delete rows matching a condition |
db.select({ count: count() }) | Count rows |
db.select({ total: sum() }) | Aggregations (sum, avg, min, max) |
Imports Summary
| Import | Path | Description |
|---|---|---|
drizzle | @superapp/db | Create a Drizzle client with proxy driver |
eq, gt, desc, ... | drizzle-orm | Standard Drizzle filter and sort operators |
schema | ./generated/schema | Auto-generated Drizzle schema from your database |
createAuth | @superapp/auth | Auth client for session management |
useSession | @superapp/auth | React hook for current session |
AuthProvider | @superapp/auth/components | Root layout wrapper for auth context |
AuthCard | @superapp/auth/components | Pre-built sign-in/sign-up/forgot-password UI |
UserButton | @superapp/auth/components | Navbar dropdown with avatar and sign out |
What's Next
- Setting Up the Client — Configure
drizzle()with your backend URL and schema. - Drizzle Compatibility — Side-by-side comparison with standard Drizzle.
- Auth Setup — Configure authentication in your frontend.