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 usedWhat 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)
| Field | Type | Purpose |
|---|---|---|
id | UUID | Primary key |
email | String (unique) | Login identifier |
username | String (unique) | Display name |
isAdmin | Boolean | Admin access flag |
subscriptionStatus | String? | active, canceled, past_due, etc. |
subscriptionPlan | String? | hobby, pro, or null |
credits | Int (default: 3) | Available AI credits |
paymentProcessorUserId | String? | Stripe/LemonSqueezy customer ID |
datePaid | DateTime? | Last payment date |
Supporting Models
| Model | Key Fields | Purpose |
|---|---|---|
| GptResponse | content (JSON string), user | Stores AI-generated schedules |
| Task | description, time, isDone, user | User tasks for AI scheduling |
| File | name, type, s3Key, user | Uploaded file metadata |
| DailyStats | date, totalViews, userCount, revenue | Aggregated analytics |
| ContactFormMessage | content, user | User-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
| Method | Status | Details |
|---|---|---|
| Email/Password | Active | Email verification required, password reset via email |
| Google OAuth | Ready to enable | Uncomment in config + add credentials |
| GitHub OAuth | Ready to enable | Same |
| Discord OAuth | Ready to enable | Same |
Admin Designation
Admins are set via environment variable at signup:
ADMIN_EMAILS=admin@example.com,cto@example.comWhen 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 signaturesWhy 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
- User clicks "Subscribe" → Checkout Session created
- Stripe hosts the payment page (PCI compliant)
- On success: webhook fires
invoice.paid→ Plane updates user's subscription - Recurring: Stripe auto-charges → webhook confirms → subscription stays active
- 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-Signatureheader with timestamp and signature - Lemon Squeezy:
X-Signatureheader 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 keyWhy 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:
- User creates tasks with descriptions and time estimates
- User clicks "Generate Schedule"
- Backend calls OpenAI GPT-3.5-turbo with function calling
- AI returns a structured schedule (JSON)
- Schedule is stored in
GptResponseand displayed to the user - 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
| Provider | Type | Privacy |
|---|---|---|
| Google Analytics | Full tracking | Requires cookie consent |
| Plausible | Privacy-first | No 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.