SaaS
Architecture
Full Stack
Startup
Backend
Ask anything about this article
Hi! I've read this article.
What would you like to know?
@farhan
Building a SaaS product requires making the right technical decisions early. Here's a battle-tested architecture guide.
| Layer | Recommended | Alternatives |
|---|---|---|
| Frontend | Next.js 15 | Remix, Nuxt |
| UI | Tailwind + shadcn/ui | Material UI |
| Backend | Next.js API / Node.js | FastAPI, Go |
| Database | PostgreSQL | PlanetScale (MySQL) |
| ORM | Prisma / Drizzle | TypeORM |
| Auth | NextAuth / Clerk | Auth0, Supabase |
| Payments | Stripe | Paddle, Lemon Squeezy |
| Resend | SendGrid, Postmark | |
| File Storage | S3 / R2 | Cloudflare R2 |
| Hosting | Vercel / Railway | AWS, Fly.io |
| Monitoring | Sentry + Posthog | DataDog |
Option 1: Shared database, shared schema (easiest)
users table: [id, tenant_id, name, email]
Every query: WHERE tenant_id = ?
Option 2: Shared database, separate schemas
tenant_1.users, tenant_2.users
Option 3: Separate databases per tenant (most isolated)
db_tenant_1, db_tenant_2
prismamodel Organization {
id String @id @default(cuid())
name String
plan Plan @default(FREE)
members Member[]
projects Project[]
createdAt DateTime @default(now())
}
model Member {
id String @id @default(cuid())
userId String
organizationId String
role Role @default(MEMBER)
user User @relation(fields: [userId], references: [id])
organization Organization @relation(fields: [organizationId], references: [id])
@@unique([userId, organizationId])
}
typescript// Create checkout session
const session = await stripe.checkout.sessions.create({
customer: customerId,
mode: 'subscription',
line_items: [{
price: 'price_pro_monthly',
quantity: 1,
}],
success_url: '{DOMAIN}/billing?success=true',
cancel_url: '{DOMAIN}/billing?canceled=true',
});
// Webhook handler for subscription events
export async function POST(req: Request) {
const event = stripe.webhooks.constructEvent(body, sig, secret);
switch (event.type) {
case 'customer.subscription.created':
await activatePlan(event.data.object);
break;
case 'customer.subscription.deleted':
await deactivatePlan(event.data.object);
break;
case 'invoice.payment_failed':
await handleFailedPayment(event.data.object);
break;
}
}
typescriptconst PLAN_FEATURES = {
FREE: {
maxProjects: 3,
maxMembers: 2,
apiCalls: 1000,
features: ['basic_analytics'],
},
PRO: {
maxProjects: 50,
maxMembers: 20,
apiCalls: 100000,
features: ['basic_analytics', 'advanced_analytics', 'api_access', 'custom_domain'],
},
ENTERPRISE: {
maxProjects: Infinity,
maxMembers: Infinity,
apiCalls: Infinity,
features: ['basic_analytics', 'advanced_analytics', 'api_access', 'custom_domain', 'sso', 'audit_log', 'sla'],
},
};
Building a SaaS is a marathon, not a sprint. Get the architecture right, and everything else becomes easier.