Architecture
How the pieces fit together.
superapp is three npm packages: @superapp/backend (server), @superapp/db (data client), and @superapp/auth (auth client). The server owns all data access, auth, and permissions. The client packages are thin typed HTTP layers that never generate SQL.
System Diagram
@superapp/db (Data) @superapp/auth (Auth)
┌────────────────────────┐ ┌──────────────────────────┐
│ drizzle() │ │ createAuth() │
│ Sends JSON over HTTP │ │ AuthCard · AuthProvider │
│ (no raw SQL) │ │ useSession · UserButton │
└────────────┬───────────┘ └────────────┬─────────────┘
│ POST /data + JWT │ POST /auth/*
└────────────┬───────────────┘
▼
@superapp/backend (Server)
┌───────────────────────────────────┐
│ HTTP Adapter (Hono/Express/Next) │
│ │ │
│ ▼ │
│ Auth (better-auth / custom) │
│ │ │
│ ▼ │
│ Permissions (CASL) │
│ │ │
│ ▼ │
│ Query Builder (Kysely) │
│ │ │
│ ▼ │
│ DuckDB (in-process) │
└─────────┬─────────────────────────┘
│
┌───────┼───────┬───────┐
▼ ▼ ▼ ▼
Postgres MySQL SQLite CSVThree Packages
@superapp/backend (Server)
The backend is the single source of truth for all data access. It:
- Accepts structured JSON requests over HTTP (never raw SQL from clients).
- Verifies JWTs through a pluggable auth provider.
- Enforces permissions using CASL-based rules that inject row filters, restrict columns, validate writes, and preset values.
- Translates JSON to SQL via Kysely and executes through DuckDB, which fans out to attached databases.
- Stores metadata (sessions, roles, audit logs) in a Turso or local SQLite database via Drizzle ORM.
import { createEngine } from '@superapp/backend'
import { createHonoMiddleware } from '@superapp/backend/adapters/hono'@superapp/db (Data Client)
The data client is real Drizzle ORM with an HTTP driver. You write standard Drizzle queries — the data you get back is already filtered, restricted, and validated by the backend's permission engine. It:
- Exposes standard Drizzle ORM query syntax that mirrors your schema.
- Sends structured JSON to the server's
/dataendpoint. - Returns permission-filtered data — row filters, column restrictions, and write validations are applied transparently by the backend before results reach the client.
- Generates TypeScript types from your server's
/schemaendpoint for full autocomplete.
import { drizzle } from '@superapp/db'@superapp/auth (Auth Client)
The auth client handles session management and ships pre-built auth UI components. It uses better-auth by default, but supports custom adapters for any auth provider. It:
- Manages JWT sessions — sign-in, sign-up, token refresh, sign-out.
- Ships auth UI components (
AuthCard,AuthProvider,UserButton) built with React. - Provides React hooks (
useSession) for accessing the current session. - Supports custom adapters — swap the default better-auth adapter for Firebase, Auth0, Clerk, or any custom auth system.
import { createAuth } from '@superapp/auth'
import { useSession } from '@superapp/auth'
import { AuthCard, AuthProvider, UserButton } from '@superapp/auth/components'Tech Stack
| Layer | Technology | Role |
|---|---|---|
| Query engine | DuckDB | In-process OLAP engine, ATTACH to external databases |
| HTTP framework | Hono | Lightweight, edge-compatible HTTP routing |
| Metadata store | Turso + Drizzle | Sessions, roles, audit logs, admin configuration |
| Authentication | better-auth | JWT-based auth with session management |
| Authorization | CASL | Attribute-based permission evaluation |
| SQL builder | Kysely | Type-safe SQL generation from JSON |
| Admin UI | React + Vite | Visual permission and connection management |
| Data client | @superapp/db | Typed Drizzle ORM over HTTP, returns permission-filtered data |
| Auth client | @superapp/auth | Session management, auth UI components (better-auth default, custom adapters) |
Dependency Graph
createEngine()
├── integrations[] → Database providers ──→ DuckDB ATTACH
├── connections{} → Named DB configs ──→ DuckDB ATTACH
├── auth → AuthProvider
│ ├── verifyToken()
│ ├── findUser()
│ └── resolveSession()
├── permissions{} → Permission definitions ──→ CASL abilities
├── roles{} → Role → permission mappings
├── duckdb{} → Pool config
├── limits{} → Rate and query limits
├── audit{} → Audit logging
└── adapter → HTTP adapter
├── POST /data
├── POST /auth/*
└── GET /schemaRequest Lifecycle
Every query follows this exact path:
- HTTP -- Adapter receives
POST /datawith JWT inAuthorizationheader. - Auth --
verifyToken()validates the JWT signature.findUser()retrieves the user record.resolveSession()enriches with roles and org memberships. - Permissions -- CASL evaluates which permissions apply based on the user's role. Filters are injected into WHERE clauses. Columns are stripped to the allowed set. For writes,
checkvalidates the data andpresetinjects server-side values. - Query Builder -- Kysely translates the JSON request into a parameterized SQL statement.
- DuckDB -- Executes the SQL against the attached database (Postgres, MySQL, SQLite, or CSV).
- Response -- Results are serialized to JSON and returned to the client.
No step can be skipped. There is no "bypass" mode in production. The client never sees raw SQL, and the server never executes unvalidated queries.