Setup
Configure the Drizzle ORM client with your superapp backend connection and generated schema.
The drizzle() function creates a Drizzle ORM instance using Drizzle Proxy under the hood. Drizzle builds parameterized SQL on the client, and the proxy driver sends SQL + params to your superapp backend over HTTP.
import { drizzle } from '@superapp/db'
import * as schema from './generated/schema'
// Each user gets their own Drizzle instance tied to their JWT.
// All queries through this `db` are automatically scoped to this user's data.
const db = drizzle({
connection: 'http://localhost:3001',
token: session.token, // ← identifies who this user is
schema,
})Options
| Option | Type | Required | Description |
|---|---|---|---|
connection | string | Yes | Your superapp backend URL |
token | string | Yes | A valid JWT obtained from the auth client |
schema | object | Yes | Your generated Drizzle schema object |
Schema Import
The schema is auto-generated from your database by the CLI. It contains standard Drizzle table definitions:
import * as schema from './generated/schema'
// schema.orders → pgTable('orders', { ... })
// schema.customers → pgTable('customers', { ... })
// schema.ordersRelations → relations(orders, ({ one }) => ({ ... }))See Type Generation for how to generate the schema.
Recommended Setup
Create a shared setup file and a React hook for easy access across your app.
lib/db.ts
import { drizzle } from '@superapp/db'
import { createAuth } from '@superapp/auth'
import * as schema from '../generated/schema'
const SUPERAPP_URL = process.env.NEXT_PUBLIC_SUPERAPP_URL!
export const authClient = createAuth(SUPERAPP_URL)
export function createDb(token: string) {
return drizzle({
connection: SUPERAPP_URL,
token,
schema,
})
}hooks/use-db.ts
import { useMemo } from 'react'
import { useSession } from '@superapp/auth'
import { createDb } from '@/lib/db'
export function useDb() {
const { data: session } = useSession()
return useMemo(
() => (session?.token ? createDb(session.token) : null),
[session?.token]
)
}Usage in a component:
import { useDb } from '@/hooks/use-db'
import { eq, desc } from 'drizzle-orm'
import * as schema from '@/generated/schema'
export function OrdersList() {
const db = useDb()
async function loadOrders() {
if (!db) return
// No user_id filter needed — the backend scopes results automatically.
// Each user only sees their own orders.
const orders = await db.select()
.from(schema.orders)
.where(eq(schema.orders.status, 'active'))
.orderBy(desc(schema.orders.createdAt))
.limit(50)
}
}How It Works
When you call a Drizzle query method, the proxy driver:
- Drizzle builds parameterized SQL on the client (e.g.,
SELECT ... FROM orders WHERE status = $1). - POSTs SQL + params to the backend's
/dataendpoint with the JWT in theAuthorizationheader. - The server validates the token, applies row-level permissions to the SQL, and executes through DuckDB.
- Returns typed results as JSON, deserialized into the expected TypeScript type.
This is standard Drizzle Proxy — the query syntax is identical to any other Drizzle setup, only the transport layer differs.
Calling Actions
The db instance also exposes a typed action() method for calling server-side actions:
// Fully typed — autocomplete on action name, input params, and return type
const result = await db.action('incrementStock', {
productId: 'prod_123',
amount: 5,
})
// result: { id: string; stock: number }This sends POST /actions/incrementStock with the input as JSON. The engine validates the JWT, checks the user's role for action_incrementStock, validates the input against the Zod schema, and returns the typed result. See Actions for details.