superapp

Introduction

A thin, secure data layer between your frontend and any database.

One library to connect your frontend to any database -- with authentication, row-level permissions, and type safety built in.

What is superapp?

superapp is two packages:

PackageWhat it does
@superapp/backendConnects to Postgres, MySQL, SQLite, or CSV through DuckDB. Handles auth, enforces row-level permissions, runs queries.
@superapp/dbDrizzle ORM client with an HTTP driver. Full type safety, auth UI components, zero server dependencies.
  Frontend (React, Next.js)


  @superapp/db (Drizzle ORM)

        │ HTTP + JWT

  @superapp/backend

    ┌───┼───────────────┐
    │   │               │
    ▼   ▼               ▼
  Auth  Permissions   Query Builder


                      DuckDB

          ┌─────┬───────┼────────┐
          ▼     ▼       ▼        ▼
       Postgres MySQL  SQLite   CSV

How it works

  1. Define your server -- connect databases, configure auth, declare permissions
  2. Generate types -- the CLI introspects your schema and outputs Drizzle table definitions
  3. Query from the frontend -- use standard Drizzle ORM syntax, permissions are enforced automatically

Quick Example

Server (server.ts):

import { createEngine } from '@superapp/backend'
import { betterAuthProvider } from '@superapp/backend/auth/better-auth'
import { postgresProvider } from '@superapp/backend/integrations/postgres'
import { createHonoMiddleware } from '@superapp/backend/adapters/hono'
import { Hono } from 'hono'
import { serve } from '@hono/node-server'

const engine = createEngine({
  integrations: [postgresProvider],
  connections: {
    main: { type: 'postgres', url: process.env.PG_URL! },
  },
  auth: betterAuthProvider({ secret: process.env.AUTH_SECRET! }),
  permissions: {
    view_own_orders: {
      table: 'main.orders',
      operations: { select: true },
      columns: ['id', 'amount', 'status', 'created_at'],
      filter: { customer_id: { $eq: '$user.id' } },
    },
  },
  roles: {
    viewer: ['view_own_orders'],
  },
})

const app = new Hono()
app.route('/', createHonoMiddleware(engine))
serve({ fetch: app.fetch, port: 3001 })

Client (lib/superapp.ts):

import { drizzle } from '@superapp/db'
import { createAuth } from '@superapp/db/auth'
import * as schema from '../generated/schema'

export const authClient = createAuth('http://localhost:3001')

export function createDb(token: string) {
  return drizzle({
    connection: 'http://localhost:3001',
    token,
    schema,
  })
}

Query (anywhere in your frontend):

import { eq, desc } from 'drizzle-orm'

const orders = await db.select()
  .from(schema.orders)
  .where(eq(schema.orders.status, 'active'))
  .orderBy(desc(schema.orders.createdAt))
  .limit(50)

The filter: { customer_id: { $eq: '$user.id' } } in the server config injects a WHERE clause into every query -- the client never sees it, and cannot bypass it.

What's Next

On this page