A production-ready, multi-tenant AI SaaS boilerplate built with NestJS, Next.js 15, and Anthropic Claude. Includes authentication, usage metering, plan limits, team management, and Stripe billing — all wired up and ready to customize.
- Multi-tenant architecture — Organization → Member → User with role-based access (owner / admin / member)
- Claude AI streaming — Real-time SSE chat powered by
claude-sonnet-4-5 via Anthropic SDK
- Usage metering — Token counting per conversation, monthly aggregate tracking, plan limit enforcement
- Plan tiers — Free (50K tokens), Pro (500K), Enterprise (5M); enforced at the API layer
- Stripe billing — Checkout sessions, customer portal, webhook handler; gracefully degrades when Stripe is not configured
- JWT auth — Access + refresh tokens, bcrypt password hashing, organization context embedded in JWT
- BullMQ email queue — Ready-to-extend transactional email via Nodemailer
- Next.js 15 App Router — Dark-themed dashboard: chat, team, billing, usage overview
| Layer |
Technology |
| Backend |
NestJS 11, TypeORM, PostgreSQL 15 |
| AI |
Anthropic Claude SDK (@anthropic-ai/sdk) with streaming |
| Auth |
JWT (access + refresh), bcrypt, Passport |
| Billing |
Stripe (graceful no-op when unconfigured) |
| Frontend |
Next.js 15 App Router, TypeScript, Tailwind CSS |
| State |
Zustand with persistence |
| CI |
GitHub Actions |
Browser → Next.js (App Router) → NestJS API → PostgreSQL
↓
Anthropic Claude
↓
Usage Tracking DB
# 1. Start PostgreSQL
cd backend && docker compose up -d
# 2. Configure backend
cp .env.example .env
# Add your ANTHROPIC_API_KEY — Stripe keys are optional (billing degrades gracefully)
npm install && npm run start:dev
# 3. Start frontend
cd ../frontend
echo "NEXT_PUBLIC_API_URL=http://localhost:3003" > .env.local
npm install && npm run dev
| Method |
Path |
Description |
| POST |
/auth/register |
Register + create organization |
| POST |
/auth/login |
Login |
| POST |
/auth/refresh |
Refresh tokens |
| Method |
Path |
Auth |
Description |
| POST |
/ai/chat |
JWT |
Stream chat (SSE) |
| GET |
/ai/conversations |
JWT |
List conversations |
| GET |
/ai/conversations/:id/messages |
JWT |
Get messages |
| DELETE |
/ai/conversations/:id |
JWT |
Delete conversation |
| Method |
Path |
Auth |
Description |
| GET |
/usage/me |
JWT |
Monthly token usage |
| GET |
/usage/me/limit |
JWT |
Check current plan limit |
| Method |
Path |
Auth |
Description |
| GET |
/organizations/me |
JWT |
Get org details |
| GET |
/organizations/me/members |
JWT |
List members |
| DELETE |
/organizations/me/members/:id |
JWT |
Remove member |
| PATCH |
/organizations/me/members/:id/role |
JWT |
Update member role |
| Method |
Path |
Auth |
Description |
| POST |
/billing/checkout |
JWT |
Create Stripe checkout session |
| POST |
/billing/portal |
JWT |
Open Stripe billing portal |
| POST |
/billing/webhook |
— |
Stripe webhook handler |
| Plan |
AI Tokens/Month |
Members |
Conversations |
| Free |
50,000 |
3 |
10 |
| Pro |
500,000 |
20 |
Unlimited |
| Enterprise |
5,000,000 |
Unlimited |
Unlimited |
PORT=3003
DB_HOST=localhost
DB_PORT=5435
DB_USERNAME=postgres
DB_PASSWORD=postgres
DB_DATABASE=ai_saas
JWT_SECRET=...
JWT_EXPIRES_IN=15m
JWT_REFRESH_SECRET=...
JWT_REFRESH_EXPIRES_IN=7d
ANTHROPIC_API_KEY=sk-ant-...
STRIPE_SECRET_KEY=sk_test_... # Optional — billing degrades gracefully
STRIPE_WEBHOOK_SECRET=whsec_... # Optional
STRIPE_PRICE_PRO=price_... # Optional
STRIPE_PRICE_ENTERPRISE=price_...
SMTP_HOST=...
SMTP_PORT=465
SMTP_USER=...
SMTP_PASS=...
NEXT_PUBLIC_API_URL=http://localhost:3003
NEXT_PUBLIC_STRIPE_PRICE_PRO=price_... # Optional
NEXT_PUBLIC_STRIPE_PRICE_ENTERPRISE=price_... # Optional
cd backend && npm test
# 8 tests: AuthService (ConflictException, register flow, bad password, bad refresh token)
# OrganizationsService (ForbiddenException, NotFoundException, successful remove)
# app.controller (hello world)