superapp
Advanced

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   CSV

Three 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 /data endpoint.
  • 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 /schema endpoint 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

LayerTechnologyRole
Query engineDuckDBIn-process OLAP engine, ATTACH to external databases
HTTP frameworkHonoLightweight, edge-compatible HTTP routing
Metadata storeTurso + DrizzleSessions, roles, audit logs, admin configuration
Authenticationbetter-authJWT-based auth with session management
AuthorizationCASLAttribute-based permission evaluation
SQL builderKyselyType-safe SQL generation from JSON
Admin UIReact + ViteVisual permission and connection management
Data client@superapp/dbTyped Drizzle ORM over HTTP, returns permission-filtered data
Auth client@superapp/authSession 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 /schema

Request Lifecycle

Every query follows this exact path:

  1. HTTP -- Adapter receives POST /data with JWT in Authorization header.
  2. Auth -- verifyToken() validates the JWT signature. findUser() retrieves the user record. resolveSession() enriches with roles and org memberships.
  3. 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, check validates the data and preset injects server-side values.
  4. Query Builder -- Kysely translates the JSON request into a parameterized SQL statement.
  5. DuckDB -- Executes the SQL against the attached database (Postgres, MySQL, SQLite, or CSV).
  6. 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.

On this page