---
title: "How Open SaaS Is Built"
description: "Repository structure, Wasp configuration, dependencies, build pipeline, and deployment"
image: "https://tractiongrowth.com/og/open-source/open-saas-wasp-starter/how-it-is-built/image.png"
---

# How Open SaaS Is Built

# How Open SaaS Is Built

Open SaaS is built around a Wasp application template that generates a React frontend, Node.js backend, Prisma data layer, and deployment-ready project structure from one source of truth. This page explains the repository layout, framework orchestration, dependencies, and deployment flow that make the template practical to fork and extend.

<Callout title="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.
</Callout>

## 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

| 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]
}
```

<Callout type="info" title="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.
</Callout>

## 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                                |

<Callout type="info" title="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.
</Callout>

## Build System

### Development

```bash
# 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

```bash
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

```bash
# 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:

```bash
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

| 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`                    |
| **Email**     | `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.ts
```

Run with:

```bash
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 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    |

## What's Next

<Cards>
  <Card title="What Is Open SaaS?" description="What it is, who it's for, and what's included." href="/open-source/open-saas-wasp-starter" />

  <Card title="Open SaaS Architecture Overview" description="System design, data model, and how Wasp connects the frontend and backend." href="/open-source/open-saas-wasp-starter/architecture" />

  <Card title="Open SaaS Workflows and Data Flows" description="User signup, payment, AI features, and file uploads traced end-to-end." href="/open-source/open-saas-wasp-starter/workflows" />
</Cards>

