Learn from OSS
Open SaaS

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 suite

Key Files

FilePurpose
main.waspCentral configuration — routes, auth, entities, queries, actions, jobs
schema.prismaDatabase 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

PackagePurposeWhy Chosen
React 18.2UI frameworkWasp's default frontend
TypeScript 5.8Type safetyEnd-to-end types from Prisma
Tailwind CSS 3.2StylingUtility-first, fast iteration
ShadCN UI v2Component libraryAccessible, customizable, modern
Radix UIHeadless primitivesFoundation for ShadCN components
React Router DOM 6Client routingWasp's default router
React Hook Form 7Form managementPerformant, minimal re-renders
Zod 3Schema validationTypeScript-native validation
RechartsData visualizationAdmin dashboard charts

Backend Dependencies

PackagePurposeWhy Chosen
Wasp 0.18Full-stack frameworkAuto-generates auth, RPC, jobs
Prisma 5.19ORMType-safe DB access
PgBossJob queueUses existing PostgreSQL (no Redis needed)
Stripe SDKPayment processingIndustry standard
@lemonsqueezy/lemonsqueezy.jsAlt paymentsMerchant of Record
OpenAI SDKAI integrationGPT function calling
@aws-sdk/client-s3File storageS3 presigned URLs
@sendgrid/mailTransactional emailEmail delivery

Infrastructure

ServicePurpose
PostgreSQLPrimary database + job queue storage (PgBoss)
AWS S3File storage
Stripe / Lemon SqueezyPayment processing
OpenAIAI text generation
SendGrid / MailgunEmail delivery
Google Analytics / PlausibleUser 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 everything

wasp start runs:

  1. PostgreSQL migration check
  2. Node.js backend with hot reload
  3. React frontend with hot reload (Vite)
  4. PgBoss job scheduler

Production Build

wasp build                # generates deployable artifacts

This 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 fly

Wasp handles:

  1. Building frontend and backend
  2. Setting up PostgreSQL database
  3. Running migrations
  4. Configuring environment variables
  5. 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 separately

Required Environment Variables

CategoryVariables
DatabaseDATABASE_URL
AuthJWT_SECRET, ADMIN_EMAILS
PaymentsSTRIPE_KEY, STRIPE_WEBHOOK_SECRET or LEMONSQUEEZY_API_KEY, LEMONSQUEEZY_WEBHOOK_SECRET
AIOPENAI_API_KEY
StorageAWS_S3_BUCKET, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION
EmailSENDGRID_API_KEY or SMTP config
AnalyticsGOOGLE_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.ts

Run with:

cd e2e-tests
npx playwright test

CI/CD

GitHub Actions runs on every commit:

  1. Lint (ESLint + TypeScript)
  2. Build check
  3. Playwright E2E tests

Customization Guide

For developers using Open SaaS as a starting point, here's what to customize:

What to ChangeWhereEffort
Brandingsrc/landing-page/, Tailwind themeLow
Payment planssrc/payment/plans.ts + Stripe dashboardLow
Auth methodsmain.wasp auth configLow
Database modelsschema.prisma + new queries/actionsMedium
New featuresmain.wasp + src/ new directoryMedium
Payment providerToggle PAYMENTS_PROCESSOR env varLow
Remove AI demoDelete src/demo-ai-app/ + wasp entriesLow

What's Next