skills/thesaifalitai/claude-setup/fullstack-architecture

fullstack-architecture

SKILL.md

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)
Weekly Installs
3
GitHub Stars
4
First Seen
3 days ago
Installed on
opencode3
gemini-cli3
claude-code3
github-copilot3
codex3
kimi-cli3