Fourteen SystemsFourteen Systems

Documentation

SaaSCoreX documentation is structured around the production lifecycle — installation, architecture, billing, deployment, and operational safeguards. Every subsystem is documented with enforcement boundaries and invariants.

Design Philosophy

Core principles that govern every decision.

  • Tenancy-first architectureEvery query, every action, every job is org-scoped by default.
  • Entitlements over plan namesGate on capabilities, never on what a plan is called.
  • Explicit server boundariesNo UI-only protection. All enforcement is server-side.
  • Progressive enhancementDisable billing, teams, or audit without touching schema.
  • Infrastructure before UISystems are correct before screens are pretty.

Getting Started

From install to running dev server.

npx create-saascorex my-app
# CLI generates and scopes your .env
pnpm db:migrate
pnpm dev

Existing repo setup

If you already have the repo cloned, use the built-in setup wizard and environment doctor:

pnpm saascorex:setup        # creates .env, generates Prisma, runs migrations
pnpm saascorex:setup --seed # same + loads demo data
pnpm saascorex:doctor       # verify env, DB, Stripe, and Prisma health

The doctor script exits with code 0 when all checks pass, making it suitable for CI health gates.

Required secrets

DATABASE_URLPostgreSQL connection string
NEXTAUTH_SECRETAuth secret — generate with openssl rand -base64 32
RESEND_API_KEYEmail delivery via Resend

All variables are scoped and auto-generated by the CLI where possible.

Optional (progressive enhancement)
STRIPE_*Enable billing (Stripe checkout, webhooks, portal)
GOOGLE_CLIENT_ID / GITHUB_CLIENT_IDOAuth providers
REDIS_URLMulti-instance rate limiting
INNGEST_*Production job processing
NEXT_PUBLIC_SENTRY_DSNError monitoring

Demo Data

pnpm demo:seed
  • 3 organizations
  • 8 users (varied roles)
  • 10 projects

Architecture

Production structure and package boundaries.

pnpm monorepo managed by Turborepo. Seven internal packages, one web application.

PackagePurpose
@saascorex/coreAuth, billing, teams, audit, API keys, RBAC, 2FA business logic
@saascorex/dbPrisma client, schema, and migrations
@saascorex/uiComponent library (Radix + Tailwind)
@saascorex/configPlans, features, brand, marketing config
@saascorex/emailTransactional emails (React Email + Resend)
@saascorex/jobsBackground jobs (Inngest)
@saascorex/features-runtimeFeature flag runtime (server + client)

Stack

Next.js 16React 19TypeScriptPostgreSQLPrisma 6NextAuth v5StripeResendInngestTailwind CSSRadixTurborepo

Email

Transactional email templates with live preview and brand-aware styling.

Four production-ready templates built with React Email and delivered via Resend. All templates pull colors, logos, and copy from @saascorex/config so branding stays consistent.

TemplateTrigger
magic-linkPasswordless sign-in
welcomeFirst sign-up
org-inviteTeam member invitation
billing-stateSubscription created, cancelled, payment failed, trial ending

Dev preview

The email dev server runs on localhost:3002 alongside the main app. Edit templates in your IDE and see changes instantly in the browser.

pnpm dev          # starts all services including email preview
# or run standalone:
pnpm --filter @saascorex/email dev

Graceful degradation

In development, email functions log to the console when RESEND_API_KEY is not set — no crashes, no silent failures. In production, missing keys throw immediately so you catch misconfigurations at deploy time.

Customization

Templates live in packages/email/src/templates/. Colors reference brand.theme.primaryHex from the shared config. Update the brand config once and every template reflects the change.

Conventions

Architectural rules that prevent data leakage, entitlement drift, and cross-tenant access errors.

Tenancy & Data Safety

All queries scoped by orgId. Server actions require requireAuth() or requireOrg(). Last-owner protection enforced server-side.

Billing & Entitlements

Gate on entitlements, never plan names. Webhooks idempotent. Plan switching with Stripe proration. Real-time usage metering on dashboard and billing pages. Billing emails respect notification preferences.

Security

Tokens SHA-256 hashed. Rate limits at sensitive edges. TOTP two-factor auth with backup codes. Zod validation on all admin actions. Audit log records all privileged actions.

Feature Flags

Three layers must agree when off: routes, UI, server actions. Prisma models always present — flags gate behavior, not schema.

Architecture Boundaries

Apps don’t import from other apps. Core contains business logic. UI is pure presentation. Server actions call into core.

Active Org Resolution

activeOrgId lives in Session table. requireOrg() reads from DB on every request — never client state.

Billing & Entitlements

Stripe integration, webhook configuration, and entitlement enforcement.

Billing is enabled by default via Stripe. Set billing: "off" in packages/config/src/features.ts to disable. All Stripe code is lazy-loaded when disabled. The dashboard and billing page show real-time usage metering with color-coded progress bars (green under 75%, warning at 75–90%, danger above 90%) for projects, team members, and API requests.

Required Stripe variables

STRIPE_SECRET_KEY=sk_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRO_MONTHLY_PRICE_ID=price_...
STRIPE_PRO_YEARLY_PRICE_ID=price_...

Required webhook events

Endpoint: /api/stripe/webhook

  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted
  • invoice.payment_failed

Code structure

packages/core/src/billing/

  • webhook-handler.tsIdempotent event processor (deduped by stripeEventId)
  • subscription-service.tsSubscription CRUD, plan switching with proration
  • checkout-service.tsCheckout session creation
  • entitlements.tsPlan entitlement rules and enforcement
  • reconciliation.tsNightly Stripe↔DB sync

Jobs & Background Tasks

Inngest-powered scheduled functions. All idempotent. All safe for multi-instance deployments.

FunctionSchedulePurpose
billing-reconciliation3 AM dailyStripe↔DB sync. Heals drift from missed webhooks.
audit-purge4 AM SundaysRemoves audit entries older than 90 days.
cleanup-expired2 AM dailyDeletes expired sessions, tokens, unaccepted invites.
webhook-retryEvery 15 minRetries failed webhook events (max 5 attempts).
data-exportEvent-drivenGathers org data and emails as JSON attachment.

billing-reconciliation and webhook-retry only run when billing is enabled. audit-purge only runs when audit logging is enabled. cleanup-expired always runs.

Adding a new job

Create a new file in packages/jobs/src/functions/. Each function calls inngest.createFunction() with a unique ID, a cron schedule or event trigger, and an async handler. Wrap each logical operation in step.run("step-id", ...) for automatic retries with exponential backoff. Idempotency comes from unique step IDs (Inngest replays successful steps on re-execution) and deterministic database operations. Export the function and add it to the allFunctions array in packages/jobs/src/index.ts.

REST API

21-endpoint REST API with OpenAPI 3.1 spec, consistent envelopes, and per-endpoint rate limiting.

SaaSCoreX ships a production-ready REST API authenticated via API keys. The OpenAPI 3.1 spec is served at /api/v1/openapi.json and a discovery endpoint at /api/v1 lists all available endpoints with auth and rate limit info.

Authentication

Pass an API key as a Bearer token in the Authorization header:

Authorization: Bearer sk_a1b2c3d4e5f6...

API keys are created in the dashboard or via POST /api/v1/api-keys. Each key is scoped to an organization and inherits the creator’s role permissions.

Response format

All endpoints return a consistent envelope:

// Success (single resource)
{ "data": { "id": "...", "name": "..." } }

// Success (list with pagination)
{ "data": { "items": [...], "meta": { "page": 1, "pageSize": 20, "total": 42, "totalPages": 3 } } }

// Error
{ "error": { "code": "FORBIDDEN", "message": "Insufficient permissions", "requestId": "req_..." } }

Endpoints

MethodPathDescription
GET/api/v1Discovery endpoint
GET/api/v1/healthHealth check
GET/api/v1/openapi.jsonOpenAPI 3.1 spec
GET/api/v1/meCurrent user + role + permissions
GET/api/v1/orgOrganization details
PATCH/api/v1/orgUpdate organization
GET/api/v1/membersList members
PATCH/api/v1/members/:idUpdate member role
DELETE/api/v1/members/:idRemove member
GET/api/v1/invitationsList invitations
POST/api/v1/invitationsCreate invitation
DELETE/api/v1/invitations/:idRevoke invitation
GET/api/v1/projectsList projects
POST/api/v1/projectsCreate project
GET/api/v1/projects/:idGet project
PATCH/api/v1/projects/:idUpdate project
DELETE/api/v1/projects/:idDelete project
GET/api/v1/api-keysList API keys
POST/api/v1/api-keysCreate API key
DELETE/api/v1/api-keys/:idRevoke API key
GET/api/v1/billingBilling summary
POST/api/v1/billing/portalCreate billing portal session
GET/api/v1/auditQuery audit logs

Rate limiting

Each endpoint has per-key rate limits. When exceeded, the API returns 429 with Retry-After, X-RateLimit-Limit, and X-RateLimit-Remaining headers. Default: 100 requests per 60 seconds for standard endpoints.

CORS

CORS is enabled with configurable allowed origins via API_CORS_ORIGINS environment variable. Preflight requests are handled automatically.

Deployment

Standalone Node server via Next.js output tracing. Five platform guides included.

  • Vercel

    Connect repo, set root to apps/web, add PostgreSQL. In-memory rate limiter only — set REDIS_URL for production.

  • Docker

    docker compose up starts PostgreSQL + app. --profile redis adds Redis for multi-instance rate limiting. 3-stage Dockerfile included.

  • Railway

    Add PostgreSQL via dashboard, configure build and start commands, set DOCKER_BUILD=1 for standalone output.

  • Fly.io

    fly launch, fly postgres create, configure secrets, deploy. Health check at /api/health.

  • Self-Hosted

    Build with Turborepo, run standalone Node server. Bring your own PostgreSQL and reverse proxy.

Production recommendations

  • Database connection pooling for >10 concurrent connections
  • Automated database backups
  • Run pnpm db:migrate in CI/CD before deploying
  • Set REDIS_URL for multi-instance deployments
  • Security headers auto-configured (HSTS, CSP, X-Frame-Options)
  • Health check at GET /api/health

Full instructions in PRODUCTION.md and docs/deploy.md in the repo.

Testing

Unit, integration, and end-to-end test infrastructure.

pnpm test              # All tests (unit + integration)
pnpm test:unit         # Unit tests only
pnpm test:integration  # Integration tests
pnpm test:e2e          # Playwright E2E
pnpm test:billing      # Billing integration tests

E2E coverage

  • critical-pathDashboard, settings, project CRUD, team members, audit
  • authSign-in, sign-up, redirects
  • orgOrg management, switching, member management
  • marketingMarketing pages load correctly

First-time setup: pnpm exec playwright install chromium

Command Reference

Every script available in the monorepo.

CommandDescription
pnpm devStart dev server
pnpm buildBuild all packages
pnpm db:migrateRun Prisma migrations
pnpm db:seedSeed base data
pnpm demo:seedLoad demo data (3 orgs, 8 users)
pnpm db:studioOpen Prisma Studio
pnpm saascorex:setupInteractive setup wizard (env, Prisma, migrations)
pnpm saascorex:setup --seedSetup + load demo data
pnpm saascorex:doctorVerify environment health (env, DB, Stripe, Prisma)
pnpm typecheckTypeScript type checking
pnpm lintLint all code
pnpm formatFormat all code (Prettier)
pnpm testRun all tests
pnpm test:e2eRun Playwright E2E tests
pnpm cleanDelete build artifacts + node_modules