Skip to content
Starterdough
Menu

Bun-native SaaS starter

Build once. Serve anywhere.

Starterdough is the Svelte and Bun answer to next-forge, Makerkit and ShipFast. Auth, organizations, teams, workspaces, billing and an admin surface, built once and served as a website, an installable PWA, a desktop app, a mobile app, or self-hosted on your own box.

One HTTP API, every surface

Every surface is a thin client of one HTTP API. The SvelteKit app, the Tauri desktop and mobile shells, the dynamic bits of the Astro sites, CLIs and the Python service all consume the same API: the same auth, the same procedures, the same OpenAPI document. No Tauri IPC, no per-platform data layer, no duplicated business logic.

  • Public sites

    Astro, static: marketing, blog, docs

  • Application

    SvelteKit, one codebase: SSR on Node or Cloudflare, static SPA, PWA install

  • Native shells

    Tauri 2, no IPC: desktop (Windows, macOS, Linux) and mobile (Android, iOS)

The one API

Hono on Bun

  • Better Auth: sessions, organizations, admin
  • oRPC: typed RPC + REST with OpenAPI 3.1
  • Stripe webhooks
  • Postgres

    Drizzle on Bun’s native driver. The API is its only client

  • FastAPI service

    AI, data and documents. Internal, reached only by the API with a service token

Clients on the left talk HTTPS to the API; only the API talks to Postgres and the Python service.
Add a feature once.
Declare it in the contract, implement it in the API, call it from any client with full types. REST and OpenAPI come for free.
Ship the frontend anywhere.
The app builds for Node, Cloudflare Workers or as a static SPA with one environment variable.
Auth follows the transport.
Browsers use an httpOnly cookie; the native shells use a bearer token. The UI code is identical.
The database has exactly one client.
Only the API touches Postgres. Frontends cannot reach it even by accident.

What ships

The parts every SaaS needs before its first feature, implemented once and available on every surface.

All features →

How a request flows

The same four steps on the web, in the desktop and mobile shells, and for third-party callers.

  1. A component calls the typed client

    A Svelte component calls api.workspaces.list({ organizationId }) from the shared API client.

  2. The request goes over HTTP

    The client POSTs to /rpc/workspaces/list. In the browser the session cookie rides along; in a native shell a bearer token is attached. During server rendering the origin is rewritten to the internal API and the browser’s cookies are forwarded.

  3. The API resolves the session and runs the procedure

    Hono routes to the oRPC handler, requireAuth resolves the session through Better Auth, and the procedure runs against Drizzle.

  4. The same procedure is plain REST

    GET /api/v1/workspaces?organizationId=… returns the same data and is documented at /api/v1/openapi.json. That is what the Python service, curl and third parties use.

Start with the repository

Bun, Docker for Postgres, and a handful of commands. Python and Rust are only needed for the AI service and the native shells.

bun install
bun run setup       # every .env, with generated secrets
bun run db:up && bun run db:migrate
bun run dev:app     # api on :3000 + web on :5173