Welcome
A production-ready Bun + Express + Prisma + Next.js boilerplate with DDD layering, BetterAuth, billing, observability and a UseDeploy design system.
What this is
An opinionated SaaS starter built around four constraints:
- Domain-Driven Design, enforced by ESLint —
domain/cannot importexpress,@prisma/client, or anything fromapplication//infrastructure/. - Cookie-based auth via BetterAuth (Postgres-backed sessions, magic link, Google OAuth, organizations).
- Provider-agnostic billing — Stripe, Mercado Pago and Polar share one
PaymentProviderinterface. - Typed contracts end-to-end — Zod schemas in
@app/contractsare the single source of truth; the OpenAPI spec is regenerated and committed on every endpoint change.
What's wired
| Capability | Where it lives | Notes |
|---|---|---|
| HTTP server | apps/server/src/index.ts | OTel boots first, then Express, then routes |
| DDD modules | apps/server/src/modules/<context>/ | iam, billing, tenancy, notifications, storage, audit, feature-flags |
| RBAC | packages/shared/src/permissions/ | resource:action strings, requirePermission(p) middleware |
| Background jobs | apps/server/src/infrastructure/jobs/ | BullMQ, Bull Board UI at /admin/queues |
| Domain events | apps/server/src/infrastructure/events/event-bus.ts | In-memory bus, listeners wire in bootstrap |
| Storage | apps/server/src/modules/storage/ | null / local / s3 / uploadthing providers |
| Rate limiting | apps/server/src/infrastructure/http/rate-limit.ts | Memoized tier limiters with separate Redis prefixes |
| Observability | apps/server/src/infrastructure/observability/ | OTel + Prometheus /metrics, Sentry server (@sentry/bun) |
| Frontend | apps/client/ | Next 16, Tailwind v4, Fumadocs, UseDeploy design system in components/brand/* |
30-second tour
git clone <repo> my-saas
cd my-saas
bun install
cp apps/server/.env.example apps/server/.env
bunx prisma migrate dev --schema apps/server/prisma/schema.prisma
bun run dev # client :3004, server :3005, docs at /docsThe minimum env to boot: BETTER_AUTH_SECRET, BETTER_AUTH_URL, DATABASE_URL, CORS_ORIGINS. Every optional adapter (Sentry, OTel, S3, UploadThing, Redis) boots to a no-op when its config is unset.
Where to next
- New here: Getting started → Project structure → Environment.
- Building a feature: How-to: add a new feature.
- Touching the UI: Frontend tokens and primitives.
- Need to know why something is the way it is: Architecture and Bounded contexts.