Build a SaaS
Structure a multi-tenant SaaS on Codexa - a React frontend, a Node + Express API, email/password auth, workspaces and Stripe or Razorpay billing.
On this page
Codexa's templates are single-purpose: a browser app, a static site or an API. A SaaS needs the first and the third. This guide builds them as two projects in the same workspace and wires them together.
Architecture
┌──────────────────┐ fetch /api/* ┌──────────────────────┐ SQL ┌────────────┐
│ frontend project │ ───────────────▶ │ api project (Express) │ ──────▶ │ PostgreSQL │
│ Vite + React │ │ sessions, tenants │ │ (yours) │
└──────────────────┘ └──────────────────────┘ └────────────┘
The frontend deploys to Codexa hosting (static). The API runs in a preview during development and is hosted on a Node host of your choice for production; Codexa hosting is static only. See Deployment.
Step 1: The API project
- Create a project from the Node + Express template; name it
saas-api. - Connect a database and add
DATABASE_URLas an environment variable. - Ask the agent, in Build mode:
Set up the data model for a multi-tenant SaaS with pg:
- tables: users, workspaces, memberships(role: owner|member), sessions
- a migrations/ folder with SQL files and an `npm run migrate` script
- bcrypt password hashing; httpOnly session cookie; requireUser middleware
- routes: POST /auth/signup, /auth/login, /auth/logout, GET /me,
POST /workspaces, GET /workspaces
- every workspace route checks membership server-side
Done when `npm run typecheck` and `npm run build` pass.
- Run the preview and use the Logs panel to confirm the server starts. Run the migration with Run command → test if you wired it there, or from your machine against the same database.
Step 2: The frontend project
- Create a project from a Vite + React template; name it
saas-web. - Add
VITE_API_URLas an environment variable pointing at the API preview URL. - Ask the agent:
Add sign-up, sign-in and a workspace switcher that talk to the API at
import.meta.env.VITE_API_URL with credentials: "include".
Protected routes redirect to /login when GET /me returns 401.
Step 3: Billing
Add a Stripe (international) or Razorpay (India) connection under workspace Integrations so the credential is tested, then add the secret as STRIPE_SECRET_KEY or RAZORPAY_KEY_ID/RAZORPAY_KEY_SECRET in the API project.
Add subscription billing with Stripe Checkout:
- POST /billing/checkout creates a Checkout session for the current workspace
- POST /webhooks/stripe verifies the signature, stores event ids in a
webhook_events table and ignores duplicates, and updates
workspaces.plan on checkout.session.completed and
customer.subscription.deleted
- never trust plan changes from the client
That is the same pattern Codexa uses for its own billing: verify, deduplicate, then act. See Webhooks.
Step 4: Enforce limits server-side
Whatever your plans promise, check them in the API before the action happens, and return a consistent error. Codexa returns 402 quota_exceeded; copying that shape keeps the frontend simple.
Step 5: Deploy
saas-web→ Codexa hosting, withVITE_API_URLset to the production API URL at build time.saas-api→ push to GitHub from the GitHub panel and deploy to your Node host; pointDATABASE_URLand the session secret at production values there.
Checklist before launch
- Passwords hashed, sessions httpOnly, CORS restricted to your origin.
- Every workspace route checks membership.
- Webhook signature verified and events deduplicated.
- Environment variables set in both projects; none committed to GitHub.