superapp
Client

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

OptionTypeRequiredDescription
connectionstringYesYour superapp backend URL
tokenstringYesA valid JWT obtained from the auth client
schemaobjectYesYour 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.

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:

  1. Drizzle builds parameterized SQL on the client (e.g., SELECT ... FROM orders WHERE status = $1).
  2. POSTs SQL + params to the backend's /data endpoint with the JWT in the Authorization header.
  3. The server validates the token, applies row-level permissions to the SQL, and executes through DuckDB.
  4. 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.

On this page