Skip to content

Examples/SaaS

Subscription SaaS (example)

A Next.js app with a Postgres database, Stripe subscriptions, a background job queue for emails and invoices, and file uploads to S3. Replace any part with how your own system works.

Scale: 2,000 paying teams; 30 req/s at peak

12345678CLIENTNext.js frontendSERVICEAPI routesDATABASEPostgresEXTERNALStripeQUEUEJob queueWORKERBackgroundworkerOBJECT STORES3EXTERNALEmail provider

Select a component to see what it is responsible for and which state it owns.

  1. 1Next.js frontend → API routes: Sign up and upgrade plan
  2. 2API routes → Postgres: Read and write accounts
  3. 3API routes → Stripe: Create checkout session
  4. 4Stripe → API routes: Subscription webhooks
  5. 5API routes → Job queue: Enqueue welcome email and invoice
  6. 6Background worker → Job queue: Claim jobs
  7. 7Background worker → Email provider: Send email
  8. 8Background worker → S3: Store invoice PDF
  • Request / response
  • Asynchronous
  • Bulk data

Select a part to trace its flows.

  • Request / response
  • Asynchronous
  • Bulk data

Parts 8

Flows 8

  1. 1Next.js frontend → API routesSign up and upgrade planRequest / response
  2. 2API routes → PostgresRead and write accountsRequest / response
  3. 3API routes → StripeCreate checkout sessionRequest / response
  4. 4Stripe → API routesSubscription webhooksAsynchronous
  5. 5API routes → Job queueEnqueue welcome email and invoiceAsynchronous
  6. 6Background worker → Job queueClaim jobsRequest / response
  7. 7Background worker → Email providerSend emailRequest / response
  8. 8Background worker → S3Store invoice PDFBulk data

Invariants 3

  • A team has at most one active subscription

    Partial unique index on subscriptions(team_id) WHERE status = 'active'; the webhook handler upserts by Stripe subscription id.

    Kept by

  • A team is upgraded only after Stripe confirms payment

    The plan changes only in the verified invoice.paid webhook handler; the checkout redirect shows a pending state.

    Kept by

  • Each Stripe event changes state once

    stripe_events(event_id) primary key inserted in the same transaction as the state change; a duplicate insert aborts the transaction.

    Kept by