Architecture Overview
System design, data flow, multi-tenancy implementation, and architectural decisions behind ShopySeed.
ShopySeed is built as a modern, scalable SaaS application using a monorepo architecture with TypeScript throughout the stack. This document explains the system design, data flow, and architectural decisions.
System Overview
┌─────────────────────────────────────────────────────────────────┐
│ ShopySeed Architecture │
├─────────────────────────────────────────────────────────────────┤
│ Frontend (Next.js) │ Backend (NestJS) │
│ ┌─────────────────────┐ │ ┌─────────────────────┐ │
│ │ Next.js 14 │────┼─▶│ NestJS API │ │
│ │ App Router │ │ │ TypeScript Strict │ │
│ │ Tailwind CSS │ │ │ Prisma ORM │ │
│ │ Shadcn/ui │ │ │ JWT Authentication │ │
│ └─────────────────────┘ │ └─────────────────────┘ │
│ │ │ │ │
│ │ │ ▼ │
│ │ │ ┌─────────────────────┐ │
│ │ │ │ PostgreSQL Database │ │
│ │ │ │ Schema-based │ │
│ │ │ │ Multi-tenancy │ │
│ │ │ └─────────────────────┘ │
│ │ │ │ │
│ │ │ ▼ │
│ │ │ ┌─────────────────────┐ │
│ │ │ │ Redis Cache │ │
│ │ │ │ Session Storage │ │
│ │ │ └─────────────────────┘ │
│ │ │ │
│ │ │ ┌─────────────────────┐ │
│ └─────────────────┼─▶│ Stripe Integration │ │
│ │ │ Webhooks & Portal │ │
│ │ └─────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Shared Packages │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ @shopyseed/shared │ │ Turborepo │ │
│ │ TypeScript Types │ │ Build System │ │
│ └─────────────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘Monorepo Structure
ShopySeed uses Turborepo to manage a TypeScript monorepo with optimized builds and shared dependencies.
Package Architecture
shopyseed/
├── apps/
│ ├── api/ # Backend API (NestJS)
│ └── web/ # Frontend Web App (Next.js)
├── packages/
│ └── shared/ # Shared TypeScript Types
├── docker/ # Docker configurations
├── docs/ # Documentation
└── turbo.json # Turborepo configurationDependency Graph
┌─────────────┐ ┌─────────────┐
│ web │ │ api │
│ (Next.js) │ │ (NestJS) │
└─────┬───────┘ └─────┬───────┘
│ │
└─────┬────────────┘
▼
┌─────────────┐
│ shared │
│ (Types) │
└─────────────┘Benefits:
- Type Safety: Shared types ensure API and frontend stay in sync
- Fast Builds: Turborepo's caching optimizes build times
- Atomic Updates: Changes to shared types update both apps
- Code Reuse: Common utilities and types shared across packages
Backend Architecture (NestJS)
Module Structure
src/
├── app.module.ts # Root application module
├── main.ts # Application bootstrap
├── common/ # Shared utilities and guards
│ ├── decorators/ # Custom decorators
│ ├── guards/ # Authentication & authorization guards
│ └── logger/ # Structured logging
├── auth/ # Authentication & authorization
│ ├── auth.controller.ts
│ ├── auth.service.ts
│ ├── auth.module.ts
│ ├── strategies/ # Passport strategies
│ └── dto/ # Data transfer objects
├── organizations/ # Multi-tenant organizations
├── billing/ # Stripe integration
├── health/ # Health check endpoints
└── prisma/ # Database moduleRequest Lifecycle
1. Request → Middleware → Guards → Controller → Service → Response
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
CORS JWT Auth RBAC Business Database
Rate Limit Validation Checks Logic Access
Logging Tenant Validation
ResolutionDetailed Flow
-
Middleware Layer
- CORS handling for frontend access
- Rate limiting to prevent abuse
- Request logging for monitoring
- Body parsing and validation
-
Authentication Guard (
JwtAuthGuard)- Validates JWT access token
- Extracts user information
- Handles refresh token rotation
-
Organization Guard (
OrganizationGuard)- Resolves tenant context
- Sets PostgreSQL search_path
- Ensures data isolation
-
Authorization Guard (
RolesGuard)- Checks user permissions (Owner/Admin/Member/Viewer)
- Route-level access control
- Hierarchical permission checking
-
Controller Layer
- HTTP request handling
- Input validation with DTOs
- Swagger documentation
-
Service Layer
- Business logic implementation
- Database operations via Prisma
- External API integrations
Multi-Tenancy Implementation
ShopySeed uses PostgreSQL schema-based isolation for multi-tenancy:
Tenant Isolation Flow
1. Request with Organization ID
│
▼
2. OrganizationGuard validates access
│
▼
3. Set search_path = 'tenant_abc123'
│
▼
4. All queries run in tenant schema
│
▼
5. Complete data isolation per tenantDatabase Schema Structure
-- Shared schema (public)
public.users -- Global user accounts
public.organizations -- Organization metadata
public.organization_members -- User-organization relationships
public.subscriptions -- Billing information
-- Per-tenant schemas (tenant_*)
tenant_abc123.projects -- Tenant-specific data
tenant_abc123.tasks -- Completely isolated
tenant_abc123.documents -- No cross-tenant accessBenefits:
- Complete Data Isolation: No risk of data leaks between tenants
- Performance: Each tenant has optimized indexes
- Scalability: Can move schemas to separate databases
- Compliance: Meets strict data isolation requirements
Authentication Flow
JWT + Refresh Token Pattern
┌─────────────────┐ ┌─────────────────┐
│ Frontend │ │ Backend │
└─────────────────┘ └─────────────────┘
│ │
│ 1. Login (email/password) │
├─────────────────────────────────▶│
│ │
│ 2. Access Token + Refresh Token │
│◀─────────────────────────────────┤
│ │
│ 3. API Request + Access Token │
├─────────────────────────────────▶│
│ │
│ 4. API Response │
│◀─────────────────────────────────┤
│ │
│ 5. Token Expires (401) │
│◀─────────────────────────────────┤
│ │
│ 6. Refresh Request │
├─────────────────────────────────▶│
│ │
│ 7. New Access Token │
│◀─────────────────────────────────┤Token Strategy
- Access Tokens: Short-lived (15 minutes), contains user + organization context
- Refresh Tokens: Long-lived (7 days), single-use, stored in database
- Rotation: Each refresh generates new refresh token, revokes old one
- Security: Compromised tokens auto-expire, minimal attack window
Token Payload Structure
interface AccessTokenPayload {
sub: string; // User ID
email: string; // User email
organizationId?: string; // Current organization context
role?: MemberRole; // Role in current organization
iat: number; // Issued at
exp: number; // Expires at
}Frontend Architecture (Next.js)
App Router Structure
src/app/
├── layout.tsx # Root layout with providers
├── page.tsx # Landing page
├── globals.css # Global styles
├── auth/ # Authentication pages
│ ├── login/page.tsx
│ ├── register/page.tsx
│ └── email-verified/page.tsx
├── dashboard/ # Protected dashboard area
│ ├── layout.tsx # Dashboard layout with navigation
│ ├── page.tsx # Dashboard home
│ ├── settings/ # User settings
│ ├── organization/ # Organization management
│ └── billing/ # Billing and subscriptions
└── api/ # API routes (if needed)State Management
ShopySeed uses React's built-in state management with strategic patterns:
Context Providers
// Authentication Context
const AuthContext = createContext<{
user: User | null;
organization: Organization | null;
login: (credentials) => Promise<void>;
logout: () => void;
switchOrganization: (orgId: string) => void;
}>()
// Theme Context (for dark/light mode)
const ThemeContext = createContext<{
theme: 'light' | 'dark';
setTheme: (theme: Theme) => void;
}>()Data Fetching Strategy
- Server Components: For initial page loads and SEO-critical content
- Client Components: For interactive components and real-time data
- SWR/React Query: For client-side data fetching with caching
- API Routes: For form submissions and client-server communication
Component Architecture
components/
├── ui/ # Base UI components (Shadcn/ui)
│ ├── button.tsx
│ ├── input.tsx
│ └── dialog.tsx
├── forms/ # Form components
│ ├── auth-forms.tsx
│ ├── organization-forms.tsx
│ └── billing-forms.tsx
├── layout/ # Layout components
│ ├── navigation.tsx
│ ├── sidebar.tsx
│ └── header.tsx
└── features/ # Feature-specific components
├── auth/
├── dashboard/
├── organizations/
└── billing/Billing Integration (Stripe)
Stripe Webhook Flow
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Stripe │ │ ShopySeed API │ │ Database │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ 1. Subscription Event │ │
├─────────────────────▶ │ │
│ │ │
│ │ 2. Verify Signature │
│ ├──────────────────────▶│
│ │ │
│ │ 3. Update Subscription│
│ ├──────────────────────▶│
│ │ │
│ 4. Acknowledge (200) │ │
│◀─────────────────────── │ │Supported Webhook Events
customer.subscription.created- New subscriptioncustomer.subscription.updated- Plan changes, renewalscustomer.subscription.deleted- Cancellationsinvoice.payment_succeeded- Successful paymentsinvoice.payment_failed- Failed payments
Subscription Management
interface Subscription {
id: string;
organizationId: string;
stripeCustomerId: string;
stripeSubscriptionId: string;
plan: string; // 'free' | 'starter' | 'pro' | 'enterprise'
status: SubscriptionStatus; // 'active' | 'past_due' | 'canceled' | 'trialing'
currentPeriodStart: DateTime;
currentPeriodEnd: DateTime;
cancelAtPeriodEnd: boolean;
}Plan Enforcement
- Usage Limits: Enforced at API level based on subscription plan
- Feature Gates: Frontend components conditionally render based on plan
- Billing Guards: NestJS guards prevent API access for expired subscriptions
Data Flow Patterns
API Request Flow
Frontend Backend Database
│ │ │
│ 1. HTTP Request │ │
├────────────────────▶│ │
│ │ │
│ │ 2. Auth Validation │
│ ├────────────────────▶│
│ │ │
│ │ 3. Business Logic │
│ │ │
│ │ 4. Database Query │
│ ├────────────────────▶│
│ │ │
│ │ 5. Query Results │
│ │◀────────────────────┤
│ │ │
│ 6. HTTP Response │ │
│◀────────────────────┤ │Error Handling Strategy
Backend Error Handling
// Global Exception Filter
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
if (exception instanceof HttpException) {
// Handle known HTTP exceptions
return response.status(exception.getStatus()).json({
statusCode: exception.getStatus(),
message: exception.message,
timestamp: new Date().toISOString(),
});
}
// Handle unknown errors
logger.error('Unexpected error:', exception);
return response.status(500).json({
statusCode: 500,
message: 'Internal server error',
});
}
}Frontend Error Boundaries
// Error Boundary for graceful error handling
class ErrorBoundary extends Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
render() {
if (this.state.hasError) {
return <ErrorFallback error={this.state.error} />;
}
return this.props.children;
}
}Security Architecture
Defense in Depth
┌─────────────────────────────────────────────────────────────┐
│ Security Layers │
├─────────────────────────────────────────────────────────────┤
│ 1. Network Level │
│ • HTTPS/TLS encryption │
│ • CORS policy enforcement │
│ • Rate limiting per IP │
├─────────────────────────────────────────────────────────────┤
│ 2. Application Level │
│ • JWT token validation │
│ • Input validation & sanitization │
│ • SQL injection prevention (Prisma) │
├─────────────────────────────────────────────────────────────┤
│ 3. Authorization Level │
│ • Role-based access control │
│ • Resource-level permissions │
│ • Multi-tenant data isolation │
├─────────────────────────────────────────────────────────────┤
│ 4. Data Level │
│ • Database connection encryption │
│ • Schema-based tenant isolation │
│ • Sensitive data hashing │
└─────────────────────────────────────────────────────────────┘Security Headers
// Helmet configuration for security headers
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
imgSrc: ["'self'", "data:", "https:"],
scriptSrc: ["'self'"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
}));Performance Considerations
Database Optimization
- Indexing Strategy: Optimized indexes for multi-tenant queries
- Connection Pooling: Prisma connection pool optimization
- Query Optimization: N+1 query prevention with Prisma includes
Caching Strategy
// Redis caching for frequently accessed data
@Injectable()
export class CacheService {
async get<T>(key: string): Promise<T | null> {
const cached = await this.redis.get(key);
return cached ? JSON.parse(cached) : null;
}
async set(key: string, value: any, ttl: number = 3600): Promise<void> {
await this.redis.setex(key, ttl, JSON.stringify(value));
}
}Frontend Performance
- Code Splitting: Automatic route-based code splitting
- Image Optimization: Next.js Image component with optimization
- Bundle Analysis: Webpack bundle analyzer integration
Monitoring and Observability
Structured Logging
// Structured logging service
@Injectable()
export class LoggerService {
info(message: string, meta?: Record<string, any>) {
console.log(JSON.stringify({
level: 'info',
message,
timestamp: new Date().toISOString(),
...meta,
}));
}
}Health Checks
/health- Basic application health/health/ready- Database and Redis connectivity- Kubernetes-compatible health endpoints
Deployment Architecture
Docker Multi-Stage Builds
# Multi-stage build for production optimization
FROM node:20-alpine AS base
# ... base configuration
FROM base AS dependencies
# Install dependencies
FROM base AS build
# Build applications
FROM base AS runtime
# Production runtimeEnvironment Separation
- Development: Docker Compose with hot reloading
- Staging: Kubernetes with production-like configuration
- Production: Kubernetes with full monitoring and backup
Scalability Patterns
Horizontal Scaling
- Stateless API: No server-side session state
- Database Read Replicas: Read queries can be distributed
- CDN Integration: Static assets served from CDN
- Microservice Ready: Modular architecture allows service extraction
Vertical Scaling
- Database Optimization: Query optimization and indexing
- Connection Pooling: Efficient database connection management
- Memory Management: Optimized for low memory footprint
This architecture provides a solid foundation for building scalable, secure SaaS applications. Each component is designed with production requirements in mind, including security, performance, and maintainability.