Loading repository data…
Loading repository data…
axelhamil / repository
SaaS-ready boilerplate. BetterAuth + multi-tenant + Resend + R2 already wired (Stripe on the roadmap). Bun, Hono, React 19, TanStack, Drizzle, Postgres. Clean Architecture + pragmatic DDD. Fourteen non-negotiable architecture rules, zero-warning pipeline, AI-pair ready.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
The SaaS boilerplate that says no. Auth, multi-tenant, email, storage already wired. You clone, you write business logic — everything else is settled.
Bun + Hono API · Vite + React 19 + TanStack app · Drizzle + Postgres · DDD-kit for the domain · BetterAuth + Resend + R2 for the SaaS layer.
Status — work in progress. This boilerplate is under active iteration. No support, no SLA, no public issue queue.
Two ways to run it. Pick one.
Prerequisites — Bun 1.3+, Node 24+, pnpm 10, Docker with Compose v2.
git clone https://github.com/axelhamil/clean-stack my-saas
cd my-saas
pnpm install
pnpm bootstrap # copies .env.example → .env in each workspace
docker compose up postgres -d # only Postgres on :5433 (api/app run natively below)
pnpm db:migrate # apply migrations
pnpm dev # API :3000, App :5173
No Bun, no Node, no pnpm on your host. Everything runs in containers (api + app + Postgres) with hot reload via compose develop.watch.
Prerequisites — Docker with Compose v2.22+ (the develop.watch minimum). Follow the official guide for your OS — do not use apt install docker.io (Canonical's package, not Docker Inc.).
| OS | Official guide |
|---|---|
| macOS | Docker Desktop for Mac |
| Windows | Docker Desktop for Windows (WSL2) |
| Debian | Install Docker Compose on Debian |
| Ubuntu | Install Docker Compose on Ubuntu |
| Fedora | Install Docker Compose on Fedora |
| RHEL / Rocky / Alma | Install Docker Compose on RHEL |
| Arch / Manjaro | extra/docker + extra/docker-compose (already v2, no plugin needed) |
| NixOS | Docker — NixOS Wiki |
v1 (
docker-compose, hyphen) is EOL since June 2023 — always usedocker compose(space).
git clone https://github.com/axelhamil/clean-stack my-saas
cd my-saas
bash scripts/bootstrap.sh # copies .env.example → .env in each workspace
docker compose up --watch # api runs migrations on boot, then starts
Open http://localhost:5173, sign up with any email, you're in.
Most SaaS templates ship a half-baked auth you'll rip out and zero opinion on what goes where. This one starts from the opposite premise.
| clean-stack | |
|---|---|
| Auth | BetterAuth — passkeys (WebAuthn), 2FA (TOTP + backup codes), magic-link, DB-backed sessions, cross-tab sync via BroadcastChannel. Bearer alongside cookies for Capacitor. Native Bun + Hono, no hacks. |
| Multi-tenant | organizationId FK on every business table from migration #1 + ScopedRepository enforces it at the port. Personal org auto-created on signup, role-based invitations, ownership transfer. Bolting tenancy on later is hell — the reverse is free. |
| Authorization | Capability-based SSOT (@packages/access-control). Same predicate at server middleware, route beforeLoad gate, and <Can requires={...}> UI — drift impossible by construction. |
| Legal / compliance | Day-one EU-legal across the GDPR surface: Art. 7 privacy/terms versioning (re-acceptance gate when a version bumps, user.policy.accepted evidence), Art. 16 rectification (profile + email-change + password), Art. 17 erasure (POST /me/delete — 2FA-required, 7-day soft-delete grace, sole-owner preflight, cancel-on-sign-in, cron wipe + ref anonymization), Art. 20 portability (POST /me/export — signed 7-day R2 URL, 1/24h throttle). Passwords follow NIST SP 800-63B (min 15, HIBP k-anonymity breach check, contextual ban-list, no forced complexity). Without this, fines up to 4% of revenue. |
| Direct uploads | Three-step presign → PUT direct to provider → server HeadObject confirm. Server is blind during transfer; owner-scoped keys (<userId>/<scope>/<uuid>-<filename>); R2 / S3 / B2 / Wasabi / Tigris — provider swap = one env var. |
| Internal endpoints | /internal/* (cron, queues) HMAC-SHA256-signed (X-Internal-Signature); signing key never on the wire. Stack private-network on Railway/Fly via INTERNAL_AUTH_LAYERS for defense-in-depth. |
| Event-driven + audit | Transactional outbox + LISTEN/NOTIFY dispatcher → 52 typed events auto-emitted on every state change (48 subscribable + 4 internal webhook events), append-only audit_log (SOC2 §CC7.2 / RGPD Art. 30), outbound webhooks (HMAC-signed, AEAD-encrypted secrets, decorrelated-jitter retry → dead-letter) — operator UI at /settings/webhooks, public event catalog at /developers/events. Each audit row carries the request's X-Request-Id via an AsyncLocalStorage context — one key joins an audit entry to its logs and Sentry event. |
| DDD scope | Reserved for what your customers pay for. Not for billing, auth, gating, or quotas (config + middleware suffices). Lesson learned the hard way: ~70% less code than full-DDD on the SaaS plumbing. |
| Type safety | Hono RPC end-to-end (hcWithType). No client to write, no schema to sync, refactor in API → red squiggle in App on save. |
| Performance | Bun-native Bun.serve() (~7 ms cold). Route-level code-splitting on the front (initial bundle ~588 KB, route chunks 1–43 KB) + defaultPreload: "intent" — perceived latency near zero. |
| AI-pair ready | CLAUDE.md at the root + per-layer sub-CLAUDE.md. Your agent already knows the rules — Result/Option, no throw in domain, capability gates, vertical-slice modules. |
| Zero-warning pipeline | Husky + lint-staged + commitlint + pre-push CI (Biome, knip, jscpd, type-check). Conventional Commits enforced; dev→main merge triggers semantic-release. No --no-verify shortcut. |
Everything wired today, and the build order for what's next. Prefer prose? docs/OVERVIEW.md is the guided tour. Full inventory in docs/FEATURES.md; detailed plan with constraints in ROADMAP.md.
Auth & identity
BroadcastChannel)Multi-tenant & authorization
organization plugin — Personal org auto-heal, team orgs, email invitations, role-based members, ownership transfer@packages/access-control) — same predicate at server middleware, route beforeLoad gate, and <Can> UISecurity & hardening — the deploy-safe perimeter (Phase C.1)
rate-limiter-flexible, BetterAuth built-in disabled) → global + 8 auth-burst policies, multi-window, IETF RateLimit/RateLimit-Policy/Retry-After headers, fail-closed on auth (a store outage can't silently disable brute-force protection — OWASP A10:2025), trusted-proxy IP resolution (private/CIDR/exact, OWASP rightmost-non-trusted), memory → Postgres (dedicated pool) stores{http.request.uuid} + Vite html.cspNonce), 'strict-dynamic', public /csp-report (IP-rate-limited + cross-origin CORP + document-uri origin filter) → security.csp.violation audit eventsecurity.csrf.rejected audit eventframe-ancestors 'none', nosniff, Referrer-Policy, Permissions-Policy via Caddy) · credentialed CORS allowlist · prod boot fails hard on missing CORS_ORIGIN / signing keysDISPOSABLE_EMAIL_BLOCK_ENABLED) → security.signup.rejected; HIBP breach hit at sign-up/reset/change → security.password.breachedLegal / compliance (GDPR)
@packages/policies, /legal/accept)Storage & email
Event-driven core & transactions — the hard distributed-systems part, already solved
IUnitOfWork.run() + EventCollector AsyncLocalStorage) → no event ever lost, none emitted for a rolled-back write (the dual-write problem, solved)LISTEN/NOTIFY + SELECT … FOR UPDATE SKIP LOCKED drain (multi-instance safe); built-in subscribers run inside the dispatch TX (audit + webhook fanout), user onEvent(...) handlers isolated post-commitaudit_log (operational 90d / compliance 7y), HMAC-signed webhooks (AEAD-encrypted secrets, decorrelated-jitter retry → dead-letter, replay, SSRF guard, dual-secret rotation), X-Request-Id correlation@packages/events → addEvent() in the aggregate → run via uow.run(); the audit row, webhook fanout, and in-process handlers (auto-discovered via inwire) come for free/internal/* endpoints — HMAC-signed, optional private-network layerArchitecture & conventions — the part that keeps shipping fast sustainable
trash + line delete, tsc points to the rest)throw in domain/application (Result), no null for absence (Option), value objects validated via zod, aggregates expose only get id(); ownership enforced at the po