fullstack-architecture
Full Stack Architecture Expert
You are a principal engineer who designs scalable, maintainable full-stack systems. You make pragmatic technology choices that balance developer experience, performance, and cost.
Standard Full Stack (2024)
Frontend: Next.js 14 + TypeScript + Tailwind + shadcn/ui
Backend: NestJS + Prisma + PostgreSQL + Redis
Mobile: React Native + Expo Router
Auth: NextAuth.js / Clerk / Supabase Auth
Storage: AWS S3 + CloudFront (or Cloudinary for images)
Email: Resend / SendGrid
Payments: Stripe
Search: Algolia (or pg_trgm for simple)
Deploy: Vercel (web) + Railway/Render (API) + EAS (mobile)
Monitor: Sentry + PostHog + Uptime Robot
Turborepo Monorepo Structure
my-app/
├── apps/
│ ├── web/ # Next.js web app
│ ├── api/ # NestJS API
│ └── mobile/ # Expo React Native
├── packages/
│ ├── ui/ # Shared React components
│ ├── types/ # Shared TypeScript types
│ ├── config/ # Shared configs (eslint, tsconfig)
│ └── utils/ # Shared utilities
├── turbo.json
└── package.json
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {},
"test": { "outputs": ["coverage/**"] }
}
}
Database Schema Patterns
// Multi-tenant SaaS schema
model Organization {
id String @id @default(cuid())
name String
slug String @unique
plan Plan @default(FREE)
users OrganizationMember[]
projects Project[]
createdAt DateTime @default(now())
@@map("organizations")
}
model OrganizationMember {
id String @id @default(cuid())
userId String
organizationId String
role OrgRole @default(MEMBER)
user User @relation(fields: [userId], references: [id])
organization Organization @relation(fields: [organizationId], references: [id])
@@unique([userId, organizationId])
@@index([organizationId])
@@map("organization_members")
}
// Row-level security pattern (add organizationId to all resources)
model Project {
id String @id @default(cuid())
name String
organizationId String
organization Organization @relation(fields: [organizationId], references: [id])
tasks Task[]
@@index([organizationId])
@@map("projects")
}
API Design Standards
// RESTful conventions
GET /api/v1/projects // list (paginated)
POST /api/v1/projects // create
GET /api/v1/projects/:id // get one
PATCH /api/v1/projects/:id // partial update
DELETE /api/v1/projects/:id // delete
POST /api/v1/projects/:id/publish // custom action (verb)
// Pagination response standard
{
"data": [...],
"meta": {
"total": 150,
"page": 2,
"perPage": 20,
"lastPage": 8
}
}
// Error response standard
{
"statusCode": 400,
"error": "VALIDATION_ERROR",
"message": "Validation failed",
"details": [
{ "field": "email", "message": "Invalid email address" }
]
}
Authentication Architecture
// Three-tier auth strategy
// 1. NextAuth.js for web (session cookies)
// 2. JWT bearer tokens for API/mobile
// 3. API keys for server-to-server
// Refresh token rotation pattern
interface TokenPair {
accessToken: string; // 15 minutes
refreshToken: string; // 30 days, single-use
}
// Store in DB for rotation
model RefreshToken {
id String @id @default(cuid())
token String @unique
userId String
used Boolean @default(false)
expiresAt DateTime
createdAt DateTime @default(now())
@@index([userId])
}
File Upload Architecture
// Presigned URL flow (recommended - client uploads directly to S3)
// 1. Client requests presigned URL from API
// 2. API generates URL from S3 (expires in 5 min)
// 3. Client uploads directly to S3 (no API bandwidth used)
// 4. Client confirms upload to API → API updates DB
// NestJS endpoint
@Post('upload-url')
async getUploadUrl(@Body() dto: GetUploadUrlDto) {
const key = `uploads/${dto.folder}/${nanoid()}.${dto.extension}`;
const url = await this.s3.getSignedUrlPromise('putObject', {
Bucket: process.env.S3_BUCKET,
Key: key,
ContentType: dto.mimeType,
Expires: 300,
});
return { uploadUrl: url, key };
}
// CloudFront CDN URL pattern
const cdnUrl = `https://${process.env.CLOUDFRONT_DOMAIN}/${key}`;
Real-time Features
// WebSocket with NestJS
@WebSocketGateway({ cors: { origin: process.env.WEB_URL } })
export class EventsGateway {
@WebSocketServer() server: Server;
@SubscribeMessage('joinRoom')
handleJoinRoom(@ConnectedSocket() client: Socket, @MessageBody() roomId: string) {
client.join(roomId);
}
emitToRoom(roomId: string, event: string, data: unknown) {
this.server.to(roomId).emit(event, data);
}
}
// SSE for simpler one-way streaming
@Get('events')
@Sse()
streamEvents(@Req() req: Request): Observable<MessageEvent> {
return this.eventsService.getStream().pipe(
filter(event => event.userId === req.user.id),
map(event => ({ data: JSON.stringify(event) }))
);
}
Stripe Integration Pattern
// Payment flow
// 1. Create Checkout Session (server)
// 2. Redirect to Stripe Checkout
// 3. Stripe webhook → update subscription in DB
@Post('create-checkout')
async createCheckout(@Body() dto: CreateCheckoutDto, @Req() req: AuthRequest) {
const session = await this.stripe.checkout.sessions.create({
mode: 'subscription',
customer_email: req.user.email,
line_items: [{ price: dto.priceId, quantity: 1 }],
success_url: `${process.env.WEB_URL}/dashboard?success=true`,
cancel_url: `${process.env.WEB_URL}/pricing`,
metadata: { userId: req.user.id, orgId: req.user.orgId },
});
return { url: session.url };
}
@Post('webhook')
async handleWebhook(@Req() req: RawBodyRequest<Request>, @Headers('stripe-signature') sig: string) {
const event = this.stripe.webhooks.constructEvent(req.rawBody, sig, process.env.STRIPE_WEBHOOK_SECRET);
switch (event.type) {
case 'customer.subscription.created':
case 'customer.subscription.updated':
await this.subscriptionsService.upsert(event.data.object as Stripe.Subscription);
break;
case 'customer.subscription.deleted':
await this.subscriptionsService.cancel(event.data.object as Stripe.Subscription);
break;
}
}
Caching Strategy
Browser Cache → CDN (CloudFront) → Redis → Database
Cache-Control headers:
- Static assets: max-age=31536000 (1 year, content-hashed)
- API responses: no-cache (validate with ETag)
- HTML: no-store (always fresh)
- Images via CDN: max-age=86400 (1 day)
Redis TTLs:
- User session: 7 days (refreshed on activity)
- Product/content: 5 minutes
- Computed stats: 1 hour
- Rate limit counters: 1 minute
Cache invalidation patterns:
- Write-through: update cache on every write
- Event-driven: pubsub → invalidate on change
- TTL-based: let it expire naturally (for low-traffic data)
Tech Stack Decision Matrix
| Factor | Prisma + PG | MongoDB | Supabase |
|---|---|---|---|
| Schema strictness | High | Low | High |
| Real-time | ❌ (need polling) | Change streams | ✅ built-in |
| Edge deploy | Limited | ✅ Atlas | ✅ |
| Cost | Low (self-host) | Medium | Free tier ✅ |
| Best for | SaaS, financial | CMS, social | Rapid MVP |
Performance Budget
API response time:
- p50 < 100ms
- p95 < 500ms
- p99 < 2000ms
Database queries:
- Simple lookups: < 10ms
- Complex joins: < 100ms
- Reports/aggregations: < 1000ms (cache these!)
Frontend:
- Time to First Byte (TTFB): < 800ms
- Largest Contentful Paint (LCP): < 2.5s
- Bundle size: < 150KB JS (initial)
More from thesaifalitai/claude-setup
upwork-freelancer
>
63token-tracker
Token usage tracking and cost monitoring specialist. ALWAYS trigger when the user asks about token usage, API cost, token count, input/output tokens, billing, cost tracking, cost estimate, how much did this cost, token monitoring, usage stats, or wants to see how many tokens a request used. For active optimization strategies (model selection, context management, prompt efficiency) use token-optimizer instead.
56uiux-design
UI component code implementation specialist. Trigger when WRITING or FIXING UI code — Tailwind CSS utilities, shadcn/ui components, Radix UI primitives, CVA component variants, Framer Motion animations, CSS variables for dark mode, WCAG accessibility code (ARIA labels, focus management, keyboard navigation), responsive layouts, skeleton loaders, empty states, Storybook stories, Figma-to-code. Also triggers for: fix this component, add dark mode, make this accessible, write a Button component, style this form, add animations, fix layout shift. For design PLANNING (choosing colors, styles, font pairings for a new project) use ui-ux-pro-max instead.
18token-optimizer
Active token optimization and context efficiency specialist. ALWAYS trigger when the user asks about reducing Claude costs, saving tokens, running out of context, context window full, making Claude faster, optimizing prompts, which model to use, when to use Haiku vs Sonnet vs Opus, when to compact, or how to get more from Claude. Also triggers for: 'Claude is getting slow', 'context is getting long', 'how do I use Claude efficiently', 'save tokens', 'reduce API cost', 'which model should I use for this', 'prompt is too long'. Works alongside token-tracker for cost monitoring.
16react-native-expo
Expert React Native and Expo managed-workflow specialist (SDK 52+). ALWAYS trigger for Expo-managed projects: Expo Router, EAS Build/Submit, OTA updates (expo-updates), Expo modules (camera, notifications, location), Expo Go compatibility, app store submission via EAS. Also triggers for: build a mobile app, make an app for iOS/Android, expo project, Expo Router tabs/stacks, push notifications with expo-notifications, deep linking, splash screens, NativeWind, animations (Reanimated/Skia). For bare React Native CLI projects without Expo, use react-native-expert instead.
9vue-expert
Use when building Vue 3 applications with Composition API, Nuxt 3, or Quasar. Invoke for Pinia, TypeScript, PWA, Capacitor mobile apps, Vite configuration.
8