How Open SaaS Is Built
Repository structure, Wasp configuration, dependencies, build pipeline, and deployment
How Open SaaS Is Built
This page covers the practical engineering behind Open SaaS: how the repository is organized, how the Wasp framework orchestrates everything, what dependencies power the features, and how it gets deployed.
For Product Managers
This page answers "how is the engineering organized?" — the tools, structure, and processes that turn source code into a running SaaS product. Understanding this helps you estimate effort for customizations and grasp deployment complexity.
Repository Structure
Open SaaS has a clean separation between the main application, marketing site, and tests:
open-saas/
└── template/
├── app/
│ ├── main.wasp ← app config (routes, auth, jobs, entities)
│ ├── schema.prisma ← database schema
│ └── src/
│ ├── auth/ ← signup fields, email templates
│ ├── payment/ ← Stripe + LemonSqueezy logic
│ ├── demo-ai-app/ ← AI scheduling feature
│ ├── file-upload/ ← S3 presigned URL handling
│ ├── admin/ ← admin dashboard pages
│ ├── analytics/ ← stats aggregation
│ ├── landing-page/ ← marketing pages
│ ├── components/ ← ShadCN UI components
│ ├── client/ ← client-side utilities
│ ├── server/ ← server-side shared logic
│ ├── shared/ ← shared types and constants
│ └── user/ ← user management
├── blog/ ← Astro/Starlight marketing site + docs
└── e2e-tests/ ← Playwright test suiteKey Files
| File | Purpose |
|---|---|
main.wasp | Central configuration — routes, auth, entities, queries, actions, jobs |
schema.prisma | Database schema — all models and relationships |
src/payment/ | Payment abstraction layer (Stripe + Lemon Squeezy) |
src/auth/ | Custom signup fields and email templates |
src/demo-ai-app/ | Complete AI feature implementation |
src/file-upload/ | S3 file handling |
src/admin/ | Admin dashboard pages and components |
src/analytics/ | Stats aggregation cron job |
The main.wasp File — Central Nervous System
The main.wasp file is the single source of truth. Here's what it configures:
App Declaration
app OpenSaaS {
wasp: { version: "^0.18.0" },
title: "My SaaS App",
db: { system: PostgreSQL },
auth: { ... },
emailSender: { provider: SendGrid },
}Routes & Pages
route LandingPageRoute { path: "/", to: LandingPage }
route PricingPageRoute { path: "/pricing", to: PricingPage }
route AccountRoute { path: "/account", to: AccountPage }
route AdminRoute { path: "/admin", to: AdminPage }
route DemoAppRoute { path: "/demo", to: DemoAppPage }Queries & Actions (type-safe RPC)
// Read operations
query getTasks {
fn: import { getTasks } from "@src/demo-ai-app/operations",
entities: [Task]
}
// Write operations
action createTask {
fn: import { createTask } from "@src/demo-ai-app/operations",
entities: [Task]
}
action generateSchedule {
fn: import { generateSchedule } from "@src/demo-ai-app/operations",
entities: [GptResponse, User, Task]
}Background Jobs
job dailyStatsJob {
executor: PgBoss,
schedule: { cron: "0 0 * * *" },
perform: {
fn: import { calculateDailyStats } from "@src/analytics/stats"
},
entities: [User, DailyStats]
}Why This Matters
Everything in main.wasp is declarative. You don't write Express routes, auth middleware, or job schedulers — you declare what you want, and Wasp generates the implementation. When a developer adds a new feature, they add a few lines to main.wasp and write the business logic. The "glue code" is automated.
Dependency Map
Frontend Dependencies
| Package | Purpose | Why Chosen |
|---|---|---|
| React 18.2 | UI framework | Wasp's default frontend |
| TypeScript 5.8 | Type safety | End-to-end types from Prisma |
| Tailwind CSS 3.2 | Styling | Utility-first, fast iteration |
| ShadCN UI v2 | Component library | Accessible, customizable, modern |
| Radix UI | Headless primitives | Foundation for ShadCN components |
| React Router DOM 6 | Client routing | Wasp's default router |
| React Hook Form 7 | Form management | Performant, minimal re-renders |
| Zod 3 | Schema validation | TypeScript-native validation |
| Recharts | Data visualization | Admin dashboard charts |
Backend Dependencies
| Package | Purpose | Why Chosen |
|---|---|---|
| Wasp 0.18 | Full-stack framework | Auto-generates auth, RPC, jobs |
| Prisma 5.19 | ORM | Type-safe DB access |
| PgBoss | Job queue | Uses existing PostgreSQL (no Redis needed) |
| Stripe SDK | Payment processing | Industry standard |
| @lemonsqueezy/lemonsqueezy.js | Alt payments | Merchant of Record |
| OpenAI SDK | AI integration | GPT function calling |
| @aws-sdk/client-s3 | File storage | S3 presigned URLs |
| @sendgrid/mail | Transactional email | Email delivery |
Infrastructure
| Service | Purpose |
|---|---|
| PostgreSQL | Primary database + job queue storage (PgBoss) |
| AWS S3 | File storage |
| Stripe / Lemon Squeezy | Payment processing |
| OpenAI | AI text generation |
| SendGrid / Mailgun | Email delivery |
| Google Analytics / Plausible | User analytics |
Key Design Choice: No Redis
Unlike many architectures, Open SaaS doesn't need Redis. Background jobs use PgBoss, which stores its job queue in PostgreSQL itself. This means one fewer service to deploy and manage — a significant simplification for solo developers and small teams.
Build System
Development
# Install Wasp (one-time)
curl -sSL https://get.wasp.sh/installer.sh | sh
# Create new project from template
wasp new -t saas
# Start development (frontend + backend + DB)
cd my-saas-app
wasp db migrate-dev # apply migrations
wasp start # starts everythingwasp start runs:
- PostgreSQL migration check
- Node.js backend with hot reload
- React frontend with hot reload (Vite)
- PgBoss job scheduler
Production Build
wasp build # generates deployable artifactsThis produces:
- A static React frontend bundle
- A Node.js Express server
- Prisma client with migrations
Deployment
One-Command Deploy
# Deploy to Railway
wasp deploy railway
# Deploy to Fly.io
wasp deploy flyWasp handles:
- Building frontend and backend
- Setting up PostgreSQL database
- Running migrations
- Configuring environment variables
- Deploying the application
Manual Deployment
For custom hosting:
wasp build
cd .wasp/build
# Deploy the web-app/ directory as a static site
# Deploy the server/ directory as a Node.js app
# Set up PostgreSQL separatelyRequired Environment Variables
| Category | Variables |
|---|---|
| Database | DATABASE_URL |
| Auth | JWT_SECRET, ADMIN_EMAILS |
| Payments | STRIPE_KEY, STRIPE_WEBHOOK_SECRET or LEMONSQUEEZY_API_KEY, LEMONSQUEEZY_WEBHOOK_SECRET |
| AI | OPENAI_API_KEY |
| Storage | AWS_S3_BUCKET, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION |
SENDGRID_API_KEY or SMTP config | |
| Analytics | GOOGLE_ANALYTICS_ID and/or PLAUSIBLE_SITE_ID |
Testing Strategy
E2E Tests (Playwright)
e2e-tests/
├── tests/
│ ├── landingPageTests.spec.ts ← marketing pages load correctly
│ ├── pricingPageTests.spec.ts ← pricing displays, CTA works
│ └── demoAppTests.spec.ts ← AI feature works end-to-end
└── playwright.config.tsRun with:
cd e2e-tests
npx playwright testCI/CD
GitHub Actions runs on every commit:
- Lint (ESLint + TypeScript)
- Build check
- Playwright E2E tests
Customization Guide
For developers using Open SaaS as a starting point, here's what to customize:
| What to Change | Where | Effort |
|---|---|---|
| Branding | src/landing-page/, Tailwind theme | Low |
| Payment plans | src/payment/plans.ts + Stripe dashboard | Low |
| Auth methods | main.wasp auth config | Low |
| Database models | schema.prisma + new queries/actions | Medium |
| New features | main.wasp + src/ new directory | Medium |
| Payment provider | Toggle PAYMENTS_PROCESSOR env var | Low |
| Remove AI demo | Delete src/demo-ai-app/ + wasp entries | Low |