Full-Stack SaaS Architecture With Next.js and Node.js
How I structure a production SaaS from scratch — folder layout, API design, auth, database modelling, and deployment pipeline — using Next.js and Node.js.
I have set up the same SaaS foundation many times across different products. The stack choices vary slightly, but the structural decisions are consistent. This is the architecture I start with when building a new full-stack SaaS product with Next.js on the frontend and Node.js on the backend.
The Stack
- Frontend: Next.js 14+ (App Router), TypeScript, Tailwind CSS
- Backend: Node.js with Express, TypeScript
- Database: PostgreSQL with Prisma ORM
- Auth: NextAuth.js or custom JWT depending on requirements
- Cache: Redis for sessions and rate limiting
- File storage: AWS S3 or Cloudflare R2
- Email: Resend
- Payments: Stripe
- Deployment: Vercel (frontend) + Railway or AWS ECS (backend)
Folder Structure
The Next.js frontend follows a feature-based structure rather than type-based:
src/
app/ # Next.js App Router pages
(auth)/ # Route group — auth pages
(dashboard)/ # Route group — app pages
api/ # API routes (lightweight, proxy to backend)
components/
ui/ # Generic reusable components
[feature]/ # Feature-specific components
lib/
api.ts # Typed API client
auth.ts # Auth helpers
utils.ts
hooks/ # Custom hooks
stores/ # Zustand storesThe Node.js backend mirrors this with a layered structure:
src/
routes/ # Express routers — HTTP only
services/ # Business logic
repositories/ # Database queries
middleware/ # Auth, validation, rate limiting
lib/
db.ts # Prisma client singleton
redis.ts # Redis client
types/ # Shared TypeScript typesAuthentication
For most SaaS products, I implement email/password auth plus Google OAuth as a minimum. The pattern I use:
- JWTs for API authentication — short-lived access tokens (15 minutes) and longer refresh tokens (7 days) stored in httpOnly cookies
- Refresh token rotation on every use — if a refresh token is reused, it invalidates the entire family (stolen token detection)
- Email verification before account activation
- Password reset via time-limited signed tokens
If the product is NextJS-only (no separate backend API), NextAuth.js handles most of this cleanly. When there is a separate Node.js API, custom JWT implementation gives more control and avoids the coupling to the Next.js deployment.
Multi-Tenancy
Most SaaS products need some concept of organisations or workspaces. The database pattern I use is row-level tenancy — every tenant-scoped table has an organisation_idcolumn, and every query includes a tenant filter.
The important part: enforce this at the service layer with a tenant context object, not just at the route level. If the current user's organisation ID is not threaded through every data access call, you will eventually ship a data leak. I have seen this happen.
Subscription and Billing
Stripe is the only reasonable choice here. The integration that works well in production:
- Stripe Checkout for the initial subscription flow — no custom payment form to maintain
- Stripe Customer Portal for plan changes and cancellations
- Webhook handler that updates your database when subscription status changes
- A
subscriptionstable in your database that mirrors the Stripe state — never query Stripe directly to check if a user has access, check your own database
Deployment Pipeline
The setup I use for most products:
- GitHub Actions for CI — run tests and type checks on every PR
- Preview deployments on Vercel for every PR — visible URLs to share with stakeholders
- Merge to main deploys frontend to Vercel automatically
- Backend deployed to Railway or AWS ECS — Docker image built in CI, deployed on merge to main
- Database migrations run as part of the deployment, before the new backend version starts serving traffic
What I Skip in the Early Stage
Microservices, message queues, and event-driven architecture are real tools for real problems. They are not good starting points for an early-stage SaaS. A well-structured monolith with clean boundaries is faster to build, easier to debug, and straightforward to split later if a specific component genuinely needs to scale independently.
If you are building a SaaS product and want to talk through the architecture, I am happy to help.