Don't start a new SaaS project from scratch. Use this starter. You get:
- Backend: A fully typed backend with Fastify, PostgreSQL, Kysely, and TypeBox
- Frontend: A modern React frontend with Vite, TanStack Router, Shadcn UI, and Zustand
- Admin UI: An Admin UI for user management (roles, activate/deactivate)
- Typesafety: Typesafety between frontend and backend with shared schemas
- Auth: Built-in auth (email/password + Google), user roles, and secure password reset
- Stripe Integration: Stripe subscriptions with checkout, billing portal, pricing table, and webhook handling
- Seeding: Database seeding scripts for development
- Migrations: Database migrations with dbmate and Kysely codegen for DB types
- Monorepo: All of this packed in a batteries-included monorepo with pnpm workspaces
Build, iterate, and ship SaaS apps faster. Develop with confidence and avoid boilerplate.
- Backend: Fastify 5, TypeBox, Stripe
- Database: PostgreSQL, dbmate, Kysely
- Frontend (SPA): React 19, TanStack Router, Tailwind v4, Shadcn UI, Zustand, Vite
- Shared: TypeScript everywhere, monorepo via pnpm workspaces
apps/
backend/ # Fastify API server (Swagger at /docs)
frontend/ # App UI (Notes, Auth, Plans)
admin/ # Admin UI (Users management)
packages/
shared-schemas/ # Shared TypeBox schemas & TS types
shared-api/ # Typed API functions for frontend
Prereqs: Node >= 18, pnpm >= 8, PostgreSQL.
- Install dependencies
pnpm install- Configure environment
- Copy
.env.exampleto.env(and per-env overrides like.env.development.local) inapps/backend/root, then set:DATABASE_URL,JWT_SECRET,COOKIE_SECRET,COOKIE_DOMAIN,FRONTEND_URLSTRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET(for billing)- Optional:
SMTP_*for email andGOOGLE_CLIENT_IDfor Google auth
See docs/server/postgres-setup.md for DB basics and apps/backend/src/plugins/config.ts for full backend env schema.
- Create DB and run migrations and seeds
pnpm migrate:up
pnpm seed:runThis uses dbmate and also generates Kysely DB types (apps/backend/src/types/database.ts).
- Start dev server
pnpm devWhat you get:
- Backend at
http://localhost:3000(Swagger at/docs) - Frontend at
http://localhost:5173 - Admin at
http://localhost:5174(open in incognito or a different browser to avoid auth conflicts)
To test Stripe locally, use the Stripe CLI to forward webhooks:
stripe login
stripe listen --forward-to localhost:3000/stripe/webhookFrom the repo root (package.json):
pnpm dev— start all packages/apps in parallelpnpm build— build all packages/appspnpm typecheck— TypeScript noEmit check across workspacepnpm lint— run ESLint across the workspacepnpm check— run lint followed by workspace type checks- Migrations (backend):
pnpm migrate:new <name>pnpm migrate:up|pnpm migrate:down|pnpm migrate:status
- Seeding (backend):
pnpm seed:run— build, then reset and seed dev data
- E2E Tests (Playwright)
- Deployment Scripts