Learn from OSS
Open SaaS

Architecture Overview

Open SaaS system design — database schema, authentication, payments, file uploads, and AI integration

Architecture Overview

For Product Managers

This page explains how Open SaaS is structured as a software system. Focus on the diagrams and the "Why It Matters" callouts to understand how the technical pieces support the business logic.

System Architecture Diagram

Open SaaS is a full-stack monolith — frontend and backend deploy as a single unit via the Wasp framework.

┌─────────────────────────────────────────────────────────────┐
│                      USERS (Browser)                        │
└──────────────────────────┬──────────────────────────────────┘

              ┌────────────▼────────────┐
              │   Wasp Application       │
              │                          │
              │  ┌─────────────────────┐ │
              │  │  React Frontend     │ │  ← ShadCN UI + Tailwind
              │  │  (SPA)              │ │     client-side routing
              │  └────────┬────────────┘ │
              │           │ Wasp RPC      │  ← auto-generated, type-safe
              │  ┌────────▼────────────┐ │
              │  │  Node.js Backend    │ │  ← Express under the hood
              │  │  (API + Jobs)       │ │
              │  └────────┬────────────┘ │
              └───────────┼──────────────┘

           ┌──────────────┼──────────────────┐
           │              │                  │
    ┌──────▼──────┐ ┌────▼──────┐   ┌───────▼──────┐
    │ PostgreSQL   │ │   PgBoss  │   │   External   │
    │  (Prisma)    │ │(Job Queue)│   │   Services   │
    │              │ │           │   │              │
    │ • Users      │ │ • Cron    │   │ • Stripe     │
    │ • Tasks      │ │ • Email   │   │ • LemonSqzy  │
    │ • Files      │ │ • Stats   │   │ • OpenAI     │
    │ • GptResp    │ │           │   │ • AWS S3     │
    │ • DailyStats │ └───────────┘   │ • Analytics  │
    └──────────────┘                 └──────────────┘

Why It Matters

Unlike Plane (which has separate frontend, API, and real-time services), Open SaaS is a monolith — frontend and backend are one deployable unit. Wasp acts as the "glue" that connects them with auto-generated, type-safe RPC calls. This makes it much simpler to deploy and reason about, at the cost of less horizontal scalability.

How Wasp Works (The Magic Layer)

Wasp is the key to understanding Open SaaS. The main.wasp file is the single source of truth for the entire application:

main.wasp

  ├── Routes        → which pages exist and their URLs
  ├── Auth          → which auth methods are enabled
  ├── Queries       → read operations (auto-typed frontend → backend)
  ├── Actions       → write operations (auto-typed frontend → backend)
  ├── Jobs          → background tasks (cron or one-off)
  ├── Entity        → which Prisma models to expose
  └── Dependencies  → npm packages used

What Wasp generates:

  • Express routes for every query/action
  • React hooks (useQuery, useAction) for calling them from the frontend
  • Full TypeScript types that flow from Prisma schema → backend → frontend
  • Auth middleware, session management, and user context
  • Job scheduling infrastructure using PgBoss

For PMs: Why This Matters

Wasp eliminates an entire category of bugs — the frontend and backend always agree on what data looks like because types are auto-generated from the database schema. A developer changes the database, and TypeScript immediately flags every place in the frontend that needs updating. This dramatically reduces "integration bugs" where the frontend sends the wrong data format.

Database Schema

Open SaaS uses PostgreSQL with Prisma ORM. The schema is defined in schema.prisma.

Entity Relationship Map

┌───────────────────────────────────────┐
│                 User                  │
│  ─────────────────────────────────    │
│  id, email, username                  │
│  isAdmin                              │
│  subscriptionStatus, subscriptionPlan │
│  credits (default: 3)                 │
│  paymentProcessorUserId               │
└───┬──────┬──────┬──────┬──────────────┘
    │      │      │      │
    ▼      ▼      ▼      ▼
  Tasks  Files  GptResp  ContactForm
                  │       Messages


            DailyStats (aggregated analytics)

User Model (Core Entity)

FieldTypePurpose
idUUIDPrimary key
emailString (unique)Login identifier
usernameString (unique)Display name
isAdminBooleanAdmin access flag
subscriptionStatusString?active, canceled, past_due, etc.
subscriptionPlanString?hobby, pro, or null
creditsInt (default: 3)Available AI credits
paymentProcessorUserIdString?Stripe/LemonSqueezy customer ID
datePaidDateTime?Last payment date

Supporting Models

ModelKey FieldsPurpose
GptResponsecontent (JSON string), userStores AI-generated schedules
Taskdescription, time, isDone, userUser tasks for AI scheduling
Filename, type, s3Key, userUploaded file metadata
DailyStatsdate, totalViews, userCount, revenueAggregated analytics
ContactFormMessagecontent, userUser-submitted contact forms

Authentication System

How Auth Works

Auth is configured declaratively in main.wasp:

auth: {
  userEntity: User,
  methods: {
    email: {
      emailVerification: { ... },
      passwordReset: { ... },
    },
    // google: {},   ← uncomment to enable
    // github: {},   ← uncomment to enable
    // discord: {},  ← uncomment to enable
  },
}

Supported Methods

MethodStatusDetails
Email/PasswordActiveEmail verification required, password reset via email
Google OAuthReady to enableUncomment in config + add credentials
GitHub OAuthReady to enableSame
Discord OAuthReady to enableSame

Admin Designation

Admins are set via environment variable at signup:

ADMIN_EMAILS=admin@example.com,cto@example.com

When a user signs up with one of these emails, isAdmin is automatically set to true. Simple, no database migration needed.

Payment Processing

Open SaaS supports two payment providers through an abstraction layer.

Payment Processor Interface

PaymentProcessor
  ├── id: "stripe" | "lemonsqueezy"
  ├── createCheckoutSession()    → redirect user to payment page
  ├── fetchCustomerPortalUrl()   → self-service subscription management
  ├── webhook()                  → handle payment events
  └── webhookMiddlewareConfigFn  → verify webhook signatures

Why Dual Providers?

Stripe is the gold standard but isn't available in all countries and requires business verification. Lemon Squeezy is a Merchant of Record — it handles tax compliance globally, making it ideal for solo developers and creators. Supporting both maximizes the template's usefulness worldwide.

Stripe Flow

  1. User clicks "Subscribe" → Checkout Session created
  2. Stripe hosts the payment page (PCI compliant)
  3. On success: webhook fires invoice.paid → Plane updates user's subscription
  4. Recurring: Stripe auto-charges → webhook confirms → subscription stays active
  5. Cancellation: webhook fires → status updated to canceled

Lemon Squeezy Flow

Same pattern, different events: order_created, subscription_created, subscription_updated, subscription_cancelled, subscription_expired.

Webhook Security

Both providers use HMAC signature verification to ensure webhook requests are genuine:

  • Stripe: Stripe-Signature header with timestamp and signature
  • Lemon Squeezy: X-Signature header with HMAC-SHA256 (timing-safe comparison)

File Upload System

S3 Presigned URL Flow

User selects file


Frontend calls backend action


Backend generates presigned S3 URL (time-limited, secure)


Frontend uploads directly to S3 (bypasses backend)


Backend stores file metadata in DB (name, type, s3Key)


File available for download via S3 key

Why Presigned URLs?

The file never passes through the Node.js server — it goes directly from the browser to S3. This means the backend doesn't need to handle large file transfers, keeping it fast and lightweight. The presigned URL is time-limited (expires in minutes) and scoped to a specific S3 key, so it's secure.

AI Integration

How the AI Demo Works

Open SaaS includes a demo AI scheduling assistant:

  1. User creates tasks with descriptions and time estimates
  2. User clicks "Generate Schedule"
  3. Backend calls OpenAI GPT-3.5-turbo with function calling
  4. AI returns a structured schedule (JSON)
  5. Schedule is stored in GptResponse and displayed to the user
  6. 1 credit deducted (or free if user has active subscription)

Credit System Logic

User requests AI operation

    ├── Has active subscription? → Allow (unlimited)

    └── Has credits > 0?
          ├── Yes → Deduct 1 credit (atomic DB operation) → Allow
          └── No  → Return 402 (Payment Required)

Analytics System

ProviderTypePrivacy
Google AnalyticsFull trackingRequires cookie consent
PlausiblePrivacy-firstNo cookies, GDPR compliant

Daily Stats Aggregation: A background job (PgBoss cron) runs daily to aggregate page views, user counts, revenue, and traffic sources into the DailyStats table for the admin dashboard.

Admin Dashboard

The admin panel is built with ShadCN UI and provides:

  • User list with search, subscription status, and admin toggle
  • Daily metrics — views, signups, revenue, churn
  • Charts — user growth and revenue trends over time
  • System overview — key metrics at a glance

Only users with isAdmin: true can access the admin routes.

What's Next